QT5加OpenCv这个组合,我前前后后折腾了好几年。最近又用这套技术栈做了一个桌面端的图像及视频处理软件,功能不算复杂,但把一个完整的图像处理、视频预览、实时滤波、参数调节都串起来了。这篇文章把项目从设计到落地的完整过程理一遍,重点放在架构怎么搭、代码怎么写、坑怎么避,希望对正在做类似工具或者毕业设计的同学有点实际帮助。
这个项目适合几类人来看:一是刚接触Qt和OpenCv,想做一个综合性练手项目的新手;二是需要在桌面端快速实现图像算法Demo的开发者;三是做课程设计、毕业设计,需要一套完整可演示的视觉处理软件的同学。整个项目技术栈固定在QT5和OpenCv,不牵扯深度学习,纯传统图像处理路线,代码量适中,但工程完整性很高。
1. 整体设计思路与项目架构解析
1.1 为什么选择QT5加OpenCv这个组合
做图像处理软件,第一步是选型。我见过不少方案,有纯OpenCv配HighGUI窗口的,有直接用PyQt加OpenCv的,也有用C#加EmguCV的。但回到桌面级“软件”这个定位,QT5加OpenCv是最稳的组合,没有之一。
原因是这样:OpenCv负责算法侧,图像滤波、边缘检测、色彩空间转换、视频解码,这些基础能力它都内置了,不用自己造轮子。QT5负责界面侧,窗口管理、按钮布局、菜单栏、状态栏、文件对话框、图像显示控件,这些图形界面能力是它的强项。两者一个管“算”,一个管“看”,职责非常清晰。
对比一下纯OpenCv自带窗口的做法,它的HighGUI只能在调试阶段用用,界面粗糙,控件能力极弱,想加一个滑条调参数都得费半天劲。而且OpenCv的窗口函数在处理视频实时流的时候经常卡顿,更不用说做多窗口联动了。QT5的QWidget体系在控件布局、事件响应、信号槽通信这些方面非常成熟,做出来的东西才像个真正的软件。
另外一点是跨平台。QT5天然跨平台,Windows、Linux、macOS一套代码编译过去基本能跑,OpenCv本身就是跨平台库,两个一结合,项目可以从Windows开发机很方便地迁到Linux服务器或者嵌入式ARM板子上。我之前就把这套代码放到orangepi CM5上做过交叉编译,改改工具链就能编过,这也是这个组合很实在的一个优势。
1.2 软件功能拆解与模块划分
这个项目的功能定位是“简单但完整”,所以功能面覆盖了图像处理和视频处理两大块。图像处理部分包括图像打开、显示、灰度化、高斯模糊、Canny边缘检测、亮度对比度调节、图像保存。视频处理部分包括视频文件播放、摄像头实时预览、帧画面抓取保存、视频录制。
为了让代码不混乱,在动手写之前一定要先做模块划分。我的做法是把工程按职责分成三个逻辑层:
- 界面层:所有QWidget子类,负责控件的摆放、按钮点击事件、参数滑条的交互,这一层不直接调OpenCv算法。
- 算法层:封装图像和视频处理的核心操作,接收cv::Mat输入,返回处理后的cv::Mat,这一层不依赖任何Qt类型。
- 数据层:负责图像文件读写、视频流读取、相机设备管理,统一封装成独立的工具类。
这么分的好处是,当你后续想替换算法实现或者扩展新功能时,只需要在算法层增加新接口,界面层和数据层不需要大改。很多初学者习惯在按钮的槽函数里直接写OpenCv代码,功能少的时候没问题,一旦功能多起来,整个cpp文件变成几千行的“混沌代码”,查起问题来非常痛苦。
1.3 线程模型:不卡界面的关键
这是整个项目里最核心的架构决策。图像处理本身是CPU密集型操作,尤其是Canny边缘检测、大图高斯滤波,单帧处理可能需要几十毫秒甚至更久。如果这些操作放在UI主线程里执行,界面会直接冻结,窗口拖不动、按钮点不了,体验非常糟糕。而视频播放每秒钟要处理25到30帧,如果在主线程里逐帧解码加显示,程序基本就卡死了。
所以这个项目引入了工作线程。具体做法是:
- 主线程只管界面交互和状态显示。
- 图像处理任务通过QtConcurrent的run方法丢到线程池执行,处理完成后通过信号槽把结果显示到界面上。
- 视频播放和相机预览用独立的QThread,在线程里循环读取视频帧,处理完一帧发一个信号,主线程收到信号后刷新显示。
这也是为什么后面要单独强调信号槽传递时的类型注册问题。跨线程传cv::Mat如果没做好类型转换,轻则编译报错,重则运行时崩溃,这些细节我放在第三部分详细说。
2. 开发环境搭建与工程配置避坑指南
2.1 编译器选型与版本匹配
这个项目的第一步坑往往不是代码,而是环境。Windows平台上,QT5通常有两种编译器组合:MSVC和MinGW。选哪个直接影响OpenCv库的下载和配置。
我的建议是:常规开发优先用MSVC。因为官方编译的OpenCv库默认就是MSVC版本的,下载下来直接能用。MinGW是社区编译的,版本匹配偶尔会出幺蛾子,比如OpenCv官方包里的lib文件MinGW根本不认,得找第三方编译过的库,平白增加了折腾时间。
版本方面,我用的是QT5.12.9加OpenCv 4.5.5,这两个版本都比较稳定。OpenCv 4.x系列对C++11支持很友好,接口也统一,不需要区分旧版的cv和cv2命名空间问题。如果你是新手,照抄这个版本组合能少踩很多坑。
2.2 工程文件配置实战
配置Qt工程的pro文件是很多初学者卡住的地方。我直接贴一份实际可用的配置,里面的路径根据你自己的安装目录修改即可。
# 项目文件配置示例 QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = VisionTool TEMPLATE = app CONFIG += c++11 CONFIG -= app_bundle # ---------- OpenCv 配置 ---------- # 这是Windows下的路径,改成你的实际安装路径 OPENCV_MSVC_PATH = C:/opencv452 INCLUDEPATH += $${OPENCV_MSVC_PATH}/include LIBS += $${OPENCV_MSVC_PATH}/x64/vc15/lib/opencv_world455.lib # Debug模式下用带d的库 CONFIG(debug, debug|release) { LIBS += $${OPENCV_MSVC_PATH}/x64/vc15/lib/opencv_world455d.lib } # ---------- 源代码文件 ---------- SOURCES += \ main.cpp \ MainWindow.cpp \ ImageProcessor.cpp \ VideoThread.cpp HEADERS += \ MainWindow.h \ ImageProcessor.h \ VideoThread.h FORMS += \ MainWindow.ui这里有个非常重要的细节:OpenCv的lib文件分debug版和release版,debug版文件名带“d”后缀,比如opencv_world455d.lib,release版不带d。如果把debug版配进了release编译,或者反过来,链接阶段会报各种奇怪的错误。上面pro文件里用CONFIG(debug, debug|release)做了区分,这个写法是Qt的标准做法,能自动适配当前编译模式。
还有一个很隐蔽的问题,就是MSVC编译器版本和OpenCv库的vc版本对应关系。OpenCv 4.5.5的官方包里有vc14和vc15两个目录,分别对应VS2015和VS2017以上版本。如果你用的是Qt 5.12自带的MSVC2017_64套件,那么vc15目录下的lib是正确的。Qt 5.15以上大概率是MSVC2019,对应vc16目录,需要额外下载对应的OpenCv版本。
2.3 环境配置的三大高频失败点
第一,运行时找不到OpenCv的dll。编译通过但一运行就弹窗报错,说缺少opencv_world455.dll。这是因为系统PATH环境变量里没加OpenCv的bin目录。解决方案是手动把OpenCv安装目录下的x64/vc15/bin路径加到系统环境变量里,或者更省事的方法:把需要的dll文件直接复制到编译输出的exe目录下。我推荐第二种,虽然笨但一劳永逸,避免换台电脑又在环境变量上折腾。
第二,Debug和Release混乱。很多人编译模式切到Release,发现一堆未解析的外部符号,多半是因为lib库配置只写了debug版本。尤其注意,OpenCv的release lib文件不要带“d”,带的必报错。这个细节上面pro文件里已经做了区分,照着抄就行。
第三,OpenCv头文件路径里有空格仍有兼容问题。比如把OpenCv解压到Program Files目录了,虽然理论上pro文件支持带空格的路径,但某些老版本工具链会在预处理阶段炸掉。我的建议是目录路径全程不要有空格和中文,统一用纯英文无空格路径,省得后面莫名其妙出问题。
3. 图像处理模块的完整实现
3.1 图像读取、显示与Mat/QImage相互转换
图像处理的第一步是把图片文件读进来并显示到界面上。OpenCv端用cv::imread读取,Qt端显示用的控件是QLabel,但QLabel不能直接显示cv::Mat,需要先把Mat转换成QImage。
这里有一个所有Qt加OpenCv开发者的必修课:颜色通道顺序转换。OpenCv默认使用BGR三通道顺序,而Qt的QImage默认是RGB顺序,直接转换会导致图像中红色和蓝色互换——蓝天变成橙色的。这个坑我见过无数人踩,包括当年的我。
核心转换代码如下,这段代码建议直接作为工具函数保存:
// cv::Mat 转 QImage,支持8位灰度、8位BGR、8位BGRA QImage MatToQImage(const cv::Mat& mat) { if (mat.type() == CV_8UC1) { // 单通道灰度图,直接拷贝 QImage image(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8); return image.copy(); } else if (mat.type() == CV_8UC3) { // 三通道BGR图,先转换通道顺序再生成QImage cv::Mat rgb; cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); QImage image(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888); return image.copy(); } else if (mat.type() == CV_8UC4) { // 四通道BGRA图 QImage image(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32); return image.copy(); } else { return QImage(); } }这里有几个细节要注意。第一,QImage构造函数里的bytesPerLine参数要传mat.step,也就是Mat每一行的字节数。这是因为OpenCv的Mat在内存中可能存在行对齐,直接默认值可能在图像宽度不是4的倍数时显示错位。第二,QImage对象和Mat共享同一块内存,如果Mat在函数结束后被释放,QImage就成了野指针,所以上面代码最后加了copy(),把数据完整拷贝到QImage自己管理的内存中。这一步多拷贝一次内存,换来的是安全。
反向转换,QImage转Mat也需要处理通道顺序问题:
cv::Mat QImageToMat(const QImage& image) { switch (image.format()) { case QImage::Format_Grayscale8: return cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), (int)image.bytesPerLine()).clone(); case QImage::Format_RGB888: { cv::Mat rgb(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), (int)image.bytesPerLine()); cv::Mat bgr; cv::cvtColor(rgb, bgr, cv::COLOR_RGB2BGR); return bgr; } case QImage::Format_ARGB32: case QImage::Format_RGB32: { cv::Mat bgra(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), (int)image.bytesPerLine()); cv::Mat bgr; cv::cvtColor(bgra, bgr, cv::COLOR_BGRA2BGR); return bgr; } default: // 统一转换成RGB888再处理 QImage converted = image.convertToFormat(QImage::Format_RGB888); return QImageToMat(converted); } }3.2 灰度化与直方图均衡化
图像处理算法层面,第一个要做的就是灰度化。OpenCv一行代码搞定:
cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);但这是个很好的教学节点。灰度化本质是把三个颜色通道的信息压缩成一个亮度值,传统的加权公式是Y = 0.299R + 0.587G + 0.114B。这个权重不是拍脑袋定的,而是根据人眼对不同颜色敏感度的实测数据来的。人眼对绿色最敏感,所以绿色权重最高;对蓝色最不敏感,权重最低。OpenCv里COLOR_BGR2GRAY用的就是这套标准权重。
直方图均衡化在图像对比度低、雾蒙蒙的情况下非常有用。它把图像灰度分布拉宽,让暗的更暗、亮的更亮,细节更清楚。实现也很简单:
cv::Mat equalized; cv::equalizeHist(gray, equalized);注意这个函数只接受单通道图像,所以必须先灰度化再均衡化。如果想对彩色图做均衡,可以转HSV色彩空间,只对V通道做均衡,然后合并回彩色图像。这种彩色图增强的方式保留了原始色彩信息,不会出现色偏问题。
3.3 高斯模糊与Canny边缘检测
高斯模糊是图像处理里用得最频繁的滤波操作。它的原理是:对每个像素点,取它周围一个邻域内的像素值,按高斯函数的权重做加权平均,得到的值作为该像素的新值。效果就是图像变“糊”,高频噪声被抑制。
OpenCv用法:
cv::Mat blurred; cv::GaussianBlur(src, blurred, cv::Size(5, 5), 0);核心参数是高斯核的大小,cv::Size(5, 5)表示5x5的窗口,核越大,图像越模糊,计算量也越大。第三参数sigmaX如果传0,OpenCv会根据核大小自动计算标准差。实际用的时候,核大小选奇数,这样有中心像素,卷积运算对齐方便。
Canny边缘检测是经典的三阶段算法:先用高斯滤波降噪,然后计算图像梯度幅值和方向,接着非极大值抑制精确定位边缘,最后双阈值检测和滞后连接把边缘连成完整的轮廓。
OpenCv用法:
cv::Mat edges; cv::Canny(blurred, edges, 50, 150);后面两个数字是双阈值,低阈值50,高阈值150。低于低阈值的像素不是边缘,高于高阈值的一定是边缘,介于两者之间但和高阈值边缘相连的也算边缘。这个参数对结果影响很大,所以我做了一个滑条控件,让用户实时调节这两个阈值,效果立竿见影。做图像处理工具的时候,参数可视化调节是非常有用的功能,比写死参数再重新编译调试高效得多。
3.4 图像保存与格式问题
图像保存用cv::imwrite,注意输出路径如果是中文,OpenCv在Windows下经常写不进去,这是老毛病。解决办法有两个:一是用cv::imencode把图像编码到内存缓冲,再用Qt的QFile写文件;二是干脆在保存时用QFileDialog选路径,Qt的对话框返回的路径是标准编码,一般没问题。
// 推荐的保存方式:先编码再写文件,避免中文路径问题 std::vector<int> params; params.push_back(cv::IMWRITE_JPEG_QUALITY); params.push_back(95); std::vector<uchar> buffer; cv::imencode(".jpg", img, buffer, params); QFile file(savePath); if (file.open(QIODevice::WriteOnly)) { file.write((const char*)buffer.data(), buffer.size()); file.close(); }这个方式还有个好处是可以动态控制压缩质量。保存PNG时设IMWRITE_PNG_COMPRESSION,取0到9,值越小压缩率越低、文件越大、速度越快。
4. 视频处理模块的完整实现
4.1 视频文件读取与逐帧播放
视频处理模块的核心是视频帧的读取和显示。OpenCv里读取视频文件的类是cv::VideoCapture,支持常见的MP4、AVI、MOV格式。最基础的用法是:
cv::VideoCapture cap; cap.open("video.mp4"); if (!cap.isOpened()) { QMessageBox::warning(this, "错误", "无法打开视频文件"); return; } cv::Mat frame; cap.read(frame); // 读一帧视频处理的难点不在读取,而在播放流畅性。如果直接在按钮槽函数里写一个while循环读帧,读到文件末尾才能退出,这期间界面完全无响应。正确的做法是使用独立线程播放。
我封装了一个VideoThread类,继承自QThread,大致结构如下:
class VideoThread : public QThread { Q_OBJECT public: explicit VideoThread(QObject *parent = nullptr); void setVideoPath(const QString& path); void setProcessingEnabled(bool enabled); void stop(); signals: void frameReady(const QImage& frame); void processingFinished(const QImage& result); void playbackFinished(); protected: void run() override; private: QString m_videoPath; bool m_processEnabled; bool m_stop; };这里有个信号槽传递的坑需要重点说明:frameReady信号里的参数是QImage,不是cv::Mat。为什么不用cv::Mat?因为跨线程传自定义类型需要调用qRegisterMetaType注册,而QImage是Qt内置类型,信号槽直接就能识别,不需要额外注册。而且QImage本身就是显示控件能直接用格式,省得主线程再做一次转换。这个设计让我在线程和界面之间传递数据非常顺滑。
4.2 摄像头调用与设备管理
调用摄像头本质上也是VideoCapture,只不过参数的整数索引换成了设备编号:
cv::VideoCapture cap(0); // 0表示第一个摄像头 if (!cap.isOpened()) { // 打开失败处理 } cap.set(cv::CAP_PROP_FRAME_WIDTH, 1280); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 720); cap.set(cv::CAP_PROP_FPS, 30);摄像头打开失败的原因很多,常见的有设备被其他程序占用、驱动没装好、笔记本摄像头权限被禁用。调试时先把设备索引0试一遍,如果系统有多个摄像头,再试1、2。
还有一个需要注意的点是:设置相机分辨率必须在打开设备之后立即设置,并且设置的结果不一定生效,部分摄像头会强制落到它支持的最接近的分辨率。如果你想确认实际分辨率,用cap.get(cv::CAP_PROP_FRAME_WIDTH)和cap.get(cv::CAP_PROP_FRAME_HEIGHT)读回来看看。
4.3 视频录制与编码格式选择
录制视频是视频处理软件很自然的需求。OpenCv的VideoWriter类负责这个任务。
cv::VideoWriter writer; // 参数:文件名、编码格式、帧率、尺寸 bool ok = writer.open("output.avi", cv::VideoWriter::fourcc('M','J','P','G'), 25.0, cv::Size(width, height));fourcc编码格式的选择影响输出文件的兼容性。我实测下来,MJPEG编码兼容性最好,Windows和Linux都能播,缺点是文件体积大。XVID编码压缩率高,文件小,但播放时有些系统需要额外装解码器。H264格式的fourcc是('H','2','6','4'),但OpenCv编译时若不包含对应编解码库会失败。
录制视频最容易出的问题是帧尺寸不匹配。写入视频的帧尺寸必须和writer.open时设置的尺寸完全一致,否则会写出损坏的文件。所以在写入前一定要检查frame.cols和frame.rows是否等于预设尺寸,不一致就resize一下:
if (frame.cols != width || frame.rows != height) { cv::resize(frame, frame, cv::Size(width, height)); } writer.write(frame);还有一个坑是录制结束时必须调用writer.release(),否则文件头没有正确写入,输出的文件可能打不开。最好在析构函数或stop函数里确保调用。
4.4 多标签页视频处理
为了把图像处理和视频处理整合在一个软件里,我用了QTabWidget做多标签页。第一个标签放图像处理面板,第二个标签放视频处理面板。这样做的思路是:图像处理和视频处理的交互差异很大,一个面向静态图片分析,一个面向动态流处理,放一个页面里控件太拥挤,分开更清晰。QTabWidget本身的使用很基础,但它在架构上体现了“功能聚合,界面隔离”的思想,后续想加深度学习识别模块,就再开一个新标签页。
5. 踩坑实录与常见问题排查
5.1 高频问题排查速查表
这个项目做完之后,我把开发过程中遇到的高频问题整理成了一张表,每个问题都对应具体的排查思路和解决方案,方便后来人有问题直接对号入座。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 编译通过但运行时提示缺少opencv_world455.dll | OpenCv的bin目录不在PATH环境变量中 | 将dll复制到exe目录,或手动添加环境变量 |
| 图像颜色显示偏蓝/偏红 | Mat的BGR通道顺序和QImage的RGB不一致 | 转换时执行cvtColor(COLOR_BGR2RGB) |
| 视频播放时界面冻结无响应 | 视频读取写在了UI主线程里 | 改用QThread或QtConcurrent处理视频帧读取 |
| 信号槽传递cv::Mat编译报错 | 自定义类型未注册到元类型系统 | 用qRegisterMetaType cv::Mat ()注册,或改用QImage传递 |
| 打开摄像头返回失败 | 摄像头被占、索引不对或权限受限 | 检查设备管理器,尝试不同索引,确认权限设置 |
| 中文路径文件读取失败 | OpenCv在Windows下对中文路径支持不佳 | 用imencode编码后用QFile写文件,或改用纯英文路径 |
| Release模式下程序崩溃,Debug正常 | OpenCv的release库配置错误 | 确认release链接的是不带d后缀的lib文件 |
| 录制出的视频无法播放 | 帧尺寸不匹配或未调用writer.release() | 写入前resize帧到预设尺寸,结束前确保release |
| QT5窗口无法拖拽文件到界面 | 未启用setAcceptDrops(true)或未重写dragEnterEvent | 启用拖拽接受事件,重写dropEvent读取文件路径 |
| QString从OpenCv数据转换后显示乱码 | 字符编码不一致 | 统一使用QString::fromUtf8或fromLocal8Bit按需转换 |
5.2 三个影响最大的隐蔽坑
第一个是waitKey在Qt事件循环里的坑。OpenCv自带窗口时,imshow之后必须调用waitKey(1)让窗口刷新。但如果你在Qt程序里用了imshow和waitKey,会发现界面卡顿,甚至waitKey一直卡住不返回。原因是OpenCv的HighGUI窗口有自己的事件循环,和Qt的事件循环互相抢占线程,两者不兼容。解决办法很干脆:在Qt程序里不要用imshow,所有图像显示都通过QLabel控件完成。
第二个坑是跨线程信号槽传递非Qt类型的编译错误。如果信号参数是cv::Mat,编译时可能报“无法从cv::Mat转换到const QArgumentType&”之类的错误。网上很多解决办法是建议加qRegisterMetaType cv::Mat (),但更优雅的方案是干脆用QImage作为信号参数,彻底绕开类型注册的问题,只在需要Mat做算法处理时再转回去。
第三个坑是QImage和cv::Mat的内存生命周期问题。QImage构造时引用Mat的内存,函数返回后Mat销毁,QImage就变成了悬空指针,显示时会花屏或者崩溃。这是很多新手最容易忽视的问题。解决方案其实很简单,用前面给的工具函数里的copy()做一次深拷贝,代价是性能略降,但换来安全撑得住。
5.3 拖拽文件支持的特殊处理
界面上支持直接拖拽图片和视频文件进来打开,这是个提升体验的好功能。但QT5里拖拽文件有个容易忽略的点,必须同时重写dragEnterEvent和dropEvent两个事件函数。dragEnterEvent里要接受拖拽事件,否则dropEvent永远不会被调用:
void MainWindow::dragEnterEvent(QDragEnterEvent* event) { if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } } void MainWindow::dropEvent(QDropEvent* event) { QList<QUrl> urls = event->mimeData()->urls(); if (urls.isEmpty()) return; QString filePath = urls.first().toLocalFile(); QFileInfo info(filePath); if (info.suffix() == "jpg" || info.suffix() == "png" || info.suffix() == "bmp" || info.suffix() == "mp4" || info.suffix() == "avi") { // 根据扩展名分发到图像处理或视频处理标签页 openFile(filePath); } }这里的分发逻辑用文件扩展名判断,虽然不够严谨,但实际用起来足够了。还可以用QFileInfo的suffix()统一转小写再比较,避免遇到大写扩展名时判断失效。
6. 进阶扩展方向与个人经验总结
6.1 从Demo到工具的进阶思路
如果基础功能都完成并稳定运行了,下一步可以往这些方向扩展,每一个都能显著提升软件的专业度和实用性。
一是加深度学习模块。把OpenCv的DNN模块或者第三方推理框架接入,用训练好的模型替换传统算法。比如目标检测用YOLO系列,人脸识别用OpenFace或SeetaFace,图像分类用ResNet等。接入方式和现有架构完全兼容,算法层加几个新接口,界面层加个新标签页就行。
二是做批量处理。现在图像处理只能一张一张看效果,可以加一个批量处理工具:用户选择文件夹,软件遍历所有图片,应用当前算法参数,输出到指定目录。这个功能在工程实践中非常有价值,能极大提升批量出图的效率。
三是加OpenCV的GUI模块扩展。OpenCv里有个viz模块,可以做3D可视化,比如显示三维点云、渲染相机位姿。如果项目往后做SLAM或者三维重建方向,这个模块可以拿来即用。
6.2 项目完整度的自我审视标准
一个图像处理软件能不能算“完整”,我有几个自检标准:能不能打开并保存常见格式的图像文件?能不能在界面上实时调节算法参数并看到效果?视频处理会不会卡死界面?错误处理是否给到用户明确提示?程序在不同电脑上能不能跑起来不报错?这些问题都解决了,软件的基本盘就稳了。剩下的算法效果提升、界面美化都是锦上添花。
6.3 重新做一次的建议
如果让我重新做一遍这个项目,我会在一开始就做好三件事。第一,先搭好线程框架,而不是先写界面再补线程,避免后期重构。第二,提前把QImage和Mat的转换封装成公共工具类,所有模块统一调用,不重复造轮子。第三,一开始就设计好错误处理机制,视频打不开、摄像头没连上、文件路径不存在,这些情况都要给用户明确的提示信息,而不是程序直接崩溃。软件开发的很多问题不是难在功能实现,而是难在没有提前做好规划。这个心得,从做这个图像处理软件里体会得特别深。