找到
39
篇与
admin
相关的结果
- 第 2 页
-
如何在华为鸿蒙系统上运行Qt开发的移动端安卓应用:具体的版本支持指南 本文详细解析了Qt开发的安卓应用(APK文件)在华为鸿蒙系统上的兼容性问题,关键取决于鸿蒙版本。HarmonyOS 4.0及之前版本基于AOSP,基本完全兼容安卓应用;而HarmonyOS NEXT(星河版)及未来5.0版本将不再支持安卓APK,需使用Qt for HarmonyOS或原生ArkTS/ArkUI开发。文章提供了版本兼容性对比及开发建议,帮助开发者适配鸿蒙生态。 这段时间我正在使用Qt来开发移动端应用。发到我的华为手机竟然不能用。于是我仔细查找了一下版本支持情况。Qt开发的安卓应用(APK文件)能否在您的华为手机上运行,完全取决于您手机搭载的鸿蒙系统版本。 HarmonyOS 兼容Android应用的情况: HarmonyOS NEXT: 不再兼容Android应用,是一个完全独立的系统,基于OpenHarmony开源项目开发。 HarmonyOS 4.0及之前版本: 兼容Android应用,用户可以继续使用在Android系统下安装的应用。 HarmonyOS 4.0的兼容性: 尽管HarmonyOS 4.0删除了部分与Android相关的底层代码,但仍然可以通过华为的“虚拟引擎”技术在一定程度上兼容Android应用。 HarmonyOS 5.0及以后版本: 计划完全放弃双框架系统,不再兼容和支持Android应用,只支持专门为HarmonyOS系统开发的应用。 简单来说,就是鸿蒙系统存在两个主要分支,它们对安卓应用的兼容性有本质区别: 鸿蒙系统 2.0 - 4.0: 这些版本是基于安卓开源项目(AOSP)构建的,因此完全兼容安卓应用。使用Qt for Android生成的标准APK文件,可以像在其他安卓手机上一样正常安装和运行。 鸿蒙星河版 (HarmonyOS NEXT): 这是一个全新的、完全独立的操作系统,不兼容安卓应用。它拥有自己的应用生态和格式。如果您的手机是这个版本,将无法直接运行任何安卓APK文件。 鸿蒙星河版图片 如果手机是鸿蒙星河版,在这种情况下,Qt for Android生成的APK文件将无法运行。需要为原生的鸿蒙系统进行开发。好消息是,Qt官方正在积极与华为合作,为HarmonyOS NEXT提供原生支持。于是将有两个选择: 使用Qt for HarmonyOS: Qt Group已经发布了针对鸿蒙系统的适配版本。这意味着可以使用Qt框架,但需要针对HarmonyOS进行编译和打包,而不是生成APK。 原生鸿蒙开发: 也可以学习使用华为官方推荐的ArkTS语言和ArkUI开发框架,在DevEco Studio开发环境中进行纯粹的鸿蒙原生应用开发。 Qt for OpenHarmony/zh 可以在Qt官网上看看“Qt for HarmonyOS”是如何使用的: Qt for HarmonyOS图片 -
Qt 6.9安卓开发环境配置全指南:从零搭建到项目部署(附避坑指南) 本文提供一份完整的Qt 6.9安卓开发环境配置教程,涵盖Qt安装器配置、JDK/SDK/NDK安装、环境变量设置、OpenSSL配置等关键步骤。针对国内开发者特别提供镜像加速方案,并详细说明每个组件的版本选择要点(如JDK 11/17、NDK 27.x兼容性),解决"安卓设置存在错误"等常见问题。教程包含大量实操截图和验证方法,帮助开发者快速搭建可用的Qt安卓开发环境,特别适合Windows平台的新手用户。 Qt安装器 关于Qt的国内镜像安装器和换源,可以看这篇文章:详细记录通过Qt在线安装器安装Qt5.15系列版本 Qt换源图片 关于Qt里的选项可以参考我的选项,建议Qt里的一个版本里的内容全选,这里已经包含了编译等工具,对文本安卓开发来言,一定是要勾选“安卓”: Qt里的勾选图片 这里一定要勾选cmake: 勾选cmake图片 然后就是漫长的等待了,我这个配置可能需要四五十个GB的空间,非常不建议安装在C盘。 安装JDK 推荐使用 JDK 11 或 JDK 17,避免版本过高或过低导致兼容性问题。安装完成后,确保 JDK 的路径已正确配置到系统环境变量中。 Java Archive Downloads - Java SE 17.0.12 and earlier JDK 17安装图片 例如我的路径是D:\Program\Java\jdk-17。添加环境变量的方法如下: (1)Win10/Win11:右键点击「此电脑」→「属性」→「高级系统设置」→「环境变量」 (2)在「系统变量」区域: 点击「新建」 变量名:JAVA_HOME 变量值:D:\Program\Java\jdk-17 (确保路径指向你的 JDK 安装目录,不带 bin 文件夹) JAVA_HOME环境变量图片 变量名:CLASSPATH 变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar (3)修改 Path 变量 在「系统变量」中找到 Path 变量 点击「编辑」 点击「新建」,添加两条记录: %JAVA_HOME%\bin %JAVA_HOME%\jre\bin (4)打开新的命令提示符(CMD/PowerShell),运行: java -version javac -versionjava版本图片 安装SDK 去这里下载:AndroidDevTools - Android 开发工具 找到SDK Tools,然后下载ZIP文件 SDK Tools图片 双击SDK Manager.exe,安装SDK所需要的插件。 在此处,我们需要选择我们开发时所需要的插件,我选择的插件如下图所示: 注意事项:我们只安装自己需要的插件即可。小陶选择的是默认,内容包括如下: SDK Manager图片 需要注意,所有的都要同意霸王条款才能安装。 安装安卓sdk图片 安装NDK 安装NDK没有什么操作,只需要把下载好的压缩包,解压至Qt的安装文件夹下,在Qt中配置好即可。 注意NDK的版本问题,因为Qt对插件的版本支持有差异。 例如我的是Qt 6.9,官网的说明是“Qt 6.9 uses NDK 26.1.10909125 and 27.2.12479018” Qt NDK 版本图片 具体的版本你可以必应里面搜“Getting Started with Qt for Android 你的Qt版本号”: NDK版本图片 然后去下载NDK即可。传送门:NDK 下载 - Android Developers 发现最新 LTS 版本版本号是27.3.13750724,问题不大,大版本号是27,并且LTS长期支持版应该是兼容性很好的。 LTS 版本版本号图片 android-ndk-r27d压缩包解压之后内容如下: android-ndk-r27d压缩包图片 f0ad4e 配置Qt安卓设置 在Qt的首选项(工具—》外部—》配置)中,点击SDKs进行配置:JDK、SDK、NDK依次选择好路径即可。 SDKs进行配置图片 发现下面的提示“安卓设置存在错误”。其实在新版本的Qt中,可以自动得到修复。在详情里面,我框起来的可能会出现问题。现在我点击“设置SDK”,就会进行修复。 安卓设置存在错误图片 最终的应该是所有都是“✓”。 完全安装好图片 安卓OpenSSL设置 点击旁边的下载OpenSSL:android_openssl - Github In this repo you can find the prebuilt OpenSSL libs for Android, a QMake include project .pri file that can be used integrated with Qt projects, and a .cmake file for CMake based projects. The following directories are available ssl_3: for Qt 6.5.0+. ssl_1_1: for Qt Qt 5.12.5+, 5.13.1+, 5.14.0+, 5.15.0+, Qt 6.x.x up to 6.4.x 直接选择这个解压后的目录即可: 安卓OpenSSL图片 添加安卓套件 在构建套件中,我们看到了一些安卓的,虽然是有黄色感叹号。下面我们来配置套件。 黄色感叹号图片 如下图所示,进入设备,点击添加。然后选择“安卓设备”。点击“开始向导”。 安卓设备图片 然后新建一个Phone即可: 新建一个Phone图片 这样就有了一个模拟的硬件: 模拟安卓硬件图片 -
手动配置gradle详细教程 | Qt Creator在构建安卓应用时下载gradle 报错Connect timed out 本文详细介绍了Qt Creator在构建安卓应用时因网络问题导致Gradle下载超时(Connect timed out)的解决方案。通过分析报错信息,指出问题根源在于自动下载Gradle失败,并提供两种解决思路:优化网络环境或手动配置Gradle。文章重点讲解手动配置方法,包括从阿里云镜像站下载指定版本的Gradle、修改Qt Creator中的Gradle配置文件(如gradle.properties和gradle-wrapper.properties),以及调整本地路径格式。最后,指导用户完成构建并定位生成的APK文件路径,帮助开发者高效绕过网络限制,顺利完成安卓应用构建。 报错核心内容 报错里面最主要的信息就是下面这两句话: Downloading https://services.gradle.org/distributions/gradle-8.12-bin.zip Exception in thread "main" java.io.IOException: Downloading from https://services.gradle.org/distributions/gradle-8.12-bin.zip failed: timeout (10000ms) Caused by: java.net.SocketTimeoutException: Connect timed out简而言之,问题是 在构建安卓 APK 的过程中,下载 Gradle 超时了。 Qt 的构建系统尝试从 Gradle 官方网站下载 gradle-8.12-bin.zip 这个文件,但是在 10 秒钟(10000ms)内没有连接成功,导致了连接超时。这通常是由于网络环境访问国外网站速度慢或不稳定造成的。 解决问题就两条路:(1)网络换到稳定的外国去;(2)手动配置Gradle 手动配置Gradle教程 最直接和推荐的解决方法是 手动下载 Gradle 并配置到 Qt Creator 中。这样可以绕过构建过程中的自动下载步骤。 第一步:手动下载 Gradle 下载链接: 你需要下载日志中提示的完全相同的版本:gradle-8.12-bin.zip。 直接点击或复制此链接到浏览器下载: https://services.gradle.org/distributions/gradle-8.12-bin.zip 解压文件: 下载完成后,放到指定的目录,例如D:\Program\qtAndroid\gradle-8.12-bin.zip。 gradle下载到本地图片 当然,上面这个地址你都time out了,使用国内的镜像站吧: gradle安装包下载_开源镜像站-阿里云 然后选择合适自己的版本,例如我的版本是8.12。 阿里云镜像图片 第二步:Qt Creator 配置 Gradle路径 打开你的Qt安装目录,并找到这个目录:D:\Qt\6.9.1\android_x86_64\src\3rdparty\gradle D:\Qt是我安装Qt的地址。 Gradle路径图片 修改 gradle.properties 文件 org.gradle.jvmargs=-Xmx2500m 改为 org.gradle.jvmargs=-Xmx1024mgradle.properties 文件图片 然后修改 gradle-wrapper.properties 配置文件。进入 gradle/weapper 目录下修改 gradle-wrapper.properties 配置文件。由于无法在线下载,需要将原来的下载地址改为安装包名称,然后手动下载并放在该目录下。 distributionUrl=file:///D:/Program/qtAndroid/gradle-8.12-bin.zip注意,你需要把本地路径转换成标准的 file URI 格式。 使用 file:/ 或 file:/// 作为协议头。 将所有的反斜杠 \ 替换为正斜杠 /。 修改distributionUrl图片 然后再重新编译即可: 重新编译图片 构建的APK文件 文件夹结构示意图: <你的项目构建目录> └── android-build └── build └── outputs └── apk └── debug └── app-debug.apk <-- 就是这个文件!详细的报错信息 D:\Qt\6.9.1\mingw_64\bin\qmake6.exe -install qinstall -exe libqmake_x86_64.so C:\Users\Administrator\Desktop\0qmake\build\Qt_6_9_1_Clang_x86_64-Debug\android-build\libs\x86_64\libqmake_x86_64.so D:\Qt\6.9.1\mingw_64\bin\qmake6.exe -install qinstall -exe libqmake_x86_64.so C:\Users\Administrator\Desktop\0qmake\build\Qt_6_9_1_Clang_x86_64-Debug\android-build\libs\x86_64\libqmake_x86_64.so 18:43:58: The command "D:\Program\qtAndroid\android-ndk-r27d\prebuilt\windows-x86_64\bin\make.exe "INSTALL_ROOT=C:\Users\Administrator\Desktop\0qmake\build\Qt_6_9_1_Clang_x86_64-Debug\android-build" install && cd C:\Users\Administrator\Desktop\0qmake\build\Qt_6_9_1_Clang_x86_64-Debug && D:\Program\qtAndroid\android-ndk-r27d\prebuilt\windows-x86_64\bin\make.exe "INSTALL_ROOT=C:\Users\Administrator\Desktop\0qmake\build\Qt_6_9_1_Clang_x86_64-Debug\android-build" install" finished successfully. 18:43:58: 正在启动 "D:\Qt\6.9.1\mingw_64\bin\androiddeployqt.exe" --input C:/Users/Administrator/Desktop/0qmake/build/Qt_6_9_1_Clang_x86_64-Debug/android-qmake-deployment-settings.json --output C:/Users/Administrator/Desktop/0qmake/build/Qt_6_9_1_Clang_x86_64-Debug/android-build --android-platform android-35 --jdk D:/Program/Java/jdk-17 --gradle Generating Android Package Input file: C:/Users/Administrator/Desktop/0qmake/build/Qt_6_9_1_Clang_x86_64-Debug/android-qmake-deployment-settings.json Output directory: C:/Users/Administrator/Desktop/0qmake/build/Qt_6_9_1_Clang_x86_64-Debug/android-build/ Application binary: qmake Android build platform: android-35 Install to device: No Skipping createRCC Downloading https://services.gradle.org/distributions/gradle-8.12-bin.zip Exception in thread "main" java.io.IOException: Downloading from https://services.gradle.org/distributions/gradle-8.12-bin.zip failed: timeout (10000ms) at org.gradle.wrapper.Install.forceFetch(SourceFile:4) at org.gradle.wrapper.Install$1.call(SourceFile:8) at org.gradle.wrapper.GradleWrapperMain.main(SourceFile:67) Caused by: java.net.SocketTimeoutException: Connect timed out at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:551) at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602) at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) at java.base/java.net.Socket.connect(Socket.java:633) at java.base/sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:304) at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178) at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:534) at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:639) at java.base/sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:266) at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:380) at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193) at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241) at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127) at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179) at java.base/sun.net.www.protocol.http.HttpURLConnection.followRedirect0(HttpURLConnection.java:2940) at java.base/sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2849) at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1948) at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1610) at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:224) at org.gradle.wrapper.Install.forceFetch(SourceFile:2) ... 2 more Building the android package failed! -- For more information, run this command with --verbose. 18:44:13: The command "D:\Qt\6.9.1\mingw_64\bin\androiddeployqt.exe --input C:/Users/Administrator/Desktop/0qmake/build/Qt_6_9_1_Clang_x86_64-Debug/android-qmake-deployment-settings.json --output C:/Users/Administrator/Desktop/0qmake/build/Qt_6_9_1_Clang_x86_64-Debug/android-build --android-platform android-35 --jdk D:/Program/Java/jdk-17 --gradle --gdbserver" terminated with exit code 14. 18:44:13: Error while building/deploying project qmake (kit: 安卓 Qt 6.9.1 Clang x86_64) 18:44:13: When executing step "构建安卓 APK" 18:44:13: Elapsed time: 00:15. -
'start' is deprecated,Qt开发中新版QProcess的start到底该怎么用? 本文介绍了Qt开发中QProcess::start()方法被标记为废弃(deprecated)后的替代方案,帮助开发者正确使用新版API。文章详细讲解两种推荐方法:一是使用带参数列表的start(program, arguments),适用于需要传递参数的情况。二是使用setProgram() + start(),适用于更灵活的场景。同时提供代码示例,帮助Qt初学者快速适应新版API,优化进程启动方式,提升代码可维护性。 在写一个Qt程序的时候,按照B站的教程直接写的进程start,但是提示这个方法已经被deprecated('start' is deprecated. QProcess::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode = ReadWrite)),虽然帮助文档已经写的很清楚了,但是作为一名初学者,还是想写个笔记mark一下。 出现这个报错的提示图片 修复这个问题有两个选择: (1)一个是使用带参数列表的 start 函数。从提示看,后面已经写到const QStringList,也就是要跟个参数列表。 void Widget::on_CommitButton_clicked() { // 获取lineedit数据 QString program = ui->cmdlineEdit->text(); // 创建process对象 QProcess *myProcess = new QProcess(this); myProcess->start(program, QStringList()); // 传入空参数列表 }(2)使用 setProgram 和 start 分开的方式。 void Widget::on_CommitButton_clicked() { // 获取lineedit数据 QString program = ui->cmdlineEdit->text(); // 创建process对象 QProcess *myProcess = new QProcess(this); myProcess->setProgram(program); myProcess->start(); }推荐使用第一种方式,因为它更简洁。如果你需要传递参数给程序,可以在 QStringList 中添加参数。这种改变是为了让进程启动的API更加明确和灵活,特别是在需要传递参数的情况下。例如: myProcess->start("program", {"arg1", "arg2", "arg3"}); -
Typecho专属知识付费PayIT插件:基于支付宝当面付实现内容变现,简约实用 本文详细介绍了一款专为Typecho博客系统设计的知识付费插件PayIT,该插件基于支付宝当面付功能,帮助内容创作者轻松实现知识变现。文章全面解析了插件的核心功能,包括付费墙设置、订单管理、多域名授权机制等,并提供了详细的配置指南和使用示例。同时,针对个人用户特别说明了支付宝当面付基础版的申请流程和收款限额,分享了内容定价策略和付费墙设计技巧,为博主提供了一套完整的知识付费解决方案。通过PayIT插件,创作者可以灵活设置不同价格的付费内容,实现从免费预览到完整内容解锁的平滑过渡,有效提升内容变现能力。 主要功能 主要功能就是让读者为你独到的见解或者内容付费,pay it!为之付费,这款插件的名字就是这样来的。当然,it大写就是IT,可以理解为信息技术。 简而言之,就是为你的知识实现付费功能。效果可以见本页的下载处。 为什么选择当面付 现在微信支付是已经不支持个人用户使用API之类的了。直接对个人开发者闭门羹。现在连payjs都不再接受个人用户申请了。虎皮椒之类的需要先交钱再用,还要手续费,如果个人博客这种小生意就很不划算了。 支付宝的当面付原则上是需要营业执照的的,但是可以不上传,这也为个人提供了一个狭小的缝隙。缺点是有额度限制,限制规则为交易限额:单笔收款≤1000,单日收款≤5W,但是我觉得已经足够了。具体的在越后面 易支付之类的我默认都是卷钱跑路党,而且一般需要先交个两三百才能用。自建码支付属实有点麻烦。 综合来看,也就当面付是最好的选择。 插件下载 插件的PayWall.php是付费墙,里面可以修改展现给读者的样式。 插件的Orders.php是订单管理页,在启动插件之后,可以对订单进行查看,以及清除未支付的订单。 隐藏内容,请前往内页查看详情 关于开通当面付 没有同名营业执照的商家,可申请开通当面付基础版,每日收款有最高额度限制,限制规则为交易限额:单笔收款≤1000,单日收款≤5W,不区分借记或贷记渠道。 如商家签约基础版后,想要补充提交同名营业执照,不受收款额度限制,可以登录商家平台—产品中心—产品签约管理,通过资质凭证补全来取消额度限制,资料提交后等待1个工作日审核即可,点此进入产品签约管理 。(信息来源:当面付产品申请条件/收款限额) 我觉得对于个人博客而言,单笔收款≤1000,单日收款≤5W怎么说都够了,如果这都不能满足,那业务也足够开个公司了! 关于如何开通当面付,网上有非常多的教程,这里我不想一步步介绍了。开通的入口在这:登录支付宝开放平台 你可以参加下面这几篇文章: 个人用户无营业执照申请支付宝当面付教程 -- 十一张的博客 【站长必看】这可能是最详细完整的支付宝当面付申请与配置教程 支付 | 个人接入支付宝当面付实现web和h5支付(超详细步骤) 反正最终的目的是获得你的应用的“AppID”、“应用私钥 (RSA2)”、“支付宝公钥 (RSA2)”这三个东西。 插件配置 在上面的开通当面付之后,记得保存好“AppID”、“应用私钥 (RSA2)”、“支付宝公钥 (RSA2)”,然后依次复制粘贴到这个配置栏里。 默认价格:默认是五元,如果在在需要付费的地方没有写具体的价格话 Cookie 有效期 (游客):设置游客付费状态保存天数。登录用户付费后永久有效。 免支付用户列表:输入免支付用户的 "用户名,邮箱" 组合,每组用户占一行。例如:user,user@example.com。一方面是为了让博主本人不要付费,此外,也有类似的“会员”效果 付费墙颜色主题:选择一个适合您网站风格的颜色主题。目前有橙色 (默认) 、天蓝色、浅蓝色、淡灰色、浅紫色、白色,如果不能满足你的需求,可以自定义网站的css或者修改PayWall.php里的代码。 PayIT的插件配置图片 插件使用 使用 <payit></payit> 标签包裹需要付费的内容: 这是免费内容,所有人都可以看到。 <payit price="4.99"> 这里是付费内容,需要支付4.99元才能查看。 可以包含任何HTML内容,如图片、链接、表格等。 </payit> 这里又是免费内容。如果使用默认价格: <payit> 这里使用插件设置中的默认价格。 </payit>需要注意价格的正确格式 <!-- 正确格式 --> <payit price="5.00">内容</payit> <payit price="10.50">内容</payit> <!-- 错误格式 --> <payit price="5">内容</payit> <!-- 缺少小数点 --> <payit price="五元">内容</payit> <!-- 非数字 --> <payit price="-1">内容</payit> <!-- 负数 -->一些技巧 (1)在付费内容前添加提示信息: <p><strong>以下内容需要付费阅读:</strong></p> <ul> <li>详细的实现步骤</li> <li>完整的代码示例</li> <li>常见问题解答</li> </ul> <payit price="8.00"> <!-- 付费内容 --> </payit>(2)给出部分免费内容作为预览: <h3>核心算法实现</h3> <p>本算法的核心思想是通过动态规划来解决...</p> <p>基本步骤包括:</p> <ol> <li>初始化数据结构</li> <li>遍历输入数据</li> <li>应用状态转移方程</li> </ol> <payit price="12.00"> <h4>详细实现代码</h4> <pre><code> function algorithm(input) { // 完整的算法实现 ... } </code></pre> <h4>复杂度分析</h4> <p>时间复杂度:O(n²)</p> <p>空间复杂度:O(n)</p> <h4>优化建议</h4> <p>针对大数据集的优化方案...</p> </payit>总的来说,一方面是为了SEO,一方面是为了读者的好感度: 确保免费部分包含足够的有价值内容 付费内容的标题和描述要清晰 关于定价多少合适,我个人的一些参考: 入门级:1-5元 进阶级:5-30元 专家级:30-100元 -
使用 Python NumPy 原生代码实现点云的体素下采样 在 3D 数据处理领域,点云是一种重要的空间信息表示形式,用于捕捉物体和环境的几何形状。然而,点云数据通常非常密集,包含数百万甚至更多的点,这对存储、处理和可视化都带来了挑战。为了解决这个问题,我们需要对点云进行下采样,以减少点的数量,同时尽量保留数据的整体结构。体素下采样(Voxel Downsampling)是一种高效的下采样方法,它通过将点云划分到三维网格(体素)中并对每个体素内的点进行平均来实现这一目标。 什么是体素下采样? 此部分内容需要付费后才能阅读 您当前未登录,游客身份购买的内容仅在当前浏览器生效 (7天)。 清除Cookie或更换设备后需重新购买。 为获得永久阅读权限, 建议注册账户 后购买。 支付 4.99 元阅读 请使用支付宝扫描二维码支付 金额:4.99 元 二维码2小时内有效,支付成功后页面将自动刷新 if(typeof payit_generate_qr === "undefined") { function payit_generate_qr(id, url, cid, price) { var container = document.getElementById(id); var qrContainer = container.querySelector(".payit-qrcode-container"); var qrCodeDiv = container.querySelector(".payit-qrcode"); var btn = container.querySelector(".payit-button"); btn.disabled = true; btn.innerText = "二维码生成中..."; var xhr = new XMLHttpRequest(); xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { try { var res = JSON.parse(xhr.responseText); if(res.code === 0 && res.qr_code) { qrContainer.style.display = "block"; container.querySelector(".payit-mask").style.display = "none"; new QRCode(qrCodeDiv, { text: res.qr_code, width: 150, height: 150 }); payit_check_status(id, "https://www.hubtools.cn/payit/check", res.out_trade_no); } else { alert("二维码生成失败: " + (res.msg || "未知错误")); btn.disabled = false; btn.innerText = "支付 " + price + " 元阅读"; } } catch (e) { alert("从服务器获取数据时出错。"); btn.disabled = false; btn.innerText = "支付 " + price + " 元阅读"; } } else { alert("请求服务器失败。"); btn.disabled = false; btn.innerText = "支付 " + price + " 元阅读"; } } }; xhr.send("cid=" + cid + "&price=" + price); } } if(typeof set_payit_cookie === "undefined") { function set_payit_cookie(name, value, days) { var expires = ""; if (days) { var date = new Date(); date.setTime(date.getTime() + (days*24*60*60*1000)); expires = "; expires=" + date.toUTCString(); } document.cookie = name + "=" + (value || "") + expires + "; path=/"; } } if(typeof payit_check_status === "undefined") { function payit_check_status(id, url, order_id) { var interval = setInterval(function() { var xhr = new XMLHttpRequest(); xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { try { var res = JSON.parse(xhr.responseText); if (res.code === 0) { clearInterval(interval); // 如果是游客,前端设置cookie if (res.token) { var cookieName = "payit_cid_" + res.cid; set_payit_cookie(cookieName, res.token, res.expire); } document.getElementById(id).innerHTML = "支付成功,正在刷新页面..."; setTimeout(function() { location.reload(); }, 200); } } catch (e) { /* 支付未成功,不处理 */ } } }; xhr.send("out_trade_no=" + order_id); }, 3000); } } .payit-wrapper { border: 2px dashed #e67e22; background-color: #fdf5ee; padding: 20px; text-align: center; margin: 20px 0; border-radius: 8px; } .payit-wrapper .payit-guest-notice { font-size: 13px; color: #555; background-color: rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.05); padding: 10px; margin: 15px auto; border-radius: 5px; max-width: 95%; line-height: 1.6; } .payit-wrapper .payit-guest-notice p { margin: 5px 0; } .payit-wrapper .payit-guest-notice a { color: #e67e22; font-weight: bold; text-decoration: none; } .payit-wrapper .payit-guest-notice a:hover { text-decoration: underline; } .payit-mask .payit-notice { font-size: 18px; color: #e67e22; font-weight: bold; margin-bottom: 15px; display: flex; align-items: center; justify-content: center; } .payit-mask .payit-button { background-color: #e67e22; color: #ffffff; border: none; padding: 12px 25px; font-size: 16px; cursor: pointer; border-radius: 5px; transition: opacity 0.2s; } .payit-mask .payit-button:hover { opacity: 0.9; } .payit-mask .payit-button:disabled { opacity: 0.6; cursor: not-allowed; } .payit-qrcode-container { color: #333; } .payit-qrcode-container p { margin: 10px 0; } .payit-qrcode { width: 150px; height: 150px; margin: 10px auto; padding: 5px; background: white; border: 1px solid #ddd; border-radius: 3px;} 代码实现 以下是一个完整的 Python 脚本,使用 NumPy 实现点云的体素下采样。代码简洁高效,适用于大多数基本的点云处理需求。 import numpy as np from collections import defaultdict def read_point_cloud(file_path): """读取点云文件,假设每行是一个点的xyz坐标,可能包含其他属性""" points = [] with open(file_path, 'r') as f: for line in f: parts = line.strip().split() if len(parts) >= 3: # 至少包含xyz三个坐标 x, y, z = map(float, parts[:3]) points.append([x, y, z]) return np.array(points) def voxel_downsample(points, voxel_size): """体素下采样""" # 创建体素网格 voxel_grid = defaultdict(list) # 计算每个点所在的体素 for point in points: voxel_coord = tuple((point // voxel_size).astype(int)) voxel_grid[voxel_coord].append(point) # 对每个体素内的点取平均 downsampled_points = [] for voxel_coord, voxel_points in voxel_grid.items(): if voxel_points: avg_point = np.mean(voxel_points, axis=0) downsampled_points.append(avg_point) return np.array(downsampled_points) def write_point_cloud(file_path, points): """将点云写入文件""" with open(file_path, 'w') as f: for point in points: f.write(f"{point[0]} {point[1]} {point[2]}\n") def main(): # 配置参数 input_file = "test1_pointcloud - Cloud.subsampled.txt" # 输入点云文件 output_file = "downsampled_test1_pointcloud - Cloud.subsampled.txt" # 输出点云文件 voxel_size = 1.2 # 体素大小,根据你的点云尺度调整 # 读取点云 points = read_point_cloud(input_file) print(f"原始点云数量: {len(points)}") # 体素下采样 downsampled_points = voxel_downsample(points, voxel_size) print(f"下采样后点云数量: {len(downsampled_points)}") # 保存下采样后的点云 write_point_cloud(output_file, downsampled_points) print(f"下采样后的点云已保存到: {output_file}") if __name__ == "__main__": main()体素大小的选择 体素大小(voxel_size)是下采样的关键参数,直接影响结果: 较大的体素大小:会导致更激进的下采样,点数减少更多,但可能会丢失细节。 较小的体素大小:保留更多细节,但下采样后的点云仍然较大。 选择合适的体素大小需要根据点云的尺度以及应用需求。例如,对于建筑扫描,1 厘米的体素大小可能合适;而对于室外大场景,1 米的体素大小可能更适合。 效果展示 左边是原始的点云,右边是体素下采样之后的点云。 下采样前后的对比图片 -
Python使用Pillow批量将tif/tiff图片转换为jpg/png 在日常图像处理工作中,TIFF格式因其高质量和支持多页等特性被广泛使用,但也存在文件体积大、兼容性差等问题。本文将介绍如何利用Python的Pillow库批量将TIFF图像转换为更通用的JPG或PNG格式,并提供带图形界面的转换工具。 为什么需要转换TIFF格式? 因为实在太大了,一张照片就占了十几MB。我一共有好几千张。 TIFF格式图片 此外,我还想变成灰度图。对于这样批量的任务,使用那些网页版本的转换器应该是不行的,不得不使用一个批量的脚本。 准备工作 其实主要使用了两个核心库,一个是pillow,一个是tkinter,tkinter是python的内置库。安装pillow即可: pip install Pillow -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple完整转换代码 import os from PIL import Image import tkinter as tk from tkinter import filedialog, messagebox def convert_tiff_to_image(input_folder, output_folder, output_format, convert_to_gray): """批量转换TIFF文件为指定格式,可选择灰度转换""" tiff_extensions = ('.tif', '.tiff') # 收集所有TIFF文件 tiff_files = [ f for f in os.listdir(input_folder) if os.path.splitext(f)[1].lower() in tiff_extensions ] if not tiff_files: messagebox.showwarning("警告", f"在目录中未找到TIFF文件:\n{input_folder}") return False # 确保输出目录存在 os.makedirs(output_folder, exist_ok=True) converted_count = 0 for filename in tiff_files: input_path = os.path.join(input_folder, filename) output_name = os.path.splitext(filename)[0] + f'.{output_format}' output_path = os.path.join(output_folder, output_name) try: with Image.open(input_path) as img: # 转换为灰度(如果需要) if convert_to_gray: img = img.convert('L') # 保存为指定格式 img.save(output_path) converted_count += 1 except Exception as e: print(f"转换失败: {filename} - {str(e)}") return converted_count def main(): """主函数:获取用户输入并执行转换""" root = tk.Tk() root.withdraw() # 设置输入文件夹 input_folder = filedialog.askdirectory(title="选择TIFF文件所在文件夹") if not input_folder: return # 设置输出文件夹 output_folder = filedialog.askdirectory(title="选择输出文件夹") if not output_folder: return # 选择输出格式 output_format = tk.simpledialog.askstring( "输出格式", "请输入输出格式 (jpg 或 png):", initialvalue="png" ) if not output_format or output_format.lower() not in ('jpg', 'png'): messagebox.showerror("错误", "输出格式必须是 'jpg' 或 'png'") return output_format = output_format.lower() # 选择是否转为灰度 convert_to_gray = messagebox.askyesno("灰度转换", "是否转换为灰度图像? (是/否)") # 执行转换 converted_count = convert_tiff_to_image( input_folder, output_folder, output_format, convert_to_gray ) if converted_count > 0: messagebox.showinfo( "完成", f"成功转换 {converted_count} 个文件!\n输出目录: {output_folder}" ) else: messagebox.showwarning("无文件转换", "没有文件被转换") if __name__ == "__main__": main()GUI交互流程 第一步:选择包含TIFF的源文件夹 第二步:选择输出目录 第三步:选择输出格式(JPG/PNG) 第四步:选择是否转为灰度图 GUI交互流程图片 最后就能再输出文件夹里获得自己想要的新格式图片了。 -
从PointNet到PointNet++,小白也能看懂的核心思想 欢迎来到小陶的技术博客。如果你和我一样,是一名刚刚踏入三维点云处理世界的新手,你一定听说过 PointNet 和 PointNet++ 这两个大名鼎鼎的模型。它们就像是3D视觉领域的“开山鼻祖”,理解了它们,学习之路会顺畅很多。 今天,我也是根据自己看的一些别的博客资料以及B站视频,梳理这两个模型的“来龙去脉”和核心思想。 为什么要有点云模型?传统方法不行吗? 深入了解PointNet之前,先问一个问题:我们熟悉的二维图片处理方法(比如CNN,卷积神经网络)为什么不能直接用在三维点云上? 想象一下,一张图片就像一个整齐的棋盘,每个像素都有自己固定的位置。CNN的“卷积核”就像一个小窗口,在棋盘上规律地滑来滑去,提取特征。但点云完全不同,它就像一把随意撒在空中的沙子。它有两大特性,让CNN直接“水土不服”: 无序性 (Unordered): 一堆点,你先看A点再看B点,和我先看B点再看A点,描述的应该是同一个物体。点的顺序不应该影响最终结果。但对于传统神经网络,输入顺序变了,结果可能就天差地别。 空间结构性 (Sparsity & Irregularity): 点云不像像素那样均匀分布,它可能在某些地方密集(比如物体的棱角),在某些地方稀疏(比如一个平面)。你没法像CNN那样用一个固定大小的窗口去“滑动”。 为了解决这两个核心痛点,PointNet应运而生。 PointNet:简单粗暴的“大力出奇迹” PointNet是第一个直接处理原始点云的深度学习模型,它的想法堪称“天才般的简单”。 核心思想:如何解决“无序性”? PointNet的作者想:我需要找到一种方法,无论点的顺序怎么打乱,我最后得到的特征都是一样的。这种特性在数学上叫做“对称函数” (Symmetric Function)。 常见的对称函数有哪些? 求和 (Summation): a+b+c 和 c+b+a 的结果是一样的。 求平均 (Average): (a+b+c)/3 和 (c+b+a)/3 的结果是一样的。 求最大值 (Max-Pooling): max(a, b, c) 和 max(c, b, a) 的结果是一样的。 PointNet最终选择了 Max-Pooling(最大池化) 作为它的核心对称函数。 PointNet工作流程(三步走): 独立特征提取: 输入是一堆点的坐标(一个大小为 N x 3 的矩阵,N是点的数量,3是XYZ坐标)。 PointNet对 每一个点 单独进行特征学习,把它从3维映射到更高维度的空间(比如1024维)。你可以把它想象成给每个点“画像”,让它的信息更丰富。这一步是通过几个共享参数的多层感知机(MLP)完成的。 全局特征聚合(关键一步): 现在我们有N个1024维的特征向量了。 PointNet在这些特征向量的 每一个维度上 做一次Max-Pooling。也就是说,在第一个维度上,从N个点中选出最大值;在第二个维度上,也选出最大值……以此类推。 做完之后,N个点的特征就被“压”成了一个1024维的 全局特征向量。这个向量代表了整个点云的“样子”。因为Max-Pooling是无序的,所以无论输入点的顺序如何,这个全局特征都是不变的! 输出结果: 最后,用这个全局特征向量去做具体的任务,比如接一个分类器判断这个点云是什么物体(桌子?椅子?),或者做一个分割器判断每个点属于物体的哪个部分。 # PointNet用于分类任务的伪代码 function pointnet_classification(point_cloud): # point_cloud 是一组N个点 {p1, p2, ..., pN} # 1. 对每个点独立应用MLP,提取高维特征 point_features = [] for point in point_cloud: # 比如把 (x,y,z) 变成一个1024维的特征 feature = MLP(point) point_features.append(feature) # 2. 使用对称函数(Max-Pooling)聚合所有点的特征 # 这是保证顺序不变性的核心! # 从 [N, 1024] 的特征矩阵,池化成 [1, 1024] 的全局特征 global_feature = max_pool(point_features) # 3. 使用最后的MLP进行分类 prediction_scores = MLP(global_feature) return prediction_scoresPointNet虽然开创了历史,但它的方法太“粗暴”了。它通过Max-Pooling把所有点的信息都揉成一团,形成一个全局特征。这导致它 无法感知局部细节。 打个比方,PointNet能认出这是一辆车,但它很难分清车轮和车灯的区别,因为它把所有点的特征“一视同仁”地混合了,丢失了点与点之间的邻里关系和局部几何结构。 PointNet++:从“全局”到“局部”的精细化升级 为了解决PointNet丢失局部结构的问题,PointNet++被提了出来。它的核心思想非常像我们熟悉的CNN,那就是——层次化特征提取 (Hierarchical Feature Learning)。 核心思想:先局部,再整体 如果说PointNet是“一口吃成个胖子”,那PointNet++就是“细嚼慢咽”。它不再直接对所有点进行粗暴的全局池化,而是: 分片 (Partition): 在点云中选择几个“中心点”。 分组 (Grouping): 以每个中心点为核心,在周围画一个“圈”(比如一个球形半径内),把邻近的点组织成一个小局部区域。 小PointNet提取局部特征: 对每一个小局部区域,使用一个迷你的PointNet来提取这个区域的局部特征。 迭代升级: 不断重复以上过程。上一层提取的局部特征,会成为下一层分组和提取的输入。这样一来,网络就能从非常小的局部细节(比如桌子腿的棱角),逐渐学习到更大范围的特征(整个桌子腿),最后再到全局特征(整张桌子)。 这个核心组件,PointNet++称之为 集合抽象层 (Set Abstraction Layer)。 一个Set Abstraction Layer的伪代码: # PointNet++ 中一个 Set Abstraction 层的伪代码 function set_abstraction_layer(points, features): # points是输入的点集,features是这些点对应的特征 # 1. 采样层:用最远点采样(FPS)选出一些中心点 centroids = farthest_point_sampling(points) # 2. 分组层:为每个中心点,找到它的邻居点 groups = [] for c in centroids: # 比如在半径r内找邻居 (Ball Query) neighbors = find_neighbors_in_radius(c, points, radius) groups.append(neighbors) # 3. PointNet层:对每个组用一个“迷你PointNet”来提取局部特征 new_features = [] for group in groups: # 注意:在送入迷你PointNet前,会先将邻居点坐标归一化到局部坐标系 local_feature = mini_pointnet_module(group) new_features.append(local_feature) # 输出:新的点(中心点)和它们对应的更高级的特征 return centroids, new_features通过堆叠多个这样的set_abstraction_layer,PointNet++就能像CNN一样,一层层地扩大感受野,从点到线,从线到面,最终理解整个三维物体的复杂结构。 如果还没理解,我们来做一个最后的比喻: PointNet: 就像全国海选。所有选手(点)都来到一个大舞台上,评委(Max-Pooling)只看一眼,选出最亮眼的那一个(最大特征值)来代表所有人。这个方法简单高效,能快速得到一个总体印象,但忽略了选手们在各自地区(局部)的特色。 PointNet++: 就像分级选举。先在每个村里选出村代表(第一层局部特征),然后村代表们再开会选出镇代表(第二层更大范围的特征),镇代表再选出县代表……最后选出国家主席(最终的全局特征)。这个过程更复杂,但能充分保留从基层到高层的各级信息,对情况的把握更精细。 特性PointNetPointNet++核心思想对称函数(Max-Pooling)处理无序性层次化特征提取结构感知仅有全局特征,无局部结构信息有局部结构信息,从局部到全局处理方式一步到位,简单粗暴逐层抽象,精细复杂适用场景简单的分类任务,对细节要求不高的场景分割、复杂场景理解等需要局部信息的任务比喻全国海选分级选举写在最后 PointNet和PointNet++是3D点云深度学习的基石。虽然现在已经有了更多更复杂的模型,但它们的思想——如何解决无序性、如何学习局部和全局特征——依然在影响着后续的研究。 对于初学者来说,不必急于深究代码的每一个细节,最重要的是理解其背后的思想。