色彩空间变化
Color Spaces
OpenCV中默认的颜色空间GBR
img[y, x] = [B, G, R]
函数
cv.cvtColor
dst = cv.cvtColor(src,code)
src:原图
dst:转换后的图
code:转换类型!!
常用转换code:
| 转换 | code |
|---|---|
| BGR → Gray | cv.COLOR_BGR2GRAY |
| BGR → HSV | cv.COLOR_BGR2HSV |
| BGR → RGB | cv.COLOR_BGR2RGB |
灰度图
只保留亮度,丢掉颜色的图
- 单通道
- 数值范围:0–255
- 0:黑
- 255:白
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
HSV
| 分量 | 含义 | 范围 |
|---|---|---|
| H | Hue(色调) | 0–179 |
| S | Saturation(饱和度) | 0–255 |
| V | Value(亮度) | 0–255 |
HSV将颜色和亮度分离了
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
cv.inRange
mask:区域选择器
mask = cv.inRange(src, lowerb, upperb)
几何变换
| 类别 | 作用 |
|---|---|
| 平移 | 图像整体移动 |
| 缩放 | 改变分辨率、尺度 |
| 旋转 | 方向校正 |
| 仿射 | 保持平行性 |
| 透视 | 校正拍摄角度 |
函数
cv.warpAffine
dst = cv.warpAffine(src, M, dsize)
src:输入图像
M:2×3变换矩阵
dsize:输出图像大小
平移
M = np.float32([
[1, 0, tx],
[0, 1, ty]
])
tx,ty表示平移多少像素
rows, cols = img.shape[:2]
M = np.float32([
[1, 0, 50],
[0, 1, 30]
])
dst = cv.warpAffine(img, M, (cols, rows))
仿射变换
保持直线和平行关系,但允许拉伸和倾斜
pts1 = np.float32([
[x1, y1],
[x2, y2],
[x3, y3]
])
pts2 = np.float32([
[x1', y1'],
[x2', y2'],
[x3', y3']
])
M = cv.getAffineTransform(pts1, pts2)
dst = cv.warpAffine(img, M, (cols, rows))
cv.resize
缩放
指定缩放因子
res = cv.resize(img,None,fx=0.5,fy=0.5)
指定目标尺寸
res = cv.resize(img,(300,200))
cv.getRotationMatrix2D旋转
M = cv.getRotationMatrix2D(center, angle, scale)
center:旋转中心(x, y)
angle:旋转角度(逆时针,单位:度)
scale:缩放比例(通常 = 1)
阈值处理(Thresholding)
阈值
阈值 = 用一个数,把像素分成两类
函数
cv.thredshold
前文也讲过这个函数,但是这一次有了更深的理解
ret, dst = cv.threshold(src, thresh, maxval, type)
src:灰度图
thresh:阈值 T
maxval:通常是 255
type:阈值类型(关键)
ret:实际使用的阈值(Otsu 会用到)与thresh一致
dat:结果图像
常见阈值类型
- 二值阈值
cv.THRESH_BINARY
规则
src > T → 255
else → 0
- 反二值阈值
cv.THRESH_BINARY_INV
规则
src > T → 0
else → 255
- 截断阈值
- To Zero
自适应阈值
每个像素,用“邻域”来决定阈值
函数
dst = cv.adaptiveThreshold(
src, maxValue,
adaptiveMethod,
thresholdType,
blockSize,
C
)
blockSize:邻域大小(奇数,如 11, 15)
C:微调常数(经验项)
自适应方法
| 方法 | 说明 |
|---|---|
ADAPTIVE_THRESH_MEAN_C | 邻域平均 |
ADAPTIVE_THRESH_GAUSSIAN_C | 邻域高斯加权 |
Otsu阈值
自动从灰度直方图中,找到“最优分割点”
OpenCV用法
ret, binary = cv.threshold(
gray, 0, 255,
cv.THRESH_BINARY_INV + cv.THRESH_OTSU
)
thresh 传 0
实际阈值存在 ret 里
使用方法
√ 前景 / 背景 灰度分布双峰明显 ✖ 光照极不均 / 多峰分布
| 方法 | 特点 | 你现在是否适合 |
|---|---|---|
| 固定阈值 | 简单但脆弱 | ❌ |
| 自适应阈值 | 局部稳健 | ⚠️(辅助) |
| Otsu | 全局最优 | ✅(当前主力) |
图像滤波
用邻域的信息,替换当前像素的值
均值滤波
dst = cv.filter2D(img, -1, kernel)
kernel = np.ones((k, k), np.float32) / (k*k)
每个像素 = 周围像素“民主投票”
噪声被平均掉
边缘会被“抹平”
高斯滤波
dst = cv.GaussianBlur(img, (5, 5), sigmaX=0)
(5,5):邻域大小(必须是奇数)
sigmaX:标准差(0 = 自动算)
压掉细小颗粒噪声
保留大尺度沉淀区
不至于像均值滤波那样“糊边”
中值滤波
邻域排序,取中间值
dst = cv.medianBlur(img, k)
| 优点 | 缺点 |
|---|---|
| 去散点非常强 | 速度慢 |
| 保边缘 | 对连续噪声一般 |
双边滤波
既考虑空间距离,也考虑灰度相似度
离得近 + 灰度像 → 强影响
离得近 + 灰度差大 → 少影响
dst = cv.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)
| 问题 | 原因 |
|---|---|
| 慢 | 非线性 |
| 参数多 | 不稳定 |
| 不易解释 | 不利于复现 |
形态学操作 Morphological Operations
形态学 = 用一个“小几何模板”,去“推、挤、修整”二值图中的形状
结构元素
kernel = np.ones((3, 3), np.uint8)
几何模板
腐蚀Erosion
只要结构元素里有一个点碰到背景(黑),这个像素就变黑
eroded = cv.erode(binary, kernel, iterations=1)
膨胀 (Dilation)
只要结构元素里有一个点碰到前景(白),这个像素就变白
dilated = cv.dilate(binary, kernel, iterations=1)
开运算
opening = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel)
先腐蚀 再膨胀
删除小物体,保留大物体
闭运算
先膨胀,再腐蚀
填补小洞,修复断边
| 问题 | 是否该用形态学 |
|---|---|
| 零散白点 | ✅ Opening |
| 孔边缘毛刺 | ✅ Opening |
| 沉淀断裂 | ✅ Closing |
| 圆孔被当成沉淀 | ❌(这是几何问题) |
图像梯度
梯度 = 让计算机感知“变化发生在哪里”
像素值变化得有多快、往哪个方向变
- 变化慢 → 平坦区域
- 变化快 → 边界 / 边缘
梯度算子
Sobel算子
用一个小模板,近似计算一阶导数
- 对噪声不太敏感(因为它“顺带平滑”)
- 是工程中最常用的梯度方法
sobelx = cv.Sobel(gray, cv.CV_64F, 1, 0, ksize=3)
sobely = cv.Sobel(gray, cv.CV_64F, 0, 1, ksize=3)
(1,0):x 方向梯度
(0,1):y 方向梯度
ksize=3:卷积核大小(常用)
Sobel 输出的是:
- 正数:灰度增大
- 负数:灰度减小
但显示图像不能有负数,所以要取绝对值:
abs_sobelx = cv.convertScaleAbs(sobelx)
梯度强度
grad = cv.addWeighted(abs_sobelx, 0.5, abs_sobely, 0.5, 0)
图里面亮的地方=边界强
Scharr算子
Sobel 在 3×3 时对角度不太公平
Scharr 改进了这一点:
- 对小核尺寸下更精确
- 计算代价略高
scharrx = cv.Scharr(gray, cv.CV_64F, 1, 0)
scharry = cv.Scharr(gray, cv.CV_64F, 0, 1)
Laplacian-二阶梯度
lap = cv.Laplacian(gray, cv.CV_64F)
lap = cv.convertScaleAbs(lap)
Canny 边缘检测
在尽量抑制噪声的前提下,得到“细、连贯、可靠”的边缘
Sobel 的问题是:
-
只告诉你 哪里变化大
-
没有判断:
- 哪些是真边缘
- 哪些是噪声
- 哪些是“边缘的边缘”
步骤
1.高斯滤波
边缘 = 梯度极大值,而噪声也会产生梯度
2.计算梯度
3. 非极大值抑制(Non-maximum Suppression)
只保留“边缘方向上最强的那一个像素”
4. 双阈值检测(Double Threshold)
| 梯度强度 | 结果 |
|---|---|
| ≥ high | 强边缘(一定保留) |
| low ~ high | 弱边缘(待定) |
| < low | 非边缘(丢弃) |
5.边缘连接
用法
edges = cv.Canny(image, threshold1, threshold2)
threshold1:低阈值threshold2:高阈值
📌 顺序无所谓,OpenCV 内部会处理