4. OpenCV基础(三):图像形态学操作、轮廓检测与直方图
各位好,欢迎来到第四章。这一章的内容,我个人觉得是OpenCV里最“出活”的部分。你想想看,前面我们学了怎么读图、调颜色、做滤波,但真正要把图像里的“东西”提取出来,靠的就是形态学和轮廓检测。说白了,就是让计算机学会“看”形状。
我记得刚入行那会儿,做一个零件检测的项目。图像里全是噪点,阈值分割完还是一团糟。后来用了开运算和闭运算,效果立竿见影。嗯,今天我们就来把这些“手术刀”一把一把讲清楚。
4.1 图像形态学操作:腐蚀与膨胀
形态学操作,核心就两个动作:腐蚀和膨胀。你可以把它们想象成“侵蚀”和“扩张”。
- 腐蚀(Erosion):让白色区域“瘦”一圈。适合去掉细小的白色噪点。
- 膨胀(Dilation):让白色区域“胖”一圈。适合填补物体内部的小空洞。
它们俩通常配合一个“核”(Kernel)来工作。核就是一个小的结构元素,比如3x3的方形或圆形。
import cv2
import numpy as np
# 读取二值图像(或先做阈值处理)
img = cv2.imread('shape.png', 0)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 定义核
kernel = np.ones((5,5), np.uint8)
# 腐蚀
erosion = cv2.erode(binary, kernel, iterations=1)
# 膨胀
dilation = cv2.dilate(binary, kernel, iterations=1)
cv2.imshow('Erosion', erosion)
cv2.imshow('Dilation', dilation)
4.2 开运算与闭运算
腐蚀和膨胀单独用,效果有限。真正好用的是它们的组合:
- 开运算(Opening):先腐蚀,再膨胀。用来去除小白点噪点,同时保持主体大小不变。
- 闭运算(Closing):先膨胀,再腐蚀。用来填充小黑洞,同时保持主体大小不变。
你想想看,如果直接腐蚀,物体变小了;直接膨胀,物体变大了。但开闭运算做完,大小基本不变,只改变细节。
# 开运算
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# 闭运算
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
4.3 轮廓检测与绘制
形态学做完,图像干净了。下一步就是找轮廓。OpenCV里最常用的就是findContours函数。这个函数会返回图像中所有连通域的边界点集。
这里有个坑,我必须要说:输入图像必须是二值图,且背景是黑色,物体是白色。否则结果会完全反掉。
# 找轮廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 在原图上绘制轮廓
img_color = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.drawContours(img_color, contours, -1, (0, 255, 0), 2)
cv2.imshow('Contours', img_color)
参数说明:
| 参数 | 含义 | 常用值 |
|---|---|---|
| mode | 轮廓检索模式 | RETR_EXTERNAL(只找最外层)、RETR_TREE(全层级) |
| method | 轮廓近似方法 | CHAIN_APPROX_SIMPLE(压缩水平/垂直/对角段) |
4.4 直方图均衡化
直方图均衡化,说白了就是“拉对比度”。当一张图太暗或太亮,细节都挤在一起时,均衡化可以把灰度值均匀摊开,让细节显现出来。
OpenCV里直接调用equalizeHist就行。注意:它只接受单通道灰度图。
img = cv2.imread('dark.jpg', 0)
equ = cv2.equalizeHist(img)
# 并排显示
result = np.hstack((img, equ))
cv2.imshow('Equalization', result)
效果非常直观。左边灰蒙蒙,右边层次分明。但有个问题:它会放大噪声。如果原图噪点多,均衡化后噪点会更明显。
4.5 模板匹配
模板匹配,就是拿一个小图(模板)在大图里滑动,找最像的位置。OpenCV提供了matchTemplate函数,返回一个匹配结果图,然后用minMaxLoc找到最佳匹配点。
img = cv2.imread('big.jpg', 0)
template = cv2.imread('small.jpg', 0)
w, h = template.shape[::-1]
# 匹配
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 画矩形
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(img, top_left, bottom_right, 255, 2)
匹配方法有几种:
| 方法 | 特点 |
|---|---|
| TM_CCOEFF_NORMED | 归一化相关系数,对光照变化不敏感,推荐 |
| TM_CCORR_NORMED | 归一化相关,速度快但精度一般 |
| TM_SQDIFF_NORMED | 归一化平方差,值越小越匹配 |
4.6 本章知识体系图
下面这张图,帮你把本章的核心逻辑串起来:
4.7 小结与避坑清单
这一章的内容,在实际项目中几乎天天用。我把几个容易出问题的地方列出来,你记一下:
- 形态学核大小:从3x3开始试,别贪大。iterations可以叠加,效果更可控。
- 轮廓检测输入:一定是二值图,且背景为黑。如果结果反了,用
cv2.bitwise_not取反。 - 直方图均衡化:先降噪再做,否则噪声会被放大。
- 模板匹配:不抗旋转和缩放。如果目标有角度变化,换特征匹配。