十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenCV 3.4.2 + Qt实战:图像处理、棋盘格标定、FFT与打包全攻略

OpenCV 3.4.2 + Qt实战:图像处理、棋盘格标定、FFT与打包全攻略 简介这是面向Qt开发者的OpenCV 3.4.2集成资源解决在Qt图形界面中调用OpenCV进行图像视频处理时的环境配置与编译难题适合具备C基础、希望快速搭建跨平台视觉应用的开发者。压缩包共543个文件约106.98MB核心内容以hpp/h头文件、dll动态库和a静态库为主另含xml配置文件、exe辅助工具、cmake模块和少量说明文档可直接用于Qt项目的链接与调用。目前已吸引526人学习下载。通过这套资源开发者可获得编译好的OpenCV库与Qt集成配置省去从源码编译的繁琐流程包内示例代码和配置文件展示了QImage与Mat的相互转换以及如何在Qt信号槽机制中调用OpenCV函数便于快速实现图像显示、滤波、边缘检测、人脸识别等常见功能适合在Qt Creator或VSQt环境下开展视觉项目开发。 一直以来OpenCV 和 Qt 这对组合在工业视觉、图像处理工具链里都是老面孔了。我这次要分享的项目标题是 opencv-3.4.2-Qt简单说就是把 OpenCV 3.4.2 集成进 Qt 界面环境做图像处理、轮廓绘制、棋盘格标定、频域波形显示等一整套桌面工具。这个版本组合不算新但胜在稳定很多老项目至今还跑在这套环境上值得仔细聊一聊。如果你正准备在 Windows 上用 Qt 写一个带界面的 OpenCV 应用或者你手里有个旧项目需要复现这篇内容应该能帮你省下不少弯路。下面我会从版本选型、Mat 与 QImage 的转换、轮廓检测实战、标定落地、FFT 波形显示到最后的打包排错完整走一遍。1. 为什么还在用 opencv-3.4.2版本选型的现实考量很多人上来就追新OpenCV 4.x、5.x 都出来了还在用 3.4.2 是不是太老其实 3.4.2 是 OpenCV 3.x 分支里一个非常特殊的版本它处于 3.4 系列的早期稳定期API 还没有被 4.x 大改很多工业项目的 SDK 和算法依赖库都是在 3.4.2 上编译验证过的。如果你要对接老的摄像头驱动、旧的 CUDA 版本、某些私有算法模块3.4.2 往往比新版本更省心。从 Qt 集成的角度讲OpenCV 只管算法和图像数据结构Qt 只管界面和事件循环两者通过 Mat 和 QImage 的数据交换打通。3.4.2 的 Mat 内存布局和 4.x 基本一致所以在 Qt 上的显示逻辑并没有本质区别没有必要为了“新”而升级整个工具链。1.1 Qt 与 OpenCV 在架构上的配合方式Qt 负责窗口、按钮、事件、绘图控件OpenCV 负责图像读取、滤波、特征提取、标定、绘制。一个典型的界面程序流程是点击按钮加载图片OpenCV 读入并处理然后把结果显示在 QLabel 或自定义绘图控件上。整个过程不涉及多线程时代码可以写得很直白。但要注意一个问题OpenCV 的 highgui 模块自带 imshow 窗口这在纯控制台程序里很好用一旦进入 Qt 环境我强烈建议不要混用 imshow 和 Qt 窗口否则两个事件循环会互相干扰表现为窗口卡死、刷新异常。正确的做法是只用 Qt 做显示OpenCV 只负责处理数据。1.2 工具链准备与版本匹配我用的是 Qt 5.12 或 5.15编译器是 MSVC 2017 或 MinGW 64 位。配 OpenCV 3.4.2 时最稳的是直接下载官方预编译的 Windows 包里面有现成的 dll 和 lib。如果你是 MinGW 环境官方包是用 MSVC 编的不能直接用需要自己用 CMake 从源码编译一次耗时大概半小时到一小时。编译参数里建议开启 WITH_QT这样 OpenCV 的 highgui 在编译时就知道要和 Qt 共存不会在运行时强行创建自己的窗口。不过即便不开只要你不用 imshow影响也不大。2. Mat 转 QImageQt 界面显示 OpenCV 图像的桥梁这是所有 Qt OpenCV 项目的第一道坎。Mat 里存的是 BGR 顺序的三通道数据QImage 默认是 RGB 顺序如果直接转显示出来红蓝会互换人脸看起来像外星人。解决办法有两种一是用 cvtColor 把 Mat 从 BGR 转成 RGB二是利用 QImage 的 Format_RGB888 配合数据拷贝。我实践中更推荐先转颜色格式因为这样保留了 Mat 的连续性后续如果再要返回 OpenCV 处理不用再调整 QImage 侧的像素顺序。2.1 核心转换代码QImage matToQImage(const cv::Mat mat) { if (mat.type() CV_8UC3) { cv::Mat rgb; cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); return QImage(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888).copy(); } else if (mat.type() CV_8UC1) { return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8).copy(); } return QImage(); }这里必须调用.copy()因为 QImage 如果直接共享 Mat 的 data 指针Mat 一旦被释放或者重新分配内存QImage 就成了野指针界面会出现花屏甚至崩溃。拷贝一份数据虽然多了一次内存操作但对于显示功能来说完全可接受。2.2 常见坑像素格式与内存管理我遇到过一个典型的坑图像显示出来是反的或者颜色发绿发紫。原因通常有两个一是 Mat 是 CV_8UC4 但被当成 CV_8UC3 处理二是 QImage 用了 Format_RGB32 却没有正确处理 alpha 通道。统一做法是先把 Mat 转为 CV_8UC3再走 matToQImage。另一个坑是 QLabel 上显示大图时没有做缩放。一张 4000x3000 的图直接塞给 QLabel界面会卡顿而且缩放后会模糊。建议用 Qt 的平滑缩放模式QPixmap pix QPixmap::fromImage(img); pix pix.scaled(ui-label-size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); ui-label-setPixmap(pix);这样既能保证显示流畅又不失真。3. 从图像处理到界面交互轮廓检测与绘制的实战组合热搜词里频繁出现 findContours 和 fillPoly说明很多人做的项目都涉及轮廓提取和掩膜生成。这个需求在工业上很常见从背景中把目标物体抠出来然后生成二值掩膜再做后续测量或识别。轮廓检测流程一般分四步灰度化、二值化、形态学去噪、findContours。这四步缺一不可尤其是二值化的阈值选择直接影响轮廓质量。3.1 findContours 的预处理细节cv::Mat gray, binary; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::threshold(gray, binary, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU); cv::morphologyEx(binary, binary, cv::MORPH_CLOSE, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5))); std::vectorstd::vectorcv::Point contours; std::vectorcv::Vec4i hierarchy; cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);这里我优先用 RETR_EXTERNAL 只取外层轮廓避免内部孔洞干扰。如果目标本身有空洞比如齿轮需要根据具体场景改成 RETR_CCOMP 或 RETR_TREE再用 hierarchy 判断父子关系。形态学闭运算的作用是填补细小断裂让轮廓封闭性更好。结构元素大小不建议盲目设大否则会让两个相近目标黏连。我一般先从 3x3 试起不行再调大。3.2 fillPoly 绘制掩膜与标注轮廓提取后经常需要在一张空白图上绘制多边形区域。这里要注意 OpenCV 3.4.2 的 fillPoly 函数签名的变化旧代码里可以直接传轮廓 vector但在 3.4.2 里必须传入InputArrayOfArrays也就是std::vectorstd::vectorcv::Point的数组形式。cv::Mat mask cv::Mat::zeros(binary.size(), CV_8UC1); std::vectorcv::Point poly contours[0]; // 假设只取最大轮廓 std::vectorstd::vectorcv::Point polyList; polyList.push_back(poly); cv::fillPoly(mask, polyList, cv::Scalar(255));用掩膜可以做很多事统计区域内的像素均值、把区域外的背景置黑、计算轮廓面积和周长。之后如果想在界面里叠加显示再把 mask 转成彩色图与原始图像做 addWeighted 融合轮廓就变成半透明高亮效果非常直观。4. 棋盘格标定相机标定在 Qt 工具中的落地热搜词里“opencv棋盘格标定的c代码”出现频率很高说明标定依然是很多机器视觉项目的刚需。OpenCV 3.4.2 自带的 findChessboardCorners 和 calibrateCamera 函数很稳定配合 Qt 界面可以做成一个可视化标定工具比纯命令行体验好得多。标定的核心逻辑并不复杂采集多张不同姿态的棋盘格图片提取角点代入标定方程得到内参矩阵和畸变系数。但实际落地时角点提取成功率是最让人头疼的。4.1 角点提取与参数调整cv::Size patternSize(9, 6); // 内角点数 std::vectorcv::Point2f corners; bool found cv::findChessboardCorners(gray, patternSize, corners, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE);如果 found 为 false可以先用 cornerSubPix 做亚像素精细化再重试。还有一个很实用的技巧不要直接用手机拍的图最好把棋盘格打印出来贴在平整平面上反光越少越好。采集图片时要保证棋盘格在画面中占据不同位置角度要多样否则标定出的畸变系数会过拟合。4.2 从界面采集到标定结果我在 Qt 工具里的实现思路是左侧放实时图像显示三个按钮分别对应“采集当前帧”“开始标定”“保存参数”。采集按钮会把当前图像存入一个 vector 缓存同时显示已采集数量当数量达到建议的 15 到 20 张就可以点击标定。cv::Mat cameraMatrix cv::Mat::eye(3, 3, CV_64F); cv::Mat distCoeffs; std::vectorcv::Mat rvecs, tvecs; cv::calibrateCamera(objectPoints, imagePoints, gray.size(), cameraMatrix, distCoeffs, rvecs, tvecs);标定完成后把相机内参写入 XML 或 JSON 文件以后加载即可。这里我习惯用 cv::FileStorage 保存它天然支持 YAML/XML 格式代码简单Qt 这边只需要在按钮事件里调用即可。5. 时域到频域QCustomPlot 与 QCustomPlot 显示频率波形“qt时域图转换为频域图”“qcustomplot kissfft时域到频域波形”这两个热搜词指向同一个需求把采集到的时域信号转换成频域波形并绘制到界面上。这个场景通常用在振动分析、音频处理或传感器数据分析。Qt 本身没有内置的波形图控件QCustomPlot 是目前最常用的第三方绘图库。它支持曲线、柱状图、颜色图交互也很流畅在 Qt 5.12 MSVC 环境下直接加入项目即可。5.1 从时域到频域的数据链路时域数据一般是 QVector 或 std::vector 采样率已知。要转成频域常见做法是用 FFT 库。KissFFT 是一个轻量级 C 语言实现适合嵌入到 Qt 项目里接口简单不需要像 FFTW 那样处理复杂的内存配置。#include kiss_fft.h // 假设输入长度为 N std::vectorkiss_fft_cpx fin(N), fout(N); for (int i 0; i N; i) { fin[i].r timeData[i]; fin[i].i 0.0; } kiss_fft_cfg cfg kiss_fft_alloc(N, 0, nullptr, nullptr); kiss_fft(cfg, fin.data(), fout.data()); free(cfg);这里要注意频率分辨率的计算频率分辨率 采样率 / N。如果采样率是 1000 HzN 是 512那每个频点间隔约 1.95 Hz。幅度值要取模后乘以 2/N 归一化才能得到真实的单边频谱幅度。5.2 QCustomPlot 绘制频域曲线QCustomPlot 画曲线非常直接核心是维护一个 QCPGraph 对象ui-customPlot-addGraph(); ui-customPlot-graph(0)-setData(freqAxis, ampAxis); ui-customPlot-xAxis-setLabel(freq(Hz)); ui-customPlot-yAxis-setLabel(Amplitude); ui-customPlot-rescaleAxes(); ui-customPlot-replot();实际使用中要留意实时更新时是否卡顿。如果你每帧都调用 replot数据量大了以后界面响应会变差。建议开启 QCustomPlot 的 setNotAntialiasedElements或者用 setNoAntialiasingOnDrag 优化性能必要时把刷新频率限制在 30 FPS 左右。实测下来1024 点的 FFT 数据绘制3000 次刷新内都不会有明显卡顿。6. 部署与排错正式交付前的几个坑开发环境里跑得好换一台机器就崩这是 Qt OpenCV 项目最常见的交付事故。热搜词里“no Qt platform plugin could be initialized”“qt打包应用程序 windeploy”正好命中这个痛点。我把自己踩过的坑和解决办法整理一下。6.1 windeployqt 打包的正确姿势windeployqt 是 Qt 官方提供的部署工具会自动把 Qt 相关 dll、插件、样式表拷贝到目标目录。但 OpenCV 的 dll 它不会帮你拷需要手动把 opencv_world342.dll或 opencv_core342.dll、opencv_imgproc342.dll 等复制到 exe 同目录。打包步骤顺序建议如下用 Release 模式编译拿到 exe。打开 Qt 命令行工具切到 exe 所在目录。执行windeployqt your_app.exe。再手动复制 OpenCV 的 dll 到同一目录。把整个目录打包成 zip交付测试。如果你是 MinGW 版本还需要把 libgcc_s_seh-1.dll、libstdc-6.dll、libwinpthread-1.dll 这三个运行时库一并带上否则目标机器会报“找不到 libgcc”之类的错误。6.2 运行时崩溃与平台插件错误“no Qt platform plugin could be initialized”这个错误十有八九是 platforms 文件夹缺失或路径不对。windeployqt 正常情况下会在 exe 目录下生成 platforms 文件夹里面放着 qwindows.dll。如果这个 dll 版本不匹配或者被杀毒软件误删就会报这个错。我的排查方法是先把整个运行目录放到系统 PATH 中再用 Dependency Walker 或 Process Explorer 检查 dll 是否加载完整。注册表环境混乱的机器上建议把 Qt 的 bin 目录也一并放到 PATH省得出奇奇怪怪的问题。还有一个容易被忽略的点OpenCV 的 dll 和 Qt 的 dll 如果都依赖不同版本的运行时库可能会在运行时发生冲突。这时可以把所有依赖库统一放在一个目录避免多版本混杂或者干脆用静态编译虽然体积大一些但交付最干净。7. 关于人脸识别与 RTMP 等其他方向的拓展热搜词里还有人脸识别和“opencv 打开rtmp失败”。这两个方向我也简单补充一下。人脸识别在 OpenCV 3.4.2 里主要是使用 haar 级联分类器或 LBP 分类器加载一个 xml 模型文件然后 detectMultiScale。和 Qt 结合时摄像头帧用 QTimer 定时读取再转成 Mat 做检测耗时控制在几十毫秒以内。关键参数是 scaleFactor 和 minNeighbors调得不好会漏检或误检建议在界面上做成可调滑块实时观察效果。RTMP 流打不开的问题多半和 ffmpeg 支持有关。OpenCV 3.4.2 的 VideoCapture 对 RTMP 的支持依赖 FFMPEG官方预编译包里可能没打开该协议或者目标地址需要额外参数。我遇到这种情况一般先确认用 VLC 能不能打开该 RTMP 地址排除网络原因后再检查 OpenCV 编译时是否带 FFMPEG。实在不行建议用 FFmpeg 库单独取流再把帧喂给 OpenCV 处理这也是工业项目的常见做法。关于组合拳的最后一个建议是如果你同时用到串口编程和 Qt 界面建议在 QThread 中处理串口数据避免阻塞主线程。串口数据通过信号槽发送时注意消息类型要用 qRegisterMetaType 注册否则会触发 Qt 的运行时警告收不到数据。OpenCV 3.4.2 和 Qt 这套组合说实话没有太多花哨的地方但它在实际工程里非常扎实。我最大的感受是很多坑其实都出在版本匹配和运行时环境上而不是算法本身。如果你正在搭这套环境建议刚开始就固定好编译器和依赖库版本不要混搭。另外代码里涉及 Mat 和 QImage 转换时务必做好深拷贝这是防止花屏和崩溃的最有效手段。最后再分享一个小技巧项目初期做一个简单的图像显示测试先把整条链路跑通再逐步增加算法模块比一口气写完再联调要省事得多。本文还有配套的精品资源点击获取
返回列表