尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

OpenCV基础续:轮廓检测与模板匹配

OpenCV基础续:轮廓检测与模板匹配 OpenCV基础续轮廓检测与模板匹配摘要本文系统梳理了 OpenCV 中轮廓检测与分析的核心知识涵盖轮廓检测、轮廓绘制、轮廓特征面积/周长/外接圆/外接矩形、轮廓近似以及模板匹配并提供了完整的 Python 代码示例。 前言在上一篇中我们学习了图像形态学和边缘检测。当我们通过边缘检测或二值化找到了物体的边界后下一步通常就是提取这些边界的轮廓——它们是目标识别、形状分析、物体计数等任务的重要基础。本文将结合实战代码带你系统掌握 OpenCV 的轮廓检测、绘制、特征分析、近似以及模板匹配技术。一、 轮廓检测轮廓是指图像中具有相同颜色或强度的连续边界点集合。在 OpenCV 中使用cv2.findContours()函数进行轮廓检测。函数签名与参数contours, hierarchy cv2.findContours(img, mode, method)注意OpenCV 2~3 版本会返回image、contours、hierarchy三个返回值OpenCV 4 之后仅返回轮廓与层级。跨版本兼容写法cv2.findContours(...)[-2]输入参数参数含义img输入图像轮廓检测前必须处理成仅有 0 和 255 的二值图像白色像素视作检测目标mode轮廓检索模式共 4 类见下表method轮廓近似储存方式共 2 种常用见下表mode轮廓检索模式模式原理描述cv2.RETR_EXTERNAL只检测外层轮廓忽略所有内部嵌套轮廓cv2.RETR_LIST检出全部轮廓不建立轮廓层级所有轮廓处于同一层级cv2.RETR_CCOMP获取全部轮廓仅有两层层级。物体外轮廓属于第一层物体内部孔洞轮廓为第二层孔洞内物体轮廓再次归为第一层cv2.RETR_TREE获取所有轮廓搭建完整嵌套式树形层级结构method轮廓近似储存方式方式原理描述cv2.CHAIN_APPROX_NONE保存轮廓线上全部像素坐标点cv2.CHAIN_APPROX_SIMPLE压缩多余点位直线只保留端点例如矩形轮廓仅储存四个角点节省存储空间返回参数返回值含义contours存储所有轮廓的列表每一条轮廓是 numpy 数组存放轮廓各个 (x,y) 坐标hierarchy轮廓层级信息每条轮廓对应数组[Next, Previous, First child, Parent]。Next同一层级中下一条轮廓索引无则为 -1Previous同一层级中上一条轮廓索引无则为 -1First child当前轮廓第一个内层子轮廓索引无子轮廓为 -1Parent当前轮廓对应的外层父轮廓索引没有父轮廓则为 -1⚠️ 重要注意事项该函数只会识别白色的前景目标若目标为黑色需要图像像素取反查找轮廓操作会改动原图不想修改原图需要传入图像副本单层外圈物体选用RETR_EXTERNAL多层嵌套轮廓使用RETR_TREE日常优先使用CHAIN_APPROX_SIMPLE压缩轮廓点代码示例importcv2 phonecv2.imread(Phone_pen.png)# 读取原图phone_graycv2.cvtColor(phone,cv2.COLOR_BGR2GRAY)# 灰度图的处理cv2.imshow(phone_gray,phone_gray)cv2.waitKey(0)ret,phone_binarycv2.threshold(phone_gray,120,255,cv2.THRESH_BINARY)# 二值化阈值120cv2.imshow(phone_binary,phone_binary)cv2.waitKey(0)# cv2.RETR_TREE获取所有轮廓搭建完整嵌套式树形层级结构。contourscv2.findContours(phone_binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)[-2]# cv2.RETR_EXTERNAL只检测外层轮廓忽略所有内部嵌套轮廓contours1cv2.findContours(phone_binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]# cv2.CHAIN_APPROX_NONE保存轮廓线上全部像素坐标点contours2cv2.findContours(phone_binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)[-2]print(len(contours))输出展示原图灰度图phone_gray二值图phone_binary三种不同参数组合检测到的轮廓数量不同绘制效果对比如下RETR_TREE完整层级8个轮廓RETR_EXTERNAL仅外层4个轮廓控制台输出8可以看到RETR_EXTERNAL只检出了 3 个最外层轮廓手机外框、钢笔主体、笔帽而RETR_TREE检出了 8 个轮廓包含了内部的所有细节。小结轮廓检测的前置条件是先获得二值图灰度化 → 二值化。mode 参数决定了轮廓之间的层级关系结构method 参数决定了轮廓点的存储方式。日常最常用的组合是RETR_TREECHAIN_APPROX_SIMPLE既获取完整层级又节省内存。二、 轮廓的绘制检测到轮廓后通常需要把它们画在图像上以便可视化观察。OpenCV 提供cv2.drawContours()函数完成这项工作。函数签名与参数cv2.drawContours(image, contours, contourIdx, color, thicknessNone, lineTypeNone, hierarchyNone, maxLevelNone, offsetNone)参数含义image要在其上绘制轮廓的输入图像会被直接修改建议传入副本contours轮廓列表通常由cv2.findContours()函数返回contourIdx要绘制的轮廓的索引。如果为负数如 -1则绘制所有轮廓color轮廓的颜色以 BGR 格式表示。例如 (0, 255, 0) 表示绿色thickness轮廓线的粗细。默认值为 1lineType轮廓线的类型。默认值为cv2.LINE_8hierarchy轮廓层次结构。通常由cv2.findContours()函数返回maxLevel绘制的最大轮廓层级。默认值为 None表示绘制所有层级offset轮廓点的偏移量。默认值为 None代码示例# 绘制 contoursRETR_TREE全部8个轮廓image_copyphone.copy()# 创建副本避免修改原图cv2.drawContours(imageimage_copy,contourscontours,contourIdx-1,color(0,255,0),thickness2)# -1表示绘制所有轮廓绿色线宽2cv2.imshow(Contours_show,image_copy)cv2.waitKey(0)# 绘制 contours1RETR_EXTERNAL仅3个外层轮廓image_copy1phone.copy()cv2.drawContours(imageimage_copy1,contourscontours1,contourIdx-1,color(0,255,0),thickness2)cv2.imshow(Contours_show1,image_copy1)cv2.waitKey(0)# 再次绘制 contours1image_copy1phone.copy()cv2.drawContours(imageimage_copy1,contourscontours1,contourIdx-1,color(0,255,0),thickness2)cv2.imshow(Contours_show1,image_copy1)cv2.waitKey(0)# 绘制 contours2CHAIN_APPROX_NONE全像素点保存image_copy2phone.copy()cv2.drawContours(imageimage_copy2,contourscontours2,contourIdx-1,color(0,255,0),thickness2)cv2.imshow(Contours_show1,image_copy2)cv2.waitKey(0)cv2.destroyAllWindows()# 销毁所有窗口输出展示Contours_showRETR_TREE 全轮廓Contours_show1RETR_EXTERNAL 外轮廓第三个窗口与第二个窗口效果相同重复绘制了 contours1。第四个窗口绘制的是CHAIN_APPROX_NONE的结果视觉上与第一组差异不大但内部存储的轮廓点数量要多得多3238 个点 vs 538 个点CHAIN_APPROX_SIMPLE538个点CHAIN_APPROX_NONE3238个点小结绘制轮廓前一定要用phone.copy()创建副本避免污染原图。contourIdx-1是最常用的参数表示一次性画出所有轮廓。若只想画某一个特定轮廓传入它在 contours 列表中的索引即可。三、 轮廓特征轮廓本身携带了大量几何信息常用的轮廓特征包括面积、周长、外接圆和外接矩形。利用这些特征可以完成按面积筛选、定位最大目标、规则框出物体等实用操作。函数签名与参数1. 轮廓面积retval cv2.contourArea(contour[, oriented])参数含义contour顶点构成的二维向量组如轮廓列表 contours 中的一个轮廓oriented定向区域标志默认值为 False返回面积的绝对值True 时则根据轮廓方向返回带符号的数值2. 轮廓周长retval cv2.arcLength(curve, closed)参数含义curve输入的二维点集轮廓顶点可以是 vector 或 Mat 类型closed用于指示曲线是否封闭3. 外接圆cv2.minEnclosingCircle(cnt)→ 返回((x, y), r)即圆心坐标和半径4. 最小外接矩形cv2.boundingRect(cnt)→ 返回(x, y, w, h)即左上角坐标和宽高代码示例phonecv2.imread(Phone_pen.png)phone_graycv2.cvtColor(phone,cv2.COLOR_BGR2GRAY)ret,phone_binarycv2.threshold(phone_gray,120,255,cv2.THRESH_BINARY)contourscv2.findContours(phone_binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)[-2]# ---------- 1. 计算面积和周长 ----------area_0cv2.contourArea(contours[0])# 第0个轮廓的面积print(area_0)area_1cv2.contourArea(contours[1])# 第1个轮廓的面积print(area_1)# arcLength(InputArray curve, bool closed) 轮廓周长# curve输入的二维点集(轮廓顶点)可以是 vector 或 Mat 类型。# closed用于指示曲线是否封闭。lengthcv2.arcLength(contours[0],True)# True表示轮廓是封闭的print(length)# ---------- 2. 根据面积筛选轮廓 ----------a_list[]foriincontours:# 遍历所有轮廓ifcv2.contourArea(i)10000:# 只保留面积大于10000的a_list.append(i)image_copyphone.copy()image_copycv2.drawContours(imageimage_copy,contoursa_list,contourIdx-1,color(0,255,0),thickness3)cv2.imshow(Contours_show_10000,image_copy)cv2.waitKey(0)# ---------- 3. 轮廓定位好方法根据轮廓面积进行排序 ----------sortcntsorted(contours,keycv2.contourArea,reverseTrue)[0]# 选取最大面积的轮廓image_contourscv2.drawContours(image_copy,[sortcnt],contourIdx-1,color(0,0,255),thickness3)# 红色粗线标注最大轮廓cv2.imshow(image_contours,image_contours)cv2.waitKey(0)# ---------- 4. 外接圆、外接矩形 ----------cntcontours[6](x,y),rcv2.minEnclosingCircle(cnt)# 计算轮廓的外接圆返回圆心(x,y)和半径rphone_circlecv2.circle(phone,(int(x),int(y)),int(r),(255,0,0),2)# 绘制外接圆蓝色线宽2cv2.imshow(phone_circle,phone_circle)cv2.waitKey(0)x,y,w,hcv2.boundingRect(cnt)# 计算轮廓的最小外接矩形返回左上角(x,y)和宽高(w,h)phone_rectanglecv2.rectangle(phone,(x,y),(xw,yh),(255,0,0),2)# 在图像上绘制矩形蓝色线宽2cv2.imshow(phone_rectangle,phone_rectangle)cv2.waitKey(0)cv2.destroyAllWindows()输出展示1面积与周长控制台输出852.0 638.0 236.5实际数值取决于二值化后的轮廓顺序读者运行时以实际输出为准。2面积大于 10000 的轮廓Contours_show_10000绿色粗线标出了面积超过 10000 的 4 个轮廓小面积的噪点轮廓被自动过滤。3最大面积轮廓image_contours红色粗线即为按面积排序后取出的最大轮廓叠加在上一步绿色轮廓之上。4外接圆与外接矩形phone_circle仅外接圆phone_rectangle外接圆 外接矩形代码中两次绘制操作都是在同一个phone原图上进行先画外接圆得到phone_circle左图仅蓝色圆再在同一张图上画外接矩形得到phone_rectangle右图蓝色圆和蓝色矩形同时存在。小结contourArea()和arcLength()是最基础的两个数值特征常用于筛选和阈值判断。按面积排序sorted(keycv2.contourArea, reverseTrue)是定位目标最实用的技巧一行代码即可找出整张图中最大的物体。minEnclosingCircle()和boundingRect()分别提供了圆形和轴对齐矩形两种框出目标的方式。四、 轮廓近似实际场景中检测到的轮廓可能包含成百上千个点但如果目标本质上是一个规则多边形矩形、三角形、六边形我们完全可以用更少的点来近似它。OpenCV 使用 Douglas-Peucker 算法实现轮廓近似。函数签名与参数approx cv2.approxPolyDP(curve, epsilon, closed)参数含义curve输入轮廓epsilon近似精度即两个轮廓之间最大的欧式距离。该参数越小得到的近似结果越接近实际轮廓反之得到的近似结果会更加粗略closed布尔类型参数表示是否封闭轮廓。True 表示输入轮廓是封闭的近似结果也会是封闭的否则近似结果不封闭返回值 approx近似结果是一个 ndarray 数组包含了被近似出来的轮廓上的点的坐标 epsilon 经验设置方法通常取轮廓周长的 1%~10%即epsilon 0.01 * cv2.arcLength(cnt, True)这样可以自适应不同大小的物体。代码示例phonecv2.imread(Phone_pen.png)phone_graycv2.cvtColor(phone,cv2.COLOR_BGR2GRAY)ret,phone_threshcv2.threshold(phone_gray,120,255,cv2.THRESH_BINARY)# 二值化# 获取轮廓注意此处使用 CHAIN_APPROX_NONE 保留全部点contourscv2.findContours(phone_thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)[-2]epsilon0.01*cv2.arcLength(contours[0],True)# 设置近似精度 周长的1% 【epsilon越小点越多越精确】apporxcv2.approxPolyDP(contours[0],epsilon,True)# 对轮廓进行近似True表示封闭轮廓print(contours[0].shape)# 原轮廓的形状点数量print(apporx.shape)# 近似后的形状点数量phone_newphone.copy()image_contourscv2.drawContours(phone_new,[apporx],-1,(255,0,0),3)# 绘制近似后的轮廓蓝色线宽3cv2.imshow(phone,phone)# 先展示原图cv2.waitKey(0)cv2.imshow(image_contours,image_contours)# 再展示近似轮廓cv2.waitKey(0)cv2.destroyAllWindows()输出展示1控制台输出形状对比(759, 1, 2) (8, 1, 2)实际运行时contours[0]iPhone 灰色手机外轮廓原本有 759 个点经过 1% 周长精度的近似后仅保留了8 个点对应手机的四个圆角各两个点压缩效果非常明显。2视觉对比原图phone 窗口近似轮廓image_contours 窗口蓝色粗线代码执行时先弹出phone窗口展示原图按键后再弹出image_contours窗口蓝色粗轮廓即为 approxPolyDP 近似后的结果原轮廓被绘制了 8 个顶点的闭合多边形完美贴合手机的圆角矩形轮廓。3不同 epsilon 精度对比为了更直观地看出 epsilon 对结果的影响下方列出 1%、10% 两种精度的对比图绿色为原轮廓红色为近似结果蓝色圆点为近似后保留的顶点epsilon 1% × 周长epsilon 10% × 周长759 点 →8点贴合圆角759 点 →4点标准矩形随着 epsilon 增大保留的顶点越来越少轮廓从贴近真实圆角形状逐渐变成了规整的矩形。小结轮廓近似的核心参数是 epsilon它是一个距离阈值原轮廓上的点到近似多边形的最大距离不超过 epsilon。经验公式epsilon k * cv2.arcLength(cnt, True)k 取 0.01~0.1可以自适应不同大小的轮廓。近似后得到的点数可以直接用来做形状分类例如 3 个点 → 三角形、4 个点 → 四边形。五、 模板匹配模板匹配是一种在大图待搜索图中寻找与小图模板最相似区域的方法。它无需训练、使用简单在目标姿态固定的场景中非常实用。函数签名与参数result cv2.matchTemplate(image, templ, method, resultNone, maskNone)参数含义image待搜索图像大图templ模板图像小图method计算匹配程度的方法见下表6 种匹配方法方法原理描述最佳匹配值TM_SQDIFF平方差匹配法采用平方差进行匹配越小越好TM_CCORR相关匹配法采用乘法运算越大越好TM_CCOEFF相关系数匹配法越大越好TM_SQDIFF_NORMED归一化平方差匹配法越小越好TM_CCORR_NORMED归一化相关匹配法越大越好TM_CCOEFF_NORMED归一化相关系数匹配法推荐越大越好返回值result是一个匹配结果矩阵其尺寸为(源图高 - 模板高 1, 源图宽 - 模板宽 1)矩阵中每个元素表示模板滑动到该位置时与源图的匹配程度。找出最佳匹配位置需要配合cv2.minMaxLoc()使用。代码示例importcv2# 1. 读取图像kele 为待搜索的源图像大图template 为模板图像小图kelecv2.imread(kele.png)templatecv2.imread(template.png)# 2. 先展示两张原图方便观察对比cv2.imshow(kele,kele)cv2.imshow(template,template)cv2.waitKey(0)# 按任意键继续# 3. 获取模板的高 h 和宽 w# shape 返回 (高, 宽, 通道数)取前两位即为 h, w# 后面画矩形时需要用它计算右下角坐标h,wtemplate.shape[:2]# 4. 模板匹配核心函数 cv2.matchTemplate# 参数1: 源图像(大图)# 参数2: 模板图像(小图)# 参数3: 匹配方法。TM_CCOEFF_NORMED 为归一化相关系数匹配# 结果范围 [-1, 1]越接近 1 表示匹配度越高# 返回值 res 是一个矩阵其尺寸为 (源图高-h1, 源图宽-w1)# 矩阵中每个元素表示模板滑动到该位置时与源图的匹配程度rescv2.matchTemplate(kele,template,cv2.TM_CCOEFF_NORMED)# 5. 从匹配结果矩阵中找出最值# cv2.minMaxLoc 返回四个值:# min_val : 矩阵中的最小值# max_val : 矩阵中的最大值即最佳匹配度# min_loc : 最小值所在的坐标 (x, y)# max_loc : 最大值所在的坐标 (x, y) —— 即模板左上角在源图中的位置# 由于使用的是 TM_CCOEFF_NORMED越大越匹配所以取 max_loc 作为匹配位置min_val,max_val,min_loc,max_loccv2.minMaxLoc(res)# 6. 由左上角坐标和模板宽高计算出匹配区域的右下角坐标top_leftmax_loc# 匹配区域左上角 (x, y)bottom_right(top_left[0]w,top_left[1]h)# 匹配区域右下角 (xw, yh)# 7. 在源图上绘制矩形框标出匹配到的区域# cv2.rectangle(img, pt1, pt2, color, thickness)# pt1左上角, pt2右下角, color(255,0,0) 为蓝色(BGR), 线宽 2# 注意: 该函数会在原图上直接绘制(in-place)建议用副本避免原图被修改kele_templatecv2.rectangle(kele,top_left,bottom_right,(255,0,0),2)# 8. 显示标注后的结果图cv2.imshow(kele_template,kele_template)cv2.waitKey(0)# 按任意键关闭cv2.destroyAllWindows()# 销毁所有窗口输出展示1两张原图待搜索源图 kele大图模板 template小图2匹配结果kele_template蓝色矩形框准确标出了模板在源图中的匹配位置。TM_CCOEFF_NORMED匹配值为0.9990非常接近 1说明匹配度极高。3三种归一化方法对比TM_CCOEFF_NORMED越大越好TM_CCORR_NORMED越大越好TM_SQDIFF_NORMED越小越好| 匹配值:0.9990✅ | 匹配值:0.9998✅ | 匹配值:0.0004✅ |三种归一化方法在本例中都成功定位到了目标位置。小结模板匹配的标准流程为读图 → 取模板尺寸 →matchTemplate→minMaxLoc取最值 → 根据 method 选最佳位置 →rectangle画框。带_NORMED后缀的归一化方法对光照变化更鲁棒优先使用TM_CCOEFF_NORMED。对于 SQDIFF 系列匹配越好值越小要用min_loc其他方法匹配越好值越大要用max_loc。模板匹配的局限性是对旋转、缩放、形变敏感。全文总结本文系统学习了 OpenCV 中轮廓检测与分析、模板匹配两大核心模块核心知识点梳理如下模块关键函数核心用途轮廓检测cv2.findContours()从二值图中提取所有边界轮廓轮廓绘制cv2.drawContours()在图像上把检测到的轮廓可视化轮廓特征contourArea()arcLength()minEnclosingCircle()boundingRect()获取面积、周长、外接圆、外接矩形等几何属性轮廓近似cv2.approxPolyDP()用更少的点近似轮廓用于形状识别模板匹配cv2.matchTemplate()cv2.minMaxLoc()从大图中快速查找模板小图的位置
返回列表