OpenCV 图像处理

1. 更改图像颜色空间

使用cv.cvtColor(),输入:图像,方法(比如cv.COLOR_BGR2HSV)

1
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
2. 几何转换操作

调整图像大小

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
import cv2 as cv

img = cv.imread('ml.png')
assert img is not None, "file could not be read, check with os.path.exists()"

res = cv.resize(img,None,fx=2, fy=2, interpolation = cv.INTER_CUBIC)

#OR

height, width = img.shape[:2]
res = cv.resize(img,(2*width, 2*height), interpolation = cv.INTER_CUBIC)

使用cv.warpAffine()平移图片;
使用cv.getRotationMatrix2D()获取2x3旋转矩阵,如旋转90度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import cv2 as cv

img = cv.imread('ml.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
rows,cols = img.shape

#M = np.float32([[1,0,100],[0,1,50]]) # 平移矩阵
M = cv.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),90,1) # 旋转矩阵
dst = cv.warpAffine(img,M,(cols,rows)) # 输入:图像,转换矩阵,尺寸(宽,高)

cv.imshow('img',dst)
cv.waitKey(0)
cv.destroyAllWindows()
3. 仿射变换

在仿射变换中,原图中平行线在变换后仍然平行。
方法为使用cv.getAffineTransform()获取2x3的转换矩阵后传入cv.warpAffine()
生成操作矩阵需要3组对应坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

img = cv.imread('ml.png')
assert img is not None, "file could not be read, check with os.path.exists()"
rows,cols,ch = img.shape

pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[10,100],[200,50],[100,250]])

M = cv.getAffineTransform(pts1,pts2)

dst = cv.warpAffine(img,M,(cols,rows))

plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()
4. 视角变换

视角变换后直线还是直线。变换矩阵是3x3的,需要4组对应已知的坐标,其中不能存在3点共线,这样便能使用cv.getPerspectiveTransform()获得转换矩阵,然后矩阵传入cv.warpPespective()即可

1
2
3
4
5
6
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])

M = cv.getPerspectiveTransform(pts1,pts2)

dst = cv.warpPerspective(img,M,(300,300))
5. 阈值处理

使用cv.threshold(),输入:图像(灰度),阈值,最大值(超阈值的设定为此),方法
返回:使用阈值, 阈值处理的图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('ml.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV)
ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC)
ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO)
ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)

titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)
plt.title(titles[i])
plt.xticks([]),plt.yticks([])

plt.show()

自适应阈值
cv.ADAPTIVE_THRESH_MEAN_C 阈值 = 邻域均值 - C
cv.ADAPTIVE_THRESH_GAUSSIAN_C 阈值 = 邻域高斯加权和均值 - C
blockSize决定领域大小,C是从邻域均值或加权均值中减去的常数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('ml.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
img = cv.medianBlur(img,5)

ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
cv.THRESH_BINARY,11,2)
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv.THRESH_BINARY,11,2)

titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]

for i in range(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()

大津二值化 Otsu’s Binarization
可以不需要选择一个数值作为阈值,从图像直方图确定全局最优阈值,该值通过最小化加权组内方差求得

1
2
# Otsu's thresholding
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
6. 图像平滑(卷积)

cv.filter2D()实现卷积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('ml.png')
assert img is not None, "file could not be read, check with os.path.exists()"

kernel = np.ones((5,5),np.float32)/25 # 平均kernel
dst = cv.filter2D(img,-1,kernel)

blr = cv.blur()

plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Averaging')
plt.xticks([]), plt.yticks([])
plt.show()

图像过滤模糊的一些常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('ml.png')
assert img is not None, "file could not be read, check with os.path.exists()"

kernel = np.ones((5,5),np.float32)/25
dst = cv.filter2D(img,-1,kernel)

blr = cv.blur(img,(5,5))

g_blr = cv.GaussianBlur(img, (5,5),0) # 高斯模糊:最后参数σx和σy

m_blr = cv.medianBlur(img, 5) # 有效消除椒盐噪声

bi_blr = cv.bilateralFilter(img,9, 75, 75) # 由一个空间高斯方程和一个像素差异方程组成,可以实现去纹理而存边界

plt.subplot(321),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(322),plt.imshow(dst),plt.title('Averaging')
plt.xticks([]), plt.yticks([])
plt.subplot(323),plt.imshow(blr),plt.title('Bluring')
plt.xticks([]), plt.yticks([])
plt.subplot(324),plt.imshow(g_blr),plt.title('gaussian')
plt.xticks([]), plt.yticks([])
plt.subplot(325),plt.imshow(m_blr),plt.title('medianBlur')
plt.xticks([]), plt.yticks([])
plt.subplot(326),plt.imshow(bi_blr),plt.title('Bilaterial filtering')
plt.xticks([]), plt.yticks([])

plt.show()
7. 形态学操作

腐蚀:使用一个kernel扫过二值图片每一个角落,其下所有像素都1时候保留中心位置在原图对应的像素,否则被“腐蚀”为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import cv2 as cv
import numpy as np

img = cv.imread('ml.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
kernel = np.ones((5,5),np.uint8)
erosion = cv.erode(img,kernel,iterations = 1) # 腐蚀
dilation = cv.dilate(img,kernel,iterations = 1) # 膨胀:但凡kernel下面有个数值都将kernel中心位置像素设为1

# 开运算 腐蚀后膨胀 可用于去噪
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)

# 闭运算 膨胀后腐蚀 用于消除形状内部的小孔
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)

# 梯度 膨胀与腐蚀的差异 结果看起来像图形的外轮廓
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)

# Top Hat 输入图像与开运算的差异
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)

# Black Hat 输入图像与闭运算的差异
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)

使用cv.getStructuringElement()生成结构化kernel,输入形状和尺寸元组即可

1
2
3
4
kernel = cv.getStructuringElement(cv.MORPH_RECT,(5,5))  # 矩形的
print(kernel)
cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5)) # 椭圆形的
cv.getStructuringElement(cv.MORPH_CROSS,(5,5)) # 十字形的
8. 图像梯度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('ml.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"

laplacian = cv.Laplacian(img,cv.CV_64F)
sobelx = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
sobely = cv.Sobel(img,cv.CV_64F,0,1,ksize=5)

plt.subplot(2,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,2),plt.imshow(laplacian,cmap = 'gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,3),plt.imshow(sobelx,cmap = 'gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,4),plt.imshow(sobely,cmap = 'gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])

plt.show()

特别注意:将输出的数据类型设定高一些,如cv.CV_64F等,再转为cv.CV_8U,可避免丢失信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('opencv.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"

# Output dtype = cv.CV_8U
#sobelx8u = cv.Sobel(img,cv.CV_8U,1,0,ksize=3)
sobelx8u = cv.Laplacian(img,cv.CV_8U)

# Output dtype = cv.CV_64F. Then take its absolute and convert to cv.CV_8U
#sobelx64f = cv.Sobel(img,cv.CV_64F,1,0,ksize=3)
sobelx64f = cv.Laplacian(img,cv.CV_64F)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)

plt.subplot(1,3,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,2),plt.imshow(sobelx8u,cmap = 'gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,3),plt.imshow(sobel_8u,cmap = 'gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])

plt.show()
9. 图像金字塔
1
2
3
lower_reso = cv.pyrDown(higher_reso)    # 下取样

higher_reso2 = cv.pyrUp(lower_reso) # 上取样

使用金字塔操作混合两个图片的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import cv2 as cv
import numpy as np,sys

A = cv.imread('opencv.png')
B = cv.imread('opencv_white.png')
assert A is not None, "file could not be read, check with os.path.exists()"
assert B is not None, "file could not be read, check with os.path.exists()"

# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in range(6):
G = cv.pyrDown(G)
gpA.append(G)

# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in range(6):
G = cv.pyrDown(G)
gpB.append(G)

# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in range(5,0,-1):
GE = cv.pyrUp(gpA[i])
L = gpA[i-1] - GE
lpA.append(L)

# generate Laplacian Pyramid for B
lpB = [gpB[5]]
for i in range(5,0,-1):
GE = cv.pyrUp(gpB[i])
L = gpB[i-1] - GE
lpB.append(L)

# Now add left and right halves of images in each level
LS = []
for la,lb in zip(lpA,lpB):
rows,cols,dpt = la.shape
ls = np.hstack((la[:,0:cols//2], lb[:,cols//2:]))
LS.append(ls)

# now reconstruct
ls_ = LS[0]
for i in range(1,6):
ls_ = cv.pyrUp(ls_)
ls_ = cv.add(ls_, LS[i])

# image with direct connecting each half
real = np.hstack((A[:,:cols//2],B[:,cols//2:]))

cv.imshow('Pyramid_blending2.jpg',ls_)
cv.imshow('Direct_blending.jpg',real)

cv.waitKey(0)
10. Canny 边缘检测

求梯度,边缘与梯度法线垂直,判断边缘点在梯度方向是否为局部最大值,是则保留,否则置零(非极大值抑制);下一步,小于最小值不认为是边缘,大于最大值是确定边缘,位于其间而又与确定边缘相连的也予以保留,否则舍弃。
使用cv.Canny(),输入:图像,最小值,最大值,用于算梯度的Sobel kernel 尺寸(默认3),L2gradient(True 全面公式较精确,False 默认的简化公式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('ml.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
edges = cv.Canny(img,100,180)

plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])

plt.show()
11. 轮廓检测

使用cv.findCountours()获取轮廓,输入:源图,轮廓模式,近似方法
源图只能是二值图,白色为目标黑色为背景
近似方法用cv.CHAIN_APPROX_NONE将保存轮廓所有点,而用cv.CHAIN_APPROX_SIMPLE可以将轮廓用少量数据描述,如矩形仅用4个焦点等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

# 绘制Contours或者通过提供的边界点绘图
cv.drawContours(im, contours, -1, (0,255,0), 2)
# 输入:源图,轮廓,轮廓索引(-1全部),颜色,线宽

cnt = countours[2]

M = cv.moments(cnt) # 求轮廓moments

# 求轮廓质心
c_x = M['m10'] / M['m00']
c_y = M['m01'] / M['m00']

area = cv.contourArea(cnt) # 求轮廓面积,即M['m00']

# 求轮廓周长
perimeter = cv.arcLength(cnt,True) # 输入:轮廓,轮廓是否闭合(True闭合)

# 轮廓近似,根据指定的精度要求使用更少的顶点近似轮廓。
epsilon = 0.1*cv.arcLength(cnt,True) # 精度参数:从轮廓到模拟轮廓的最大允许距离
approx = cv.approxPolyDP(cnt,epsilon,True)

凸包 Convex Hull
凸曲线都是往外凸的,只少也是平的。cv.convexHull用来修复凸性缺陷(内凹情况)

1
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]])
  • points 传入的轮廓
  • hull 输出,通常免去
  • clockwise 方向标志 True顺时针
  • returnPoints 默认True返回凸点坐标,而False返回凸点坐标对应的轮廓点索引
    实际使用hull = cv.convexHull(cnt)就行了
1
print(cv.isContourConvex(cnt))  # 检查轮廓凸性

轮廓的直边界框

1
2
x, y, w, h = cv.boundingRect(cnt)
cv.rectangle(im, (x,y),(x+w, y+h),(0,0,255),1)

轮廓的旋转边界宽
使用cv.minAreaRect()绘制轮廓的最小面积边界框,返回(中心坐标(x,y),(宽, 高),旋转角度)。传入cv.boxPoints()绘制矩形

1
2
3
4
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(im,[box],0,(0,0,255),2)

最小封闭圆
完全包括轮廓的最小圆

1
2
3
4
(x,y),radius = cv.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv.circle(im,center,radius,(0,255,0),2)

拟合椭圆

1
2
ellipse = cv.fitEllipse(cnt)
cv.ellipse(im,ellipse,(0,255,0),2)

拟合直线

1
2
3
4
5
rows,cols = img.shape[:2]
[vx,vy,x,y] = cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
12. 轮廓属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 宽高比 Aspect_ratio
x,y,w,h = cv.boundingRect(cnt)
aspect_ratio = float(w)/h

# 轮廓占边界框面积比 Extent
area = cv.contourArea(cnt)
x,y,w,h = cv.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area

# 坚固性 Solidity
area = cv.contourArea(cnt)
hull = cv.convexHull(cnt)
hull_area = cv.contourArea(hull)
solidity = float(area)/hull_area

# 当量直径 Equivalent Diameter 与轮廓面积相同圆的直径
area = cv.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)

# 方向 Orientation
(x,y),(MA,ma),angle = cv.fitEllipse(cnt)

# Mask
mask = np.zeros(imgray.shape,np.uint8)
cv.drawContours(mask,[cnt],0,255,-1)
pixelpoints = np.transpose(np.nonzero(mask))
#pixelpoints = cv.findNonZero(mask)

# 最大值最小值及其位置
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(imgray,mask = mask)

# 平均颜色或亮度
mean_val = cv.mean(im,mask = mask)

# 极值点
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])

# 多边形测试 返回点到多边形的最短距离,为正在轮廓内,为0在轮廓上,为负在轮廓外,第三参数True返回距离,False返回正负1(速度快2-3x)
dist = cv.pointPolygonTest(cnt,(50,50),True)

# 轮廓匹配 可用于OCR
ret, thresh = cv.threshold(img1, 127, 255,0)
ret, thresh2 = cv.threshold(img2, 127, 255,0)
contours,hierarchy = cv.findContours(thresh,2,1)
cnt1 = contours[0]
contours,hierarchy = cv.findContours(thresh2,2,1)
cnt2 = contours[0]

ret = cv.matchShapes(cnt1,cnt2,1,0.0)
print( ret )
13. 轮廓的层级

我们在使用查找轮廓的时候返回了一个hierarchy,即轮廓可能在另一轮廓之内的这种父子关系在opencv中的表达
** [Next, Previous, First_Child, Parent] **
Next 表示同一层级的下一个轮廓
Previous 同一层级的上一个轮廓
First_Child 第一个子轮廓
Parent 父轮廓

注:如果没有父子轮廓,该位置设为-1

轮廓检索模式
RETR_LIST直接生成所有轮廓,无父子关系
RETR_EXTERNAL只要最外轮廓
RETR_CCOMP 排成2级,外部轮廓为层级1,孔洞轮廓为层级2
RETR_TREE 完整的层级

14. 直方图

直方图的x轴是亮度,从0到255的(在8bit图像下,可以更改),纵高是每一亮度像素的总数量。直方图分析只用灰度图
直方图常用术语如下:
BINS 用来指定x轴上有多少个区间,如256,或者16
DIMS 采集数据的维度,如1
RANGE 需要采集数据的范围,通常[0,256]

使用OpenCV获取直方图
cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])

  1. images : uint8或float32的源图,用方括号包含,如 “[img]”。
  2. channels : 准备计算的通道的索引,用方括号包含,灰度图可用[0]。彩色图可用[0], [1] 或 [2] 分别计算蓝色、绿色、红色通道的直方图。
  3. mask : 掩膜图,计算全图直方图时候置”None”即可,否则创建一个mask放到这里。
  4. histSize : 用方括号包含的BINS,如 [256]。
  5. ranges : 范围,通常 [0,256]。
    1
    2
    3
    img = cv.imread('ml.png', cv.IMREAD_GRAYSCALE)
    assert img is not None, "file could not be read, check with os.path.exists()"
    hist = cv.calcHist([img],[0],None,[16],[0,256])

使用Numpy获取直方图,OpenCV比之快40x

1
hist,bins = np.histogram(img.ravel(),256,[0,256])

绘制直方图

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('bottle.png')
assert img is not None, "file could not be read, check with os.path.exists()"
color = ('b','g','r')
for i,col in enumerate(color):
histr = cv.calcHist([img],[i],None,[256],[0,256])
plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()

获取掩膜Mask内的直方图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('bottle.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"

# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[400:800, 400:1000] = 255
masked_img = cv.bitwise_and(img,img,mask = mask)

# Calculate histogram with mask and without mask
# Check third argument for mask
hist_full = cv.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv.calcHist([img],[0],mask,[256],[0,256])

plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask,'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])

plt.show()

直方图均衡化,提高图像对比度,统一光照条件

1
2
3
4
5
img = cv.imread('wiki.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
equ = cv.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side
cv.imwrite('res.png',res)

对比度有限自适应直方图均衡CLAHE
将图像分成小块做常规的直方图均衡,如果任何直方图bin超过了给定的对比度限(默认40),做直方图均衡前会将这些像素剪切均匀分散到其他bins,做完后使用双线性插值去除边界不自然。

1
2
3
4
# create a CLAHE object (Arguments are optional).
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv.imwrite('clahe_2.jpg',cl1)

2D直方图
将图像BGR转HSV,对Hue和Saturation进行绘制,还是用cv.calcHist()

  • channels = [0,1] 因为我们使用H 和 S 平面
  • bins = [180,256] 180 是 H 平面,256是 S 平面
  • range = [0,180,0,256] Hue范围从0到180,Saturation从0到256
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import numpy as np
    import cv2 as cv
    from matplotlib import pyplot as plt

    img = cv.imread('view.png')
    assert img is not None, "file could not be read, check with os.path.exists()"
    hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
    hist = cv.calcHist( [hsv], [0, 1], None, [180, 256], [0, 180, 0, 256] )

    plt.imshow(hist,interpolation = 'nearest')
    plt.show()

直方图反向投射
用于图像分割或者识别图像中物体。通过创建一个与输入同样宽高的单色图,其每个像素点代表输入图片的对应像素输入物体的概率,也就是越亮的地方有目标物体的概率越大。
方法:计算包含目标物体的图像之直方图,该图应经可能全部都是目标物体。做颜色直方图会比灰度直方图效果更佳。然后反向投射这个直方图到需要查找目标的图片,也就是计算目标图片每个像素属于目标物体图片的概率,并显示之。在合适的阈值下,可以达到将目标分割出来的目的。

1
cv.calcBackProject(	images, channels, hist, ranges, scale[, dst]	) ->	dst

用法类似与cv.calcHist()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import numpy as np
import cv2 as cv

roi = cv.imread('trees.png')
assert roi is not None, "file could not be read, check with os.path.exists()"
hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)

target = cv.imread('view.jpg')
assert target is not None, "file could not be read, check with os.path.exists()"
hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)

# calculating object histogram
roihist = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )

# normalize histogram and apply backprojection
cv.normalize(roihist,roihist,0,255,cv.NORM_MINMAX)
dst = cv.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)

# Now convolute with circular disc
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
cv.filter2D(dst,-1,disc,dst)

# threshold and binary AND
ret,thresh = cv.threshold(dst,50,255,0)
thresh = cv.merge((thresh,thresh,thresh))
res = cv.bitwise_and(target,thresh)

res = np.vstack((target,thresh,res))
cv.imwrite('res.jpg',res)
15. 图像傅里叶转换

对应正弦信号,振幅剧烈变化表示高频,缓慢变化为低频。图像类似的,边缘和噪音的亮度变化剧烈,因此算是高频信号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('view.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
f = np.fft.fft2(img) # 变换
fshift = np.fft.fftshift(f) # 将低频放到中心
magnitude_spectrum = 20*np.log(np.abs(fshift))

plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

rows, cols = img.shape
crow, ccol = rows//2, cols//2
fshift[crow-10:crow+11, ccol-10:ccol+11] = 0 # 低频删除
f_ishift = np.fft.ifftshift(fshift) # 逆偏移
img_back = np.fft.ifft2(f_ishift) # 逆变换
img_back = np.real(img_back)

plt.subplot(131),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(img_back, cmap = 'gray')
plt.title('Image after HPF'), plt.xticks([]), plt.yticks([])
plt.subplot(133),plt.imshow(img_back)
plt.title('Result in JET'), plt.xticks([]), plt.yticks([])

plt.show()

OpenCV的实现要快一些,但是没有Numpy这么直观。

16. 模板匹配

使用cv.matchTemplate() ,如果输入图像尺寸(W,H),目标图(w,h),那么输出(W-h+1,H-h+1)
使用cv.minMaxLoc()找到极值之所在作为矩形左上角坐标,结合(w,h)绘制包含目标的矩形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('view.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
img2 = img.copy()
template = cv.imread('build.jpg', cv.IMREAD_GRAYSCALE)
assert template is not None, "file could not be read, check with os.path.exists()"
w, h = template.shape[::-1]

# All the 6 methods for comparison in a list
methods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR',
'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']

for meth in methods:
img = img2.copy()
method = eval(meth)

# Apply template Matching
res = cv.matchTemplate(img,template,method)
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)

# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)

cv.rectangle(img,top_left, bottom_right, 255, 2)

plt.subplot(121),plt.imshow(res,cmap = 'gray')
plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img,cmap = 'gray')
plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
plt.suptitle(meth)

plt.show()
17. 霍夫直线变换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import cv2 as cv
import numpy as np

img = cv.imread('opencv.png')
assert img is not None, "img loading wrong"
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray,50,150,apertureSize = 3)

# 输入: 二值图,ρ精度,θ精度,阈值
lines = cv.HoughLines(edges,1,np.pi/180,100)
for line in lines:
rho,theta = line[0]
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))

cv.line(img,(x1,y1),(x2,y2),(0,0,255),2)

cv.imwrite('houghlines3.jpg',img)

霍夫圆变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
import cv2 as cv

img = cv.imread('opencv_white.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
img = cv.medianBlur(img,5)
cimg = cv.cvtColor(img,cv.COLOR_GRAY2BGR)

circles = cv.HoughCircles(img,cv.HOUGH_GRADIENT,1,20,
param1=50,param2=30,minRadius=0,maxRadius=0)

circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv.circle(cimg,(i[0],i[1]),2,(0,0,255),3)

cv.imshow('detected circles',cimg)
cv.waitKey(0)
cv.destroyAllWindows()