基于OpenCV 的图像 基本操作(二)

1,103 阅读8分钟
import numpy as np
import matplotlib.pyplot as plt
import cv2 as cv
# opencv 默认读取是BGR
mat = cv.imread('man.jpg')
# matplotlib 默认显示是RGB
mat = cv.cvtColor(mat, cv.COLOR_BGR2RGB) 
(h, w , c) = mat.shape[0:3]
print('宽:{0}\n高:{1}\n通道:{2}'.format(w, h, c))
plt.imshow(mat)
plt.show()
宽:500
高:503
通道:3

一、腐蚀 Erosion

其原理是在原图的小区域内取局部最小值。这个核也叫结构元素,因为形态学操作其实也是应用卷积来实现的,结构元素可以是矩形、椭圆、十字形。

# 矩形结构
kernel_rect = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
# print (kernel_rect)
# 椭圆结构
kernel_ellipse = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5))
# print (kernel_ellipse)
# 十字结构
kernel_cross = cv.getStructuringElement(cv.MORPH_CROSS, (5, 5))
# print (kernel_cross)
# 用矩形结构进行腐蚀
imgs = [mat]
# matplot 默认是显示RGB
plt.figure(figsize=(12,2))
for i in range(5):
    mat_rect = cv.erode(mat, kernel_rect, iterations=i+1)
    imgs.append(mat_rect)
    plt.subplot(1, 5, i+1)
    plt.imshow(imgs[i])
plt.show()

二、膨胀

膨胀与腐蚀相反,

imgs = [mat]

plt.figure(figsize=(12,2))
for i in range(5):
    mat_rect = cv.dilate(mat, kernel_rect, iterations=i+1)
    imgs.append(mat_rect)
    plt.subplot(1, 5, i+1)
    plt.imshow(imgs[i])
plt.show()

三、开运算

先腐蚀后膨胀,除去白点

# 矩形结构 区域放大些
kernel_rect = cv.getStructuringElement(cv.MORPH_RECT, (10, 10))
opening = cv.morphologyEx(mat, cv.MORPH_OPEN, kernel_rect)
imgs = [mat, opening]

plt.figure(figsize=(6,2))
for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.imshow(imgs[i])
plt.show()

四、闭运算

先膨胀在腐蚀,去除黑点

# 矩形结构
kernel_rect = cv.getStructuringElement(cv.MORPH_RECT, (4, 4))
closing = cv.morphologyEx(mat, cv.MORPH_CLOSE, kernel_rect)
imgs = [mat, closing]

plt.figure(figsize=(6,2))
for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.imshow(imgs[i])
plt.show()

五、先开运算在闭运算

kernel_rect = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
opening = cv.morphologyEx(mat, cv.MORPH_OPEN, kernel_rect)
closing = cv.morphologyEx(opening, cv.MORPH_CLOSE, kernel_rect)
imgs = [mat, opening, closing]

plt.figure(figsize=(8,2))
for i in range(3):
    plt.subplot(1, 3, i+1)
    plt.imshow(imgs[i])
plt.show()

六、Grandient 形态学梯度

膨胀图减去腐蚀图,得到图像的轮廓

kernel_rect = cv.getStructuringElement(cv.MORPH_RECT, (4, 4))
grandient = cv.morphologyEx(mat, cv.MORPH_GRADIENT, kernel_rect)
imgs = [mat, grandient]
plt.figure(figsize=(6,2))
for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.imshow(imgs[i])
plt.show()

七、白帽

原图减去 开运算的图。即获取被去除的白色点。

kernel_rect = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
topHat = cv.morphologyEx(mat, cv.MORPH_TOPHAT, kernel_rect)
imgs = [mat, topHat]
plt.figure(figsize=(6,2))
for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.imshow(imgs[i])
plt.show()

八、黑帽

原图 减去 闭运算。即获取被去除的黑色点

kernel_rect = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
blackHat = cv.morphologyEx(mat, cv.MORPH_BLACKHAT, kernel_rect)
imgs = [mat, blackHat]
plt.figure(figsize=(6,2))
for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.imshow(imgs[i])
plt.show()

九、图像平滑

  • Averaing 平均 计算卷积框覆盖区域所有像素的平均值得到卷积的结果
# opencv 默认读取是BGR
mat = cv.imread('face.jpeg')
# matplotlib 默认显示是RGB
mat = cv.cvtColor(mat, cv.COLOR_BGR2RGB) 
kernelsizes = [(3, 3), (15, 15), (35, 35)]
plt.figure(figsize=(15, 15))
plt.subplot(1, 4, 1)
plt.imshow(mat)
for i, kernel in enumerate(kernelsizes):
    plt.subplot(1, 4, i+2)
    # 平均平滑
    blur = cv.blur(mat, kernel)
    plt.imshow(blur)
plt.show()

  • Gaussian 高斯模糊 现在把卷积核换成高斯核,核内符合高斯分布,方框中心的值最大,其余方框根据距离中心元素的递减。构成一个高斯小山包,求加权平均。
kernelsizes = [(3, 3), (15, 15), (35, 35)]
plt.figure(figsize=(15, 15))
plt.subplot(1, 4, 1)
plt.imshow(mat)
for i, kernel in enumerate(kernelsizes):
    plt.subplot(1, 4, i+2)
    # 高斯模糊
    blur = cv.GaussianBlur(mat, kernel, 5)
    plt.imshow(blur)
plt.show()

  • Median 中值模糊 卷积框对应的中值来替代中心像素的值。
plt.figure(figsize=(15, 15))
plt.subplot(1, 4, 1)
plt.imshow(mat)
for i, kernel in enumerate((3, 15, 35)):
    plt.subplot(1, 4, i+2)
    # 中值模糊
    blur = cv.medianBlur(mat, kernel, 5)
    plt.imshow(blur)
plt.show()

  • Bilateral 双边滤波

能在保持边界清晰的情况下有效的去除噪音。 不仅考虑像素点之间的空间关系,而且还会考虑像素值之间的关系(像素相似度)。

# 定义三组数据  diameter 定义半径
params = [(11, 20, 7), (15, 40, 30), (40, 40, 30)]
plt.figure(figsize=(15, 15))
plt.subplot(1, 4, 1)
plt.imshow(mat)
# 领域直径,灰度值相似性高斯函数标准差,空间高斯函数标准差
for i, (diameter, sigmaColor, sigmaSpace) in enumerate(params):
    print('{0},{1},{2}'.format(diameter, sigmaColor, sigmaSpace))
    plt.subplot(1, 4, i+2)
    # 双边滤波
    blur = cv.bilateralFilter(mat, diameter, sigmaColor, sigmaSpace)
    plt.imshow(blur)
plt.show()
11,20,7
15,40,30
40,40,30

十、图像二值化

  • THRESH_BINARY

    大于阈值 变成最大值 小于阈值变为0

  • THRESH_BINARY_INV

    和 THRESH_BINARY 相反

  • THRESH_TRUNC

    大于阈值,变成和阈值一样的值,小于阈值不变

  • THRESH_TOZERO

    大于阈值,变为0 ,小于阈值不变

  • THRESH_TOZERO_INV

    大于阈值,不变,小于阈值变为0

mat = cv.imread('money.jpg')
mat = cv.cvtColor(mat, cv.COLOR_BGR2RGB) 
plt.figure(figsize=(15, 15))
plt.subplot(1, 7, 1)
plt.title('original')
plt.imshow(mat)
gray = cv.cvtColor(mat, cv.COLOR_RGB2GRAY)
plt.subplot(1, 7, 2)
plt.title('gray')
plt.imshow(gray, cmap='gray')
ret = 127
# 127 阈值, 255 最大值
ret1,thresh1 = cv.threshold(gray, ret, 255, cv.THRESH_BINARY)
ret2,thresh2 = cv.threshold(gray, ret, 255, cv.THRESH_BINARY_INV)
ret3,thresh3 = cv.threshold(gray, ret, 255, cv.THRESH_TRUNC)
ret4,thresh4 = cv.threshold(gray, ret, 255, cv.THRESH_TOZERO)
ret5,thresh5 = cv.threshold(gray, ret, 255, cv.THRESH_TOZERO_INV)
titles = ['THRESH_BINARY', 'THRESH_BINARY_INV', 'THRESH_TRUNC', 'THRESH_TOZERO', 'THRESH_TOZERO_INV']
imgs = [thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(5):
    plt.subplot(1, 7 , i+3)
    plt.title(titles[i])
    plt.imshow(imgs[i],cmap='gray')
plt.show()

  • 自动选择阈值
gray = cv.cvtColor(mat, cv.COLOR_RGB2GRAY)
ret1,thresh1 = cv.threshold(gray, 50, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
print('阈值:{0}'.format(ret1))
ret2,thresh2 = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV  | cv.THRESH_OTSU)
print('阈值:{0}'.format(ret2))
阈值:133.0
阈值:133.0

 自适应阈值

前面的部分我们使用全局阈值,但是当图像上的不同部分具体不同亮度时候效果就不是很好。这种情况下我们需要采用自适应阈值,

此时阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一副图像上的不同区域采用的是不同阈值。

Aapative Method - 指定计算阈值的方法:

  1. ADPTIVE_THRESH_MEAN_C

    阈值取自相邻区域的平均值

  2. ADPTIVE_THRESH_GAUSSIAN_C

    阈值取值相邻区域的加权和,权重为一个高斯窗口。

其它参数:

  1. Block Size -相邻大小(用来计算阈值区域大小)

  2. C 这个是一个常数,阈值就等于平均值或者加权平均值减去这个常数。

plt.figure(figsize=(15, 8))
plt.subplot(2, 3, 1)
plt.title('original')
plt.imshow(mat)

plt.subplot(2, 3, 2)
plt.title('gray')
plt.imshow(gray, cmap='gray')

# 中值滤播
median = cv.medianBlur(gray, 5)
plt.subplot(2, 3, 3)
plt.title('median')
plt.imshow(median, cmap='gray')

# 普通二值化
ret1,th1 = cv.threshold(median, 127, 255, cv.THRESH_BINARY)
plt.subplot(2, 3, 4)
plt.title('THRESH_BINARY_N')
plt.imshow(th1, cmap='gray')

# 平均值阈值
th2 = cv.adaptiveThreshold(median, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 5, 3)
plt.subplot(2, 3, 5)
plt.title('ADAPTIVE_THRESH_MEAN_C')
plt.imshow(th2, cmap='gray')

# 高斯阈值
th3 = cv.adaptiveThreshold(median, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 5, 2)
plt.subplot(2, 3, 6)
plt.title('ADAPTIVE_THRESH_GAUSSIAN_C')
plt.imshow(th3, cmap='gray')

plt.show()

十一、图片颜色空间

  • RGB
mat = cv.imread('man.jpg')
mat = cv.cvtColor(mat, cv.COLOR_BGR2RGB) 
(R, G, B) = cv.split(mat)
zeros = np.zeros(mat.shape[:2], dtype='uint8')
plt.figure(figsize=(15, 15))
plt.subplot(1, 4, 1)
plt.imshow(mat)
for i in range(3):
    plt.subplot(1, 4, i+2)
    # 平均平滑
    merge = cv.merge([R if i==0 else zeros, G if i==1 else zeros, B if i==2 else zeros])
    plt.imshow(merge)
plt.show()

  • HSV HSV 是一种比较直观的颜色视图,HSV颜色空间可以更好的数字化处理颜色,这个模型中颜色参数分别是: 色调(H,Hue) 范围 : 0-360 (0:红色)(60:黄色)(120:绿色)(180:青色)(240:蓝色)(300:品红) 饱和度(S,Saturation),颜色接近光谱色的程度,光谱色白色成分为0 范围 0-100 值越大越饱和 明度(V,Value)表示颜色的明亮程度。取值 0为黑 100 为白
hsv = cv.cvtColor(mat, cv.COLOR_RGB2HSV)
zeros = np.zeros(mat.shape[0:2], dtype='uint8')
plt.figure(figsize=(15, 15))
plt.subplot(1, 4, 1)
plt.title('m')
plt.imshow(mat)
i = 2
for (name,chan) in zip(('H', 'S', 'V'), cv.split(hsv)):
    global i
    plt.subplot(1, 4, i)
    i = i + 1
    plt.title(name)
    plt.imshow(chan)
plt.show()    

  • Lab

L 表示颜色的明度 a 正值表示红色,负值表示绿色 b 正值表示黄色,负值表示蓝色

Lab = cv.cvtColor(mat, cv.COLOR_RGB2LAB)
zeros = np.zeros(mat.shape[0:2], dtype='uint8')
plt.figure(figsize=(15, 15))
plt.subplot(1, 4, 1)
plt.title('m')
plt.imshow(mat)
i = 2
for (name,chan) in zip(('L', 'A', 'B'), cv.split(Lab)):
    global i
    plt.subplot(1, 4, i)
    i = i + 1
    plt.title(name)
    plt.imshow(chan)
plt.show() 

  • Grayscale

灰阶图像就是黑白图片,通过调节灰度值来显示影像

gray = cv.cvtColor(mat, cv.COLOR_RGB2GRAY)
plt.figure(figsize=(8, 4))
plt.subplot(1, 2, 1)
plt.imshow(mat)
plt.subplot(1, 2, 2)
plt.imshow(gray, cmap='gray')
plt.show()

十二、图像梯度

像素点位置

x-1,y-1 x,y-1 x+1,y-1
x-1,y x,y x+1,y
x-1,y+1 x,y+1 x+1,y+1

I 指的是图像像素值

一阶导数: x的梯度 Gx = I(x+1,y)-I(x,y)

y的梯度 Gy = I(x,y+1) - I(x,y)

二阶导数: x的梯度: Gx = I(x+1,y)+I(x-1,y) - 2I(x,y)

y的梯度: Gy = I(x,y+1)+I(x,y-1)-2I(x,y)

梯度大小和方向:

G = \sqrt{({G}_{x}^2 + {G}_{y}^2)}

\theta = tan^{-1}(\frac{{G}_{x}}{{G}_{y}})

Opencv 提供了三种不同梯度滤波器:Sobel,Scharr,Lapiacian

mat = cv.imread('car.jpg')
mat = cv.cvtColor(mat, cv.COLOR_BGR2RGB) 
gray = cv.cvtColor(mat, cv.COLOR_RGB2GRAY)
# 拉普拉斯算子
# CV_64F 输入出图像的深度(数据类型),64位float类型,因为梯度可能是正也可能是负
laplacian = cv.Laplacian(gray, cv.CV_64F)
# Sobel 算子
# 1,0 表示在x方向求一阶导数,最大可以求2阶导数
sobelx = cv.Sobel(gray,cv.CV_64F,1, 0, ksize=3)
# 0,1 表示在y方向求一阶导数,最大可以求2阶导数
sobely = cv.Sobel(gray,cv.CV_64F,0, 1, ksize=3)
imgs = [mat, gray, laplacian, sobelx, sobely]
titles = ['Original', 'gray', 'laplacian', 'sobelx', 'sobely']
plt.figure(figsize=(15, 5))
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(imgs[i], cmap='gray')
    plt.title(titles[i])
plt.show()

十三、Canny边缘检测

1.噪声去除

由于边缘检测很容易受到噪声影响,所以第一步是使用高斯滤波器去除噪声。

2.计算图像梯度

对平滑后的图像使用Sobel算子计算水平方向和垂直方向的梯度。

3.非极大值抑制

在获取梯度方向和大小后,应该对整幅图像做个扫描,去除那些非边界上的点,对每个像素进行检查,在看这个点的梯度是不是周围具有相同梯度方向的点中最大的。

4.滞后阈值

现在要确定那些边界才是真正的边界。这时我们需要设定两个阈值:minValue 和 maxValue,即高于maxVlue 被认为是边界,小于minValue 抛弃。
# Canny 函数 
# 参数一 输入图像
# 参数二 minVal
# 参数三 maxValue
# 参数四 Sobel算子 卷积核大小
# 参数五 设定求梯度大小的公式
edges = cv.Canny(gray, 50, 150)
imgs = [mat, edges]
titles = ['Original', 'Canny']
plt.figure(figsize=(16, 16))
for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.imshow(imgs[i], cmap='gray')
    plt.title(titles[i])
plt.show()

# 自动确定阈值的方法
def auto_canny(image, sigma=0.33):
    v = np.median(image)
    lower = int(max(0, (1-sigma)*v))
    upper = int(min(255, (1+sigma)*v))
    return (lower, upper)
    
    
(minValue, maxValue) = auto_canny(gray, sigma=0.1)
print('minValue:{0},maxValue:{1}'.format(minValue, maxValue))
edges = cv.Canny(gray, minValue, maxValue)
imgs = [mat, edges]
titles = ['Original', 'Canny']
plt.figure(figsize=(16, 16))
for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.imshow(imgs[i], cmap='gray')
    plt.title(titles[i])
plt.show()
print(edges)
minValue:207,maxValue:254