OpenCV |2.图像处理

6 阅读5分钟

色彩空间变化

Color Spaces

OpenCV中默认的颜色空间GBR

img[y, x] = [B, G, R]

函数

cv.cvtColor
dst = cv.cvtColor(src,code)

src:原图

dst:转换后的图

code:转换类型!!

常用转换code:
转换code
BGR → Graycv.COLOR_BGR2GRAY
BGR → HSVcv.COLOR_BGR2HSV
BGR → RGBcv.COLOR_BGR2RGB
灰度图

只保留亮度,丢掉颜色的图

  • 单通道
  • 数值范围:0–255
  • 0:黑
  • 255:白
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
HSV
分量含义范围
HHue(色调)0–179
SSaturation(饱和度)0–255
VValue(亮度)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 > T255
else     → 0
  • 反二值阈值
cv.THRESH_BINARY_INV

规则

src > T0
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 内部会处理