《零基础入门AI: 从轮廓查找到形态学变换(OpenCV图像预处理)》
本文针对图像处理初学者,详细解析OpenCV核心预处理技术,包含概念解释、可视化示例和关键代码片段,帮助您建立系统的图像处理知识体系。
一、图像轮廓特征查找:对象的几何描述
轮廓是连接图像中所有连续边界点的曲线,用于描述物体的形状特征。在OpenCV中,轮廓查找通常需要先进行二值化处理:
import cv2
import numpy as np# 读取图像并转为灰度图
img = cv2.imread('object.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 二值化处理
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)# 查找轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
-
外接矩形(Bounding Rect)
- 能完全包围轮廓的正矩形,边与坐标轴平行(不旋转)
- 特点:计算简单快速
- 应用:物体定位、碰撞检测
cv2.boundingRect()
返回矩形左上角坐标和宽高- 数学表示:
(x, y, width, height)
x, y, w, h = cv2.boundingRect(contour) cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
-
最小外接矩形(Rotated Rect)
- 可旋转的矩形,面积最小且完全包围轮廓
- 特点:更紧密包围对象
- 应用:物体方向检测、精密测量
cv2.minAreaRect()
返回中心点、尺寸和旋转角度- 数学表示:
(center_x, center_y), (width, height), angle
rect = cv2.minAreaRect(contour) box = cv2.boxPoints(rect) # 获取四个顶点 box = np.int0(box) cv2.drawContours(img, [box], 0, (0, 0, 255), 2)
- 完整示例
import cv2 import numpy as np# 读图 num = cv2.imread('../images/num.png')# 转灰度 gray = cv2.cvtColor(num, cv2.COLOR_BGR2GRAY)# 二值化处理 _,thresh = cv2.threshold(gray,200,255,cv2.THRESH_BINARY_INV)# 查找轮廓 contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)# 绘制轮廓 cv2.drawContours(num,contours,-1,(255,0,0),2)# 循环遍历每一条轮廓 for cnt in contours:# 图像轮廓特征查找:最小外接矩形 (x , y) w,h angleresult = cv2.minAreaRect(cnt)# print(result)# 处理点坐标points = cv2.boxPoints(result).astype(np.int32)# 绘制最小外接矩形cv2.drawContours(num,[points],0,(0,0,255),2)# 绘制外接矩形x,y,w,h = cv2.boundingRect(cnt)cv2.rectangle(num,(x,y),(x+w,y+h),(0,255,0),2)# 显示 cv2.imshow('num',num) cv2.waitKey(0) cv2.destroyAllWindows()
-
最小外接圆(Min Enclosing Circle)
- 完全包围轮廓的最小圆形
- 特点:对不规则形状更有效
- 应用:细胞分析、粒子检测
cv2.minEnclosingCircle()
返回圆心和半径- 数学表示:
(center_x, center_y), radius
(x, y), radius = cv2.minEnclosingCircle(contour) center = (int(x), int(y)) radius = int(radius) cv2.circle(img, center, radius, (255, 0, 0), 2)
- 完整示例
import cv2 import numpy as np# 读图 num = cv2.imread('../images/num.png')# 转灰度 gray = cv2.cvtColor(num, cv2.COLOR_BGR2GRAY)# 二值化 _,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY_INV)# 轮廓查找 contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)# 循环遍历轮廓 for cnt in contours:# 获取最小外接圆(x,y),radius = cv2.minEnclosingCircle(cnt)# 处理点坐标(x, y) = np.int32((x,y))radius = np.int32(radius)# 绘制圆cv2.circle(num,(x,y),radius,(0,0,255),2)# 显示 cv2.imshow('num',num) cv2.waitKey(0) cv2.destroyAllWindows()
应用场景:物体尺寸测量、方向识别、目标定位
二、直方图均衡化:提升图像对比度
直方图是图像像素强度的统计分布图:
- X轴:像素强度值(0-255)
- Y轴:具有该强度的像素数量
- 应用:分析图像亮度分布、指导增强处理
- 完整示例(绘制直方图)
import cv2
import numpy as np# 读图
bg = cv2.imread('../images/bg.png')# 创建一个黑色背景图,绘制直方图
black = np.zeros((256,256,3), np.uint8)# 统计像素
hist = cv2.calcHist([bg], [1], None, [256], [0, 256])
# print(hist)
# print(hist[241,0])# 获取直方图的最小值、最大值及其对应的最小值的位置索引、最大值的位置索引
minval, maxval, minloc, maxloc = cv2.minMaxLoc(hist)
# print(minval, maxval, minloc, maxloc)# 定义直方图的高
h_hist = np.int32(256)# 循环拿像素的个数
for i in range(256): # [num]l = np.int32(hist[i].item() * h_hist / maxval)point1 = (i,h_hist - l)point2 = (i,h_hist)cv2.line(black, point1, point2, (0,255,0), 2)# 显示
cv2.imshow('black', black)
cv2.waitKey(0)
cv2.destroyAllWindows()
-
直方图均衡化(HE)
直方图均衡化通过重新分布像素强度来增强对比度:
- 原理:将集中分布的强度值扩展到整个范围
- 效果:增强图像细节,特别是暗区和亮区,增强整体对比度但可能放大噪声
- 数学基础:累积分布函数(CDF)
import cv2# 读图 读为灰度图 img = cv2.imread('../images/zhifang.png',cv2.IMREAD_GRAYSCALE)# 直方图均衡化处理 dst = cv2.equalizeHist(img)cv2.imshow('img',img) cv2.imshow('dst',dst) cv2.waitKey(0) cv2.destroyAllWindows()
-
自适应直方图均衡化(AHE)
- 将图像分块后单独均衡化
- 增强局部对比度但放大噪声
-
对比度受限自适应均衡化(CLAHE)
- 限制局部对比度增强幅度
- 最佳效果:增强细节同时抑制噪声
import cv2# 读图 img = cv2.imread('../images/zhifang.png',cv2.IMREAD_GRAYSCALE)# 创建一个clahe对象 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))# 使用clahe 对象去调用apply()方法 cl1 = clahe.apply(img)cv2.imshow('img', img) cv2.imshow('cl1', cl1)cv2.waitKey(0) cv2.destroyAllWindows()
三、模板匹配:在图像中查找特定模式
在输入图像中滑动搜索与模板最相似的区域:
- 原理:滑动模板图像,计算相似度
- 应用:目标检测、工业质检、OCR预处理
import cv2
import numpy as np# 读图
img = cv2.imread('../images/game.png')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
temp = cv2.imread('../images/temp.png')
gray_temp = cv2.cvtColor(temp, cv2.COLOR_BGR2GRAY)print(gray_img.shape)
print(gray_temp.shape)# 获取 temp 的 h, w
h, w = gray_temp.shape# 模板匹配 拿到匹配结果矩阵
#
res = cv2.matchTemplate(gray_img,gray_temp,cv2.TM_CCOEFF_NORMED)
# print(res.shape)# 设置阈值
threshold = 0.8# 获取匹配上的结果的索引
loc = np.where(res >= threshold)
# print(loc)# 解包,拿到成对的(x, y) 的索引
# print(zip(*loc))
for i in zip(*loc):# print(i)x,y = i[1],i[0]# print(x,y)cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),1)# 显示
cv2.imshow('temp',temp)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
匹配方法对比:
六种主要相似度计算方法:
方法 | 公式 | 特点 | 适用场景 | 最佳匹配位置 |
---|---|---|---|---|
平方差匹配(TM_SQDIFF) | R=∑(T−I)2R = \sum(T-I)^2R=∑(T−I)2 | 值越小越匹配 | 精确匹配 | 最小值 |
归一化平方差(TM_SQDIFF_NORMED) | R=∑(T−I)2∑T2∑I2R = \frac{\sum(T-I)^2}{\sqrt{\sum T^2 \sum I^2}}R=∑T2∑I2∑(T−I)2 | 不受亮度影响 | 光照变化 | 最小值 |
相关匹配(TM_CCORR) | R=∑(T×I)R = \sum(T \times I)R=∑(T×I) | 值越大越匹配 | 快速匹配 | 最大值 |
归一化相关(TM_CCORR_NORMED) | R=∑(T×I)∑T2∑I2R = \frac{\sum(T \times I)}{\sqrt{\sum T^2 \sum I^2}}R=∑T2∑I2∑(T×I) | 最常用 | 通用场景 | 最大值 |
相关系数(TM_CCOEFF) | R=∑(T′×I′)∑T′2∑I′2R = \frac{\sum(T' \times I')}{\sqrt{\sum T'^2 \sum I'^2}}R=∑T′2∑I′2∑(T′×I′) | 消除均值影响 | 纹理匹配 | 最大值 |
归一化相关系数(TM_CCOEFF_NORMED) | 同上,归一化 | 最鲁棒 | 复杂场景 | 最大值 |
四、霍夫变换:检测几何形状
-
霍夫变换原理
霍夫变换将图像空间中的形状映射到参数空间:
- 核心思想:“投票机制” - 图像中的点投票支持可能的形状
- 优势:对噪声和部分遮挡鲁棒
-
霍夫直线变换
检测图像中的直线(计算量大):
- 参数空间:(ρ,θ)(\rho, \theta)(ρ,θ)
- ρ\rhoρ:原点到直线的距离
- θ\thetaθ:直线与x轴的夹角
import cv2 import numpy as np# 读图 img = cv2.imread('../images/huofu.png')# 灰度化 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)# 二值化 _,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)# 边缘检测 edges = cv2.Canny(thresh,100,200)# 霍夫变换 返回的是[r, theta] lines = cv2.HoughLines(edges,0.8,np.pi/180,90) # print(lines)for line in lines:r, theta = line[0]sin_theta = np.sin(theta)cos_theta = np.cos(theta)x1,x2 = 0, img.shape[1]y1 = int((r - x1 *cos_theta) / sin_theta)y2 = int((r - x2 *cos_theta) / sin_theta)cv2.line(img,(x1,y1),(x2,y2),(255,0,0),2)cv2.imshow('img',img) cv2.waitKey(0) cv2.destroyAllWindows()
- 参数空间:(ρ,θ)(\rho, \theta)(ρ,θ)
-
统计概率霍夫变换(HoughLinesP)
改进版直线检测:
- 优点:只检测线段,直接返回线段端点
- 效率:比标准霍夫变换更快
import cv2 import numpy as np# 读图 img = cv2.imread('../images/huofu.png')# 灰度化 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)# 二值化 _,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)# 边缘检测 edges = cv2.Canny(thresh,100,200)lines = cv2.HoughLinesP(edges,0.8,np.pi/180,90,minLineLength =50,maxLineGap=100) print(lines)for line in lines:x1,y1,x2,y2 = line[0]cv2.line(img,(x1,y1),(x2,y2),(255,0,0),2)cv2.imshow('img',img) cv2.waitKey(0) cv2.destroyAllWindows()
-
霍夫圆变换
检测图像中的圆形:
- 参数空间:(x,y,r)(x, y, r)(x,y,r)
- 原理:三维累加器投票
import cv2 import numpy as np# 读图 img = cv2.imread('../images/huofu.png')# 灰度化 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)# 二值化 _,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)# 边缘检测 edges = cv2.Canny(thresh,100,200)# 霍夫圆变换 circles = cv2.HoughCircles(edges,cv2.HOUGH_GRADIENT,1,10,param2 = 20) # print(circles)for circle in circles:x,y,radius = np.int32(circle[0])# print(x,y,radius)cv2.circle(img,(x,y),radius,(0,255,0),2)cv2.imshow('img',img) cv2.waitKey(0) cv2.destroyAllWindows()
五、图像亮度变换:像素值调整
-
亮度变换基础
调整图像整体或局部区域的亮度:
- 全局调整:影响整个图像
- 局部调整:影响特定区域
-
线性变换
最基础的亮度调整方法:
- 公式:g(x,y)=α⋅f(x,y)+βg(x,y) = \alpha \cdot f(x,y) + \betag(x,y)=α⋅f(x,y)+β
- α\alphaα:控制对比度(>1增加,<1减小)
- β\betaβ:控制亮度(正数变亮,负数变暗)
import cv2# 读图 cat = cv2.imread('../images/cat1.png')# 线性变换 alpha = 1.5 # 对比度增益 beta = 1 # 亮度增量 dst = cv2.addWeighted(cat, alpha, cat, beta, 0)# 显示 cv2.imshow('cat', cat) cv2.imshow('dst', dst) cv2.waitKey(0) cv2.destroyAllWindows()
-
直接像素修改
通过遍历像素直接修改值:
- 应用:创建自定义滤镜、选择性调整
- 注意:效率较低,应谨慎使用
bright_img = np.zeros_like(img) for y in range(img.shape[0]):for x in range(img.shape[1]):bright_img[y,x] = min(255, img[y,x] + 30) # 增加亮度
-
滑条控制亮度
import cv2
import numpy as np# 读图
cat = cv2.imread('../images/cat1.png')#创建窗口 用于实现滑条
window_name = "slide"
cv2.namedWindow(window_name,cv2.WINDOW_AUTOSIZE)\img = cv2.imread('../images/cat1.png')def change(p):x = p/256*511-255dst = np.uint8(np.clip(img + x,0,255))cv2.imshow('dst',dst)print(x)# 创建一个滑条
initial_value = 100
cv2.createTrackbar("add_p","slide",initial_value,255,change)cv2.waitKey(0)
cv2.destroyAllWindows()
实际应用:使用向量化操作提高效率
bright_img = cv2.add(img, 30)
六、形态学变换:形状处理的基础
-
结构元素(核)
形态学操作的核心组件:
- 作用:定义邻域大小和形状
- 类型:矩形、椭圆、十字形
- 大小:奇数尺寸(3×3、5×5等)
基于结构元素(核) 的形状操作:
kernel = np.ones((5,5), np.uint8) # 5x5正方形核
-
基本操作
操作 | 函数 | 效果 | 可视化 | 原理 | 应用场景 |
---|---|---|---|---|---|
腐蚀 | cv2.erode() | 缩小物体边界,消除小点 | ![]() | 用核的最小值替换锚点 | 去除噪声 |
膨胀 | cv2.dilate() | 扩大物体边界,填补空洞 | ![]() | 用核的最大值替换锚点 | 连接断裂 |
开运算 | cv2.morphologyEx(MORPH_OPEN) | 先腐蚀后膨胀 → 去噪 | ![]() | 先腐蚀后膨胀 | 背景分割 |
闭运算 | cv2.morphologyEx(MORPH_CLOSE) | 先膨胀后腐蚀 → 补洞 | ![]() | 先膨胀后腐蚀 | 前景完整 |
礼帽 | cv2.morphologyEx(MORPH_TOPHAT) | 原图 - 开运算 → 突出亮区域 | ![]() | 原图 - 开运算 | 背景均匀的亮特征 |
黑帽 | cv2.morphologyEx(MORPH_BLACKHAT) | 闭运算 - 原图 → 突出暗区域 | ![]() | 闭运算 - 原图 | 背景均匀的暗特征 |
形态学梯度 | cv2.morphologyEx(MORPH_GRADIENT) | 膨胀图 - 腐蚀图 → 提取边缘 | ![]() | 膨胀 - 腐蚀 | 边缘检测 |
import cv2
import numpy as np# 读图
car = cv2.imread('../images/car.png',cv2.IMREAD_GRAYSCALE)# 定义核
kernel = np.ones((5,5),np.uint8)# 腐蚀
erosion = cv2.erode(car,kernel,iterations = 1)# 膨胀
dilation = cv2.dilate(car,kernel,iterations = 1)# 开运算
opening = cv2.morphologyEx(car,cv2.MORPH_OPEN,kernel)# 闭运算
close = cv2.morphologyEx(car,cv2.MORPH_CLOSE,kernel)# 礼帽运算
tophat = cv2.morphologyEx(car,cv2.MORPH_TOPHAT,kernel)# 黑帽运算
blackhat = cv2.morphologyEx(car,cv2.MORPH_BLACKHAT,kernel)# 形态学梯度 膨胀和腐蚀差
gradient = cv2.morphologyEx(car,cv2.MORPH_GRADIENT,kernel)cv2.imshow('car',car)
cv2.imshow('erosion',erosion)
cv2.imshow('dilation',dilation)
cv2.imshow('opening',opening)
cv2.imshow('close',close)
cv2.imshow('tophat',tophat)
cv2.imshow('blackhat',blackhat)
cv2.imshow('gradient',gradient)cv2.waitKey(0)
cv2.destroyAllWindows()
核形状选择:
cv2.MORPH_RECT
矩形核(默认)cv2.MORPH_ELLIPSE
椭圆核cv2.MORPH_CROSS
十字形核
形态学操作可视化
原始图像: [1 1 1 0 0][1 1 1 0 0][1 1 1 0 0][0 0 0 0 0]腐蚀后: [1 1 0 0 0][1 1 0 0 0][0 0 0 0 0][0 0 0 0 0]膨胀后: [1 1 1 1 0][1 1 1 1 0][1 1 1 0 0][0 0 0 0 0]
技术组合应用示例
# 完整的预处理流程
img = cv2.imread('industrial_part.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 1. 亮度调整
adjusted = cv2.convertScaleAbs(gray, alpha=1.2, beta=20)# 2. CLAHE增强对比度
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_img = clahe.apply(adjusted)# 3. 中值滤波去噪
blurred = cv2.medianBlur(clahe_img, 5)# 4. 形态学开运算去噪
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
opened = cv2.morphologyEx(blurred, cv2.MORPH_OPEN, kernel)# 5. Canny边缘检测
edges = cv2.Canny(opened, 50, 150)# 6. 查找轮廓并绘制最小外接矩形
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:rect = cv2.minAreaRect(cnt)box = cv2.boxPoints(rect)box = np.int0(box)cv2.drawContours(img, [box], 0, (0,0,255), 2)cv2.imshow('Result', img)
cv2.waitKey(0)