Sobel 算子

Sobel 算子是计算图像 梯度 的方法之一。常用于图像边缘检测。

参阅:图像梯度_saltriver_CSDN

一般用 3*3 的Sobel 算子来计算,水平方向上的灰度值为:

$$ G_x = \begin{bmatrix} -1 & 0 & +1 \\ -2 & 0 & +2 \\ -1 & 0 & +1 \end{bmatrix} * A $$

垂直方向上的灰度值为:

$$ G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ +1 & +2 & +1 \end{bmatrix} * A $$
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
  • ddepth:图像的深度
  • dx, dy:水平和竖直方向
  • ksize:Sobel 算子的大小
img = cv2.imread('media/pie.png', cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)

# 负数默认会被截断成0,所以要取绝对值
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)

res = np.hstack((img, sobelx, sobely))
cv_show('img, sobelx, sobely', res)

可以用 cv2.addWeighted 将两个方向的梯度结果融合在一起:

sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv_show('sobelxy', sobelxy)

不建议使用:

sobelxy=cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_show('sobelxy_direct', sobelxy)
lena = cv2.imread('media/lena.jpg', cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(lena, cv2.CV_64F, 1, 0, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)

sobely = cv2.Sobel(lena, cv2.CV_64F, 0, 1, ksize=3)
sobely = cv2.convertScaleAbs(sobely)

sobelxy_direct = cv2.Sobel(lena, cv2.CV_64F, 1, 1, ksize=3)
sobelxy_direct = cv2.convertScaleAbs(sobelxy_direct)

sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
res = np.hstack((lena, sobelxy, sobelxy_direct))
cv_show("lena, sobelxy, sobelxy_direct", res)

Scharr 算子Laplacian 算子

Scharr 算子:

$$ G_x = \begin{bmatrix} -3 & 0 & +3 \\ -10 & 0 & +10 \\ -3 & 0 & +3 \end{bmatrix} $$
$$ G_y = \begin{bmatrix} -3 & -10 & -3 \\ 0 & 0 & 0 \\ +3 & +10 & +3 \end{bmatrix} $$

Laplacian 算子:

$$ G = \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{bmatrix} $$

不同算子对比

img = cv2.imread('media/lena.jpg', cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)

scharrx = cv2.Scharr(img, cv2.CV_64F, 1, 0)
scharry = cv2.Scharr(img, cv2.CV_64F, 0, 1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)

laplacian = cv2.Laplacian(img, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)

res = np.hstack((sobelxy, scharrxy, laplacian))
cv_show('sobelxy, scharrxy, laplacian', res)

可以看到,敏感度:scharr > sobel > laplacian


Canny 边缘检测

Canny 边缘检测算法是澳洲计算机科学家 John F. Canny 于 1986 年开发出来的一个多级边缘检测算法。

参阅:

算法步骤:

  1. 图像降噪:高斯滤波器
  2. 计算图像梯度大小和方向:Sobel 算子
  3. 非极大值抑制
  4. 双阈值筛选
img = cv2.imread("media/lena.jpg", cv2.IMREAD_GRAYSCALE)

Canny1 = cv2.Canny(img, 80, 150)
Canny2 = cv2.Canny(img, 50, 100)

res = np.hstack((Canny1, Canny2))
cv_show('Canny1, Canny2', res)

Canny() 中两个数字的是双阈值检测中的两个阈值,这个阈值范围影响边缘检测的灵敏度。

img = cv2.imread("media/car.png", cv2.IMREAD_GRAYSCALE)
Canny1 = cv2.Canny(img, 120, 250)
Canny2 = cv2.Canny(img, 50, 100)

res = np.hstack((img, Canny1, Canny2))
cv_show('img, Canny1, Canny2', res)

图像金字塔

将同一张图片制作为不同大小,摞在一起就成了图像金字塔。

参阅:

高斯金字塔

向下采样(逐步缩小):

img = cv2.imread("media/nanami.jpg")
down = cv2.pyrDown(img)
cv2.imshow('img', img)
cv2.imshow('down', down)
cv2.waitKey(0)

向上采样(逐步放大):

img = cv2.imread("media/nanami.jpg")
up = cv2.pyrUp(img)
cv2.imshow('img', img)
cv2.imshow('up', up)
cv2.waitKey(0)

(图片出自《summer pockets》)

向上采样或向下采样都是不可逆的,也就是说先执行向上采样再执行向下采样得到的图片相较于原图丢失了信息,会模糊许多。

拉普拉斯金字塔

参阅:拉普拉斯金字塔_笔尖bj_CSDN


图像轮廓

绘制轮廓

cv2.findContours(img, mode, method)
  • img:输入图像
  • mode:轮廓检测模式,详见 RetrievalModes_OpenCV docs,常用的是 RETR_TREE,即检索所有的轮廓
  • method:轮廓逼近方式,详见 ContourApproximationModes_OpenCV docs
    • CHAIN_APPROX_NONE:保存轮廓上所有的点
    • CHAIN_APPROX_SIMPLE:压缩简化轮廓,最终只保留轮廓的终点部分,如矩形只保留四个顶点

通常使用二值图像来进行图像轮廓检测。

img = cv2.imread("media/figure.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

# 因为轮廓是画在传入图片上的,所以最好复制一份,不然原图会变
img_contour = img.copy()

# 传入参数依次是:输入图像,轮廓,轮廓索引,颜色模式,线条粗细
contour = cv2.drawContours(img_contour, contours, -1, (0, 0, 255), 2)
res = np.hstack((img, contour))
cv_show('img, contour', res)

轮廓特征

cnt = contours[0]

# 面积
print(cv2.contourArea(cnt))

# 周长,True 表示闭合
print(cv2.arcLength(cnt, True))

轮廓近似

原理:设定一个阈值,然后将图像轮廓上的点逐个两两作差,如果其差值小于设定的阈值,就连接两点作为原轮廓的近似。所以这个阈值越接近于 0,近似出的轮廓就越接近原轮廓。

img = cv2.imread("media/figure2.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]

img_copy1 = img.copy()
res1 = cv2.drawContours(img_copy1, [cnt], -1, (0, 0, 255), 2)

# 阈值设定,一般取轮廓周长的小数倍
epsilon = 0.1 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
img_copy2 = img.copy()
res2 = cv2.drawContours(img_copy2, [approx], -1, (0, 0, 255), 2)

res = np.hstack((img, res1, res2))
cv_show('img, res1, res2', res)

外接图形

如外接矩形、外接圆等。

img = cv2.imread("media/figure.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]

img_copy1 = img.copy()
x, y, w, h = cv2.boundingRect(cnt)
res1 = cv2.rectangle(img_copy1, (x, y), (x + w, y + h), (0, 255, 0), 2)

img_copy2 = img.copy()
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
res2 = cv2.circle(img_copy2, center, radius, (0, 255, 0), 2)

res = np.hstack((res1, res2))
cv_show('res1, res2', res)