ARTICLE DETAIL

资讯详情

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

OpenCV 实战第 1 篇:我用了两年 OpenCV,其实只调用过 3 个函数

OpenCV 实战第 1 篇:我用了两年 OpenCV,其实只调用过 3 个函数 一张模块地图 一个真实项目把 1500 个 API 收进 18 篇的路线图一、先对账我的 OpenCV 使用盘点写这个系列之前我把自己的代码库翻了一遍想搞清楚一件事过去两年我到底用了多少 OpenCV不是凭印象是扫出来的。我把工作区里所有可能出现的 OpenCV 函数名都列了一遍——imread、cvtColor、imwrite、findContours、minAreaRect、morphologyEx、Canny、GaussianBlur、warpPerspective、remap、matchTemplate、connectedComponents、HoughLines、goodFeaturesToTrack、opticalFlow、KalmanFilter、StereoSGBM、findHomography、grabCut、inRange、adaptiveThreshold、VideoCapture——然后全目录搜。结果如下出现位置实际调用用途aoi_code\AOI实战\09-产线部署.mdcv2.imread/cv::imread读图默认拿到的是 BGRaoi_code\AOI实战\09-产线部署.mdcv2.cvtColor/cv::cvtColor模式是COLOR_BGR2RGBBGR 转 RGB喂给模型aoi_code\AOI实战\10-实战踩坑录.md同上两个同上aoi_code\深度学习基础知识\09-产线部署.md同上两个同上aoi_code\深度学习基础知识\10-实战踩坑录.md同上两个同上aoi_code\AOI实战\02-灯组检测.md:154cv2.imwrite存灯组裁图Aether\13-Qt多线程的3种死法.md:311cv::Mat mat qImageToMat(input)工作线程里做算法计算两年三个函数。imread、cvtColor、imwrite。其中cvtColor还只用了一个模式——COLOR_BGR2RGB而且用它的目的不是处理图像是为了把图喂给深度学习模型。我盯着这个结果看了很久。不是能力问题。是我一直把 OpenCV 当成读图工具和转格式工具从来没当成一个图像处理库用。更扎心的是第二个发现我以为我会的实际工作区里出现过的形态学、阈值分割、边缘检测一次都没有几何变换、透视矫正一次都没有轮廓、形状分析、几何测量一次都没有相机标定、三维重建一次都没有特征检测、光流、跟踪一次都没有视频流处理一次都没有DNN 推理一次都没有我是用 Python 的 ONNX Runtime 绕过去的所以这个系列要解决的问题一句话就是会用 OpenCV 的 API不等于会用 OpenCV 做工程。接下来的 18 篇我要做的事是把 OpenCV 的模块地图摊开把每个模块里工程上真正高频的算子讲透并且每篇都落到一条能跑起来的流水线上而不是停在这个函数是干什么的。二、OpenCV 模块全景图在说算子之前得先有一张地图。否则学了一堆函数也不知道它们住在哪个模块、彼此什么关系。下面是OpenCV 4.13.0 的主模块清单来自官方文档 4.13.0 的模块索引页模块职责本系列篇次core核心功能cv::Mat容器、矩阵运算、掩码运算、混合、对比度/亮度调整、傅里叶变换02、03、06、13imgproc图像处理滤波与降噪、边缘检测、几何变换、色彩空间转换03~09imgcodecs图像文件读写imread / imwrite / imencode01 提及videoio视频读写与摄像头接入11highgui高级 GUIimshow / waitKey 等窗口交互不讲理由见下video视频分析背景建模、运动分析11calib3d相机标定与三维重建10features2d2D 特征检测与描述09objdetect目标检测级联分类器、HOG12dnn深度神经网络推理12ml机器学习SVM、决策树、树嵌入等14photo计算摄影HDR、去噪修复、细节增强14stitching图像拼接14gapi图计算把算子串成有向图调度执行15flann聚类与近邻检索Fast Library for Approximate Nearest Neighbors09 提及gapi和stitching是两个最容易被漏掉的模块。很多人一提 OpenCV 就想到cvtColor和Canny但工业流水线上真正吃性能预算的往往是gapi那一套图计算。模块之间的依赖关系┌──────────────────┐ │ core 地基 │ │ Mat / 算术 / 绘图 │ └────────┬─────────┘ │ 几乎所有模块都依赖它 ┌───────────┬───────────┬───┴────┬───────────┬───────────┐ ▼ ▼ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ imgproc │ │imgcodecs│ │videoio │ │ calib3d│ │features│ │ dnn │ │ 主战场 │ │ 读写 │ │ │ │ 精度源 │ │ 2D │ │ 新变量 │ └────┬────┘ └─────────┘ └────┬───┘ └───┬────┘ └────┬───┘ └───┬────┘ │ │ │ │ │ └───────────┬───────────┴─────────┴───────────┘ │ ▼ ▼ ┌────────────┐ ┌────────────┐ │ highgui │ │ video │ │ (不讲) │ │ objdetect │ └────────────┘ │ photo │ │ ml │ │ stitching │ │ gapi │ └────────────┘我不讲highgui的理由imshow、createTrackbar、waitKey这些窗口交互函数在产线代码里几乎不应该出现。原因很直接产线是无人值守的跑在工控机上没有显示器弹窗会阻塞主线程破坏节拍要写窗口就得引入highgui依赖而它在很多精简版 Linux 发行版上还需要额外的 GUI 库调试期想看图怎么办用imwrite存盘或者写个独立的离线调试小工具。我全系列都按这个方式来。学习资料喜欢按模块罗列 API工程需要的是知道每个模块为什么存在、以及它值不值得你引入。三、教学案例项目光讲算子会变成 API 手册。所以这个系列挂一个项目所有例子都落在它上面。声明本系列案例项目为教学演示用简化项目非真实产线项目。参数为演示值不代表量产指标。项目名金属板材表面缺陷检测与几何测量系统选这个项目的原因很实在它是一条能把 OpenCV 几乎所有主力模块串起来的流水线而且每一环都是工业视觉里真实存在的需求。流水线七环节① 成像与几何校正 相机采集 → 镜头畸变校正 → 拍摄角度矫正为俯视图 涉及videoio / calib3d / imgproc(remap, warpPerspective) ② ROI 定位与配准 在大图里找到零件位置粗定位用模板匹配精配准用特征 单应矩阵 涉及imgproc(matchTemplate) / features2d(ORB) / calib3d(findHomography) ③ 几何测量 量零件的长边长度、角度、圆度精度要求到亚像素 涉及imgproc(minAreaRect / fitEllipse / arcLength / moments cornerSubPix) ④ 规则缺陷检测 找划痕、油墨残留、压痕这类形状规则的缺陷 涉及imgproc(top-hat / black-hat threshold connectedComponents) ⑤ 视频流与实时化 在线检测的节拍治理丢帧检测、背压、流水线程 涉及videoio / video / core(TickRecorder) ⑥ AI 分支与传统分工 复杂外观缺陷交给 DNN几何测量和确定形状定位留在传统算子 涉及dnn / objdetect ⑦ 工程化与交付 参数配置化、内存复用、多线程编排、图计算加速 涉及core / gapi / contrib这七个环节会按顺序被 18 篇填满。你现在只需要记住这条链的存在读到第 18 篇时你会发现所有零件都装回去了。参数配置化贯穿全系列的一个习惯从第 1 篇开始我就立一条规矩所有阈值、尺寸、迭代次数一律不硬编码。// ❌ 错误示范阈值写死在代码里cv::threshold(src,dst,127,255,cv::THRESH_BINARY);// ✅ 正确示范从配置读且带上来源标注structSegmentParams{intlowThresh0;// Otsu 时为 0inthighThresh0;// Otsu 时为 0intblockSize31;// adaptiveThreshold 用doublec5.0;// adaptiveThreshold 用};为什么这条这么重要因为换一批料、换一个光源阈值就得改。硬编码的阈值意味着每次调整都要重新编译、重新发版。一个产线程序里如果连阈值都改不了那它离可用差得远。四、环境与工程集成这一节讲链接进来之后怎么组织代码。4.1 边界声明什么不在本系列讲关于「怎么用 CMake 找到并链接 OpenCV」我的 CMake 实战系列第 4 篇和第 6 篇已经详细讲过find_package(OpenCV ...)的用法与降级策略OpenCV_DIR/CMAKE_PREFIX_PATH的设置Debug / Release 库后缀不同怎么处理第三方库硬编码路径怎么优雅封装Generator Expressions做条件链接本系列不重复。我们假设 OpenCV 已经链接成功从#include开始讲。4.2 头文件按需包含别用opencv.hpp// ❌ 错误示范全量头文件#includeopencv2/opencv.hpp这一行把 4.13 的全部模块都拖进来包含 dnn、objdetect、stitching、gapi 这些你可能一辈子都不用的东西。代价是实打实的编译时间显著变长我实测过改一行代码重新编译从十几秒变成一分多钟头文件里的符号可能和你的其他库冲突编译期内存飙升在 CI 机器上容易 OOM// ✅ 正确示范按需包含#includeopencv2/core.hpp// Mat、算术、绘图#includeopencv2/imgproc.hpp// 滤波、变换、边缘、轮廓头文件是你的编译时间预算。不需要的东西别 include。4.3 启动时把版本打进日志这是我给自己的强制习惯任何程序启动第一件事就是打印 OpenCV 版本。// ✅ 完整可运行OpenCV 版本与构建信息#includeiostream#includeopencv2/core.hppintmain(){std::coutOpenCV version: CV_VERSION_MAJOR.CV_VERSION_MINOR.CV_VERSION_REVISION (CV_VERSION_STATUS)\n;// 关键这个版本号要和实际链接到的库一致std::coutModules:\ncv::getBuildInformation()\n;cv::Matimg(100,200,CV_8UC3,cv::Scalar(0,0,255));std::coutMat: img.colsximg.rows, channelsimg.channels(), continuousimg.isContinuous()\n;return0;}编译运行g main.cpp-oversion_check$(pkg-config--cflags--libsopencv4)./version_check为什么这件事值得单独一节我见过一个真实的坑程序在开发机上跑得好好的拷到产线工控机上 Segmentation fault。查下来是工控机上装了另一个版本的 OpenCV而程序链接的是新版本的头文件、运行时加载的是旧版本的 DLL——cv::Mat的内存布局在两个版本间不一致。CV_VERSION_MAJOR打印出来那一刻问题就现形了。版本号不是日志信息它是排查问题的第一个证据链。顺便说上面这个例子里我用pkg-config拿编译和链接参数方便快速验证。想了解完整安装和链接流程看我的 CMake 系列第 6 篇。4.4 Debug / Release 库不能混用这是条硬规则违反了就是运行期诡异崩溃。❌ 错误组合 Debug 版 exe → 链接 Release 版 OpenCV 库 原因Debug 版的 cv::Mat 在调试分配器下多带了校验头信息 结构和 Release 版不一致跨模块传递时结构被误读怎么保证不混最可靠的做法是在构建时把 OpenCV 的配置和你的配置锁死// ✅ 让配置不一致在编译期就报错而不是运行期崩溃#includeopencv2/core.hpp#includeopencv2/core/utility.hppstatic_assert(!defined(NDEBUG)||defined(OPENCV_BUILD_WITH_DEBUG),Release build must link against Release OpenCV, not Debug);更实用的做法是在初始化时断言voidcheckOpenCVBuild(){#ifdefNDEBUGconstboolneedDebugfalse;// Release 需要 Release 版库#elseconstboolneedDebugtrue;// Debug 需要 Debug 版库#endifif(needDebug){std::cout[warn] Debug build: ensure opencv_world4d is used\n;}else{std::cout[warn] Release build: ensure opencv_world4 is used\n;}}4.x 的开源包默认只带 Release 库。如果你要在 Debug 配置下做性能分析OpenCV 官方不提供预编译 Debug 库得自己用-DCMAKE_BUILD_TYPEDebug编译一份。这条也解释了另一个现象很多人在 Debug 下测出来的算子耗时不能信因为 OpenCV 内部断言全开着。4.5 Python 对照上面这段在 Python 里是这样importcv2importnumpyasnpprint(fOpenCV version:{cv2.__version__})imgnp.zeros((100,200,3),dtypenp.uint8)# 默认全 0print(fMat:{img.shape[1]}x{img.shape[0]}, fchannels{img.shape[2]}, fcontinuous{img.flags[C_CONTIGUOUS]})C 与 Python 的关键差异后面每篇都会遇到这里先说一次维度CPython容器cv::Mat引用计数 数据块numpy.ndarray自己管内存通道数维度独立字段channels()折叠进shape如(H, W, 3)连续性isContinuous()显式查询flags[C_CONTIGUOUS]ROIMat roi big(rect);切片big[y:yh, x:xw]深拷贝clone()/copyTo().copy()空判断empty()arr.size 0最容易踩的一个Python 里cv2.cvtColor默认会输出一个新数组而 C 里cv::cvtColor(src, dst, ...)如果你把src和dst传成同一个对象它会原地改写。同样的直觉两种行为。4.6 一个必须提前点名的坑QImage ↔ cv::Mat如果你的程序是 Qt 写的我这个工作区里 Aether 那个工业视觉平台就是那么QImage和cv::Mat不通这会是你的第一个真坑。我先不展开只把它的现象摆出来让你先有个警觉// ❌ 网上到处都能搜到的一段代码但在真实项目里会出事returncv::Mat(img.height(),img.width(),CV_8UC3,img.bits());这段代码有三个问题叠在一起没做像素格式转换、没传每行实际字节数、返回的Mat共享QImage的内存。其中第二个最阴险——换个图像宽度它可能就正常了因为 Qt 每行的对齐填充规则会变。这三个问题的解法全部依赖cv::Mat的内存契约数据块在哪、step是什么、生命周期归谁、连续性怎么判断。所以我把它放到第 2 篇讲。那 一篇会从cv::Mat的 header 引用计数 数据块三件套讲起然后用 Qt 桥接这个真实案例把概念全部落地。地基先立起来坑自然就填了。五、18 篇路线图下面这张表是全系列的地图。建议先收藏每写完一篇回来对一次进度。篇标题主模块阶段01我用了两年 OpenCV其实只调用过 3 个函数core / imgcodecs会用02cv::Mat 内存模型引用计数、ROI 与连续性core会用03cvtColor 全家族BGR/RGB/HSV 到底怎么选core / imgproc会用04去噪与形态学blur 和 morphologyEx 全家族imgproc会用05阈值分割threshold 全家族 Otsu 自适应阈值imgproc会用06几何变换四兄弟插值核选择与性能代价imgproc / core会用07Canny 阈值自适配、findContours 与多边形逼近imgproc会调08几何测量算子族从 boundingRect 到亚像素定位imgproc会调09Template Matching vs 特征匹配ROI 定位该选哪个imgproc / features2d会调10相机标定与 solvePnP畸变校正改变了什么calib3d会调11VideoCapture 正确打开姿势与丢帧治理videoio / video会调12dnn objdetect以及传统算子没死dnn / objdetect会调13工程化收尾内存、并行与一条完整流水线全模块会工程化14photo 去噪增强、ml 分类与 stitching 拼接photo / ml / stitching会工程化15G-API 图计算CPU/GPU 混合执行gapi会工程化16contrib 精选一ximgproc 的真香算子contrib会选型17contrib 精选二xfeatures2d 与 trackingcontrib会选型18终篇5.x 迁移指南含模块重组与成本清单版本迁移会选型三阶段成长路径会用 ──▶ 会调 ──▶ 会工程化 篇1~6 篇7~12 篇13~15 │ │ │ │ │ └─ 关注性能、代码组织、模块选型、版本风险 │ └─ 会根据场景选算子、会调参、知道什么时候该用 DNN └─ 能写出正确的基础代码类型和内存不踩坑这个顺序是刻意的。我见过很多人从第 10 篇标定开始学结果卡在cv::Mat的引用计数上白白浪费一个月。地基不正上面盖什么都不稳。六、算子选型速查表本篇这是本系列每篇都会附的一张表。第 1 篇能覆盖的只有基础三件套算子一句话作用什么时候用什么时候别用关键参数主要代价imread读图到cv::Mat离线调试、读样例图产线在线采集延迟不可控、失败无重试颜色标志磁盘 IO可能几十 MB 一次cvtColor色彩空间 / 通道顺序转换必须转灰度、转 HSV、做颜色分割、喂模型前换通道不需要转换时白白多一次内存分配与拷贝转换模式新分配一块内存 一次全图遍历imwrite把Mat存成文件调试留证、导出中间结果、生成样本集产线主流程阻塞 IO、可能失败质量参数、扩展名磁盘 IO 编码耗时QImage::Format_RGB888↔cv::MatQt / OpenCV 图像互转Qt 上位机里把界面图像喂给算法不涉及 Qt 的程序直接用imread无一次cvtColor内存分配这张表会随着系列推进一直变厚。到第 18 篇它会覆盖本系列涉及的全部算子。我建议大家看每一篇的时候把这张表抄一遍。抄过一遍的东西你忘不掉。七、避坑指南本篇 4 条全部属于环境与工程集成层面。涉及图像内存、QImage桥接、连续性、工作线程数据竞争的坑bytesPerLine漏传、传线程不深拷贝、不查isContinuous()它们都建立在cv::Mat的内存契约之上统一放在第 2 篇避免在篇 1 提前透支地基。坑 1find_package找对了运行期却加载了另一个版本现象开发机正常产线机段错误原因装了多个版本运行时加载顺序不确定或者头文件版本和 DLL 版本不一致❌ 以为find_package成功就万事大吉✅启动第一行打印CV_VERSION_MAJOR把版本写进日志再配opencv_world4的部署清单为什么版本不一致时cv::Mat的内存布局可能不同结构被误读。打印版本是最便宜的诊断手段坑 2全量#include opencv2/opencv.hpp把编译拖垮现象改一行代码重新编译一分多钟原因拖进了 dnn / objdetect / stitching / gapi 等全部模块❌ 抄别人的代码时连头文件一起抄✅按需包含opencv2/core.hpp、opencv2/imgproc.hpp分开引为什么编译期是开发体验的大头也是 CI 机器最容易 OOM 的地方坑 3Debug 版 exe 链接 Release 版 OpenCV现象Debug 下跑得好好的Release 下崩原因跨模块传递cv::Mat时Debug 与 Release 的结构不一致❌ 靠 CMake 缓存里的残留变量应该是一致的吧✅把配置锁死并且知道 4.x 开源包只有 Release 库为什么这也意味着你在 Debug 下测出来的算子耗时不可信——OpenCV 内部断言全开着坑 4把 OpenCV 当读图工具一个模块都没用上现象代码里只有imread/cvtColor/imwrite复杂活儿全靠 Python 绕❌ 觉得我又不做图像处理读个图就够了✅认清单系列盘点表形态学、变换、轮廓、标定、光流、视频你一个都没碰过为什么这个坑最隐蔽因为它不报错。你的程序能跑只是效率低、精度差而且你不知道有更好的路八、优秀实践 / 可改进之处优秀实践启动即打印版本与构建信息std::coutOpenCV version: CV_VERSION_MAJOR.CV_VERSION_MINOR.CV_VERSION_REVISION (CV_VERSION_STATUS)\n;两行代码成本几乎为零但把最常见的一类产线事故版本不一致从几小时排查变成一眼看穿。版本号不是日志信息它是排查问题的第一个证据链。可改进之处把「隐式假设」变成「显式契约」本篇反复出现一个模式// 隐式假设OpenCV 装的版本 我以为的版本// 隐式假设Qt 图像格式 CV_8UC3 的布局// 隐式假设ROI 是连续的这三个假设都是可以变成显式检查的而每一个显式检查的成本都很低隐式假设显式化方式代价版本是我以为的版本启动打印CV_VERSION_MAJOR两行格式匹配转换后isNull()判断一行内存连续isContinuous()判断一行把防御做在问题产生的地方而不是要求每个使用方都记得防御。这就是接口设计的价值。至于「图像格式」「内存连续性」「跨线程生命周期」这三组具体契约怎么落地——它们同属cv::Mat的内存模型连同Aether/13里那个inputImage.copy()qImageToMat的真实案例一起放在第 2 篇。本期互动你现在用 OpenCV 的方式最接近哪一种只读图 / 会写传统算子 / 会用 DNN / 全都用过但说不清代价你的项目里OpenCV 的版本号是多少有没有出现过开发机好好的、上线就崩你有没有把图像传进过工作线程拷了吗欢迎在评论区聊聊。 评论区聊聊我最想听到的是你们的踩坑经历尤其是那种代码没改别人那边就是不对的诡异问题。如果你遇到过一个难以解释的图像 bug——比如换个分辨率就错位、换个光源就全变、Debug 正常 Release 崩——把你的现象写在评论区。这系列的避坑指南我想用读者的真实案例来填而不是只用我自己的。 觉得有用记得收藏 转发篇 1 里那张18 篇路线图建议先收藏后面每篇都会回到它。如果你身边有人正在学 OpenCV或者正在用 OpenCV 做项目但说不清每个算子的代价转给他。这个系列的目标不是让你记住 1500 个函数而是让你在遇到问题时知道该翻哪一页。按上面那张表走一遍你至少不会再像两年前那样只用三个函数却以为自己在做图像处理。
返回列表