OpenCV图像处理(二)

OpenCV图像处理(二)

颜色空间转换

1.转换颜色空间

cv2.cvtColor(input_image, flag):flag是转换类型。

  • cv2.COLOR_BGR2GRAY:BGR->Gray
  • cv2.COLOR_BGR2HSV:BGR->HSV

OpenCV的HSV格式中:H(色彩/色度)的取值范围是[0, 179];S(饱和度)的取值范围是[0, 255];V(亮度)的取值范围是[0, 255]。其他软件可能不同,所以在对比的时候,需要归一化。

2.物体跟踪

提取蓝色物体的步骤:

  • 获取图像
  • 将图像转换到HSV空间
  • 设置HSV阈值到蓝色范围
  • 获取蓝色物体。还在蓝色物体周围画圈
import cv2
import numpy as np
#加载图像
img2 = cv2.imread("/Users/~~~/logo.jpg")
#BGR-->HSV
img2hsv = cv2.cvtColor(img2, cv2.COLOR_BGR2HSV)
# 设定蓝色物体的阈值
lower_blue = np.array([110,50,50])
upper_blue=np.array([130,255,255])
# 根据阈值构建掩模
mask=cv2.inRange(img2hsv,lower_blue,upper_blue)
# 对原图像和掩模进行位运算
res=cv2.bitwise_and(img2,img2,mask=mask)
显示图像
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
res

3.找到要跟踪的HSV值

blue =np.uint8([[[0,255,0]]])
hsv_blue=cv2.cvtColor(blue, cv2.COLOR_BGR2HSV)
print(hsv_blue)#[[[120,255, 255]]]
# 设定蓝色物体的阈值
H = hsv_blue[0][0][0]
print(H)#120
lower_blue = np.array([H-10,50,50])
upper_blue=np.array([H+10,255,255])
# 根据阈值构建掩模
mask=cv2.inRange(hsv_blue,lower_blue,upper_blue)
# 对原图像和掩模进行位运算
res=cv2.bitwise_and(img2, img2, mask=mask)
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
#结果和上图一样

几何变换

1.扩展缩放

cv2.resize():改变图像的尺寸大小。

  • cv2.INTER_AREA:缩小;
  • cv2.INTER_LINEAR或者cv2.INTER_CUBIC(运行速度慢):扩展。
res = cv2.resize(img1, None, fx=2, fy =2, interpolation = cv2.INTER_LINEAR)
# or
height,width = img1.shape[:2]
res = cv2.resize(img1, (width//2, height//2), interpolation=cv2.INTER_LINEAR)

2.平移和旋转

  • cv2.warpAffine(img,H,(cols,rows)):平移。img,需要的图像,H变换矩阵,最后一个参数是变换后的大小
  • cv2.getRotationMatrix2D(cols,rows),45,0.6):旋转。第一个参数旋转中心,第二个旋转角度,第三个缩放因子。
rows,cols=img1.shape[:2] 
M=cv2.getRotationMatrix2D((cols/2,rows/2),90,0.6)#旋转90度
dst=cv2.warpAffine(img,M,(2*cols,2*rows)) 
cv2.imshow('img1',dst)
waitKey(0)
cv2.destroyAllWindows()

3.仿射变换

原图中所有平行线在结果图中同样平行。

import cv2
from matplotlib import pyplot as plt
import numpy as np
# 加载图像
img2 = cv2.imread("/Users/~~~/logo.jpg")
rows, cols, ch = img.shape
pst1 = np.float32([[50, 50], [200, 50], [20, 200]])
pst2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv2.getAffineTransform(pst1, pst2)
dst = cv2.warpAffine(img, M, (cols, rows))
plt.subplot(121)
plt.imshow(img)
plt.title('Original')
plt.subplot(122), plt.imshow(dst), plt.title('Output')
plt.show()

仿射变换结果图

4.图像透视

视角变换,变换前是直线变换后还是直线
cv2.getPerspectiveTransform()

# -*- coding:utf-8 -*-
import cv2
from matplotlib import pyplot as plt
import numpy as np
# 加载图像
img = cv2.imread("/Users/~~~/number.jpg")
rows, cols, ch = img.shape
print(img.shape)
#选取四个顶点坐标
pst1 = np.float32([[400, 600], [2300,550], [200, 1720], [2530, 1650]])
#新图的四个顶点
pst2 = np.float32([[0, 0], [400, 0], [0, 300], [400, 300]])
M = cv2.getPerspectiveTransform(pst1, pst2)
dst = cv2.warpPerspective(img, M, (400, 300))
plt.subplot(121)
plt.imshow(img)
plt.title('Original')
plt.subplot(122), plt.imshow(dst), plt.title('Output')
plt.show()

结果:
在这里插入图片描述

图像阈值

1.简单阈值

cv2.threshhold(img,a,b,w):像素高于某个值是赋予一个新值(可能是白色),否则赋予另外一个颜色。

  • img:原图像;
  • a:图像进行分类的阈值;
  • b:高于(或小于)这个阈值被赋予的新的像素值;
  • w:阈值方法
    • cv2.THRESH_BINARY:二值阈值化
    • cv2.THRESH_BINARY_INV:反向二值阈值化
    • cv2.THRESH_TRUNC:截断阈值化
    • cv2.THRESH_TOZERO:超过阈值被置为0
    • cv2.THRESH_TOZERO_INV:低于阈值被置为0
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("/Users/~~/flower.jpg",0)
print(img)
ret,thresh1=cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2=cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3=cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4=cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5=cv2.threshold(img,127,255,cv2.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')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

结果:
全局阈值

2.自适应阈值

全局阈值:整幅图像采用同一个数作为阈值。
自适应阈值:此时的阈值是根据图像上的 每一个小区域计算与其对应的阈值。

参数:

  • Adaptive Method:指定计算阈值方法
    • cv2.ADPTIVE_THRESH_MEAN_C:阈值取自相邻区域的平均值
    • cv2.ADPTIVE_THRESH_GAUSSIAN_C:阈值取值相邻区域的加权和,权重为一个高斯窗口。
  • Block Size:领域的大小;
  • C:常数。阈值等于平均值或加权平均值减去这个常数。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("/Users/~~/gray.jpg", 0)
# 中值滤波
img = cv2.medianBlur(img,5)
ret,thresh1=cv2.threshold(img,127,255,cv2.THRESH_BINARY)
#Block Size = 11,C = 2
thresh2 = cv2.adaptiveThreshold(img,255, cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY, 11, 2)
thresh3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY, 11, 2)
titles = ['Original Image','Global Thresholding (v = 127)','Adaptive Mean Thresholding','Adaptive Gaussian Thresholding']
images = [img, thresh1, thresh2, thresh3]
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()

结果:
自适应阈值

3.Otsu’s 二值化

即对 一副双峰图像自动根据其直方图计算出一个阈值。(对于非双峰图像,这种方法 得到的结果可能会不理想)

  • 例1:全局阈值
  • 例2:Otsu二值化
  • 例3:先用5*5高斯核去噪音,再用Otsu二值化
import cv2
from matplotlib import pyplot as plt
img = cv2.imread("/Users/~~/noisy.jpg", 0)
# 全局阈值
ret, thresh1=cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
#Otsu二值化
ret2, thresh2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# 先去噪再Otsu二值化
# (5,5)为高斯核的大小,0为标准差
blur = cv2.GaussianBlur(img, (5, 5), 0)
res3, thresh3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# plot all the images and their histograms
images = [img, 0, thresh1,
         img, 0, thresh2,
         blur, 0, thresh3,]
titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v = 127)',
          'Original Noisy Image', 'Histogram', "Otsu's Thresholding ",
          'Original filtered Image', 'Histogram', "Otsu's Thresholding",]
# 这里使用了 pyplot 中画直方图的方法,plt.hist, 要注意的是它的参数是一维数组
# 所以这里使用了(numpy)ravel 方法,将多维数组转换成一维,也可以使用 flatten 方法 #ndarray.flat 1-D iterator over an array.
# ndarray.flatten 1-D array copy of the elements of an array in row-major order.
for i in range(3):
#第一行图
    plt.subplot(3, 3, i*3+1)
    plt.imshow(images[i*3], 'gray')
    plt.title(titles[i*3])
    plt.xticks([])
    plt.yticks([])
 
    plt.subplot(3, 3, i*3+2)
    plt.hist(images[i*3].ravel(), 256)
    plt.title(titles[i*3+1])
    plt.xticks([]),
    plt.yticks([])
#第三行图  
    plt.subplot(3, 3, i*3+3)
    plt.imshow(images[i*3+2], 'gray')
    plt.title(titles[i*3+2])
    plt.xticks([])
    plt.yticks([])
plt.show()

结果:
Otsu's

图像平滑(模糊)

可以对2D图像实施低通滤波(LPF),高通滤波(HPF)等

  • LPF:去除噪音,模糊图像;四种模糊图像的方法
  • HPF:找到图像的边缘。

1. 平均

用卷积框覆盖所有像素的平均值来代替中心元素。

  • cv2.blur(K):需要归一化的卷积框K。
  • cv2.boxFilter():可以不适用卷积框。normalize = False
import cv2
from matplotlib import pyplot as plt
# 加载图像
img2 = cv2.imread("/Users/~~/logo.jpg")
blur = cv2.blur(img2, (5,5))
plt.subplot(121), plt.imshow(img2)
plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(blur), plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

2.高斯模糊

  • 把卷积核换成高斯核。可以有效的从图像中去除高斯噪音。
  • cv2.GaussianBlur():需要指定高斯核的宽和高(必须是奇数)。以及高斯函数沿 X,Y 方向的标准 差。如果我们只指定了 X 方向的的标准差,Y 方向也会取相同值。如果两个标准差都是 0,那么函数会根据核函数的大小自己计算。
  • 也可用cv2.getGaussianKernel()自己构建高斯核
#0 是指根据窗口大小(5,5)来计算高斯函数标准差 
blur = cv2.GaussianBlur(img,(5,5),0)

3.中值模糊

  • 卷积框对应像素的中值来替代中心像素的值;
  • 除椒盐噪声;

给图像加椒盐噪声

import cv2
from matplotlib import pyplot as plt
# 加载图像
img2 = cv2.imread("/Users/~~/logo.jpg")
#加椒盐噪声
def SaltAndPepper(src,percetage):  
    SP_NoiseImg=src 
    SP_NoiseNum=int(percetage*src.shape[0]*src.shape[1]) 
    for i in range(SP_NoiseNum): 
        randX=random.random_integers(0,src.shape[0]-1) 
        randY=random.random_integers(0,src.shape[1]-1) 
        if random.random_integers(0,1)==0: 
            SP_NoiseImg[randX,randY]=0 
        else: 
            SP_NoiseImg[randX,randY]=255 
    return SP_NoiseImg
grayImage = cv2.cvtColor(srcImage,cv2.COLOR_BGR2GRAY) #灰度变换 
SaltAndPepper_noiseImage = SaltAndPepper(grayImage,0.1)

中值模糊

median = cv2.medianBlur(SaltAndPepper_noiseImage, 5)

结果:
中值模糊

4.双边滤波

cv2.bilateralFilter(): 在保持边界清晰的情况下有效地去除噪音。但是与其他滤波器相比会比较慢。

blur = cv2.bilateralFilter(img,9,75,75)

形态学转换

形态学转换是根据图像形状进行的简单操作。
操作对象一般是二值化图像。
需要两个参数:原始图像和结构化元素(核)。
基本的操作是腐蚀和膨胀。
延伸的有开运算、闭运算以及梯度等,

1.腐蚀

把前景图像的边界腐蚀掉,但是前景仍是白色。
原理:卷积和沿着图像滑动,如果卷积核对应的原图像的所有像素值都是1,那么中心元素就保持原来的像素值,否则就变为零。
用处:可去除白噪声

import numpy as np
import cv2
img = cv2.imread('"/Users/~~/logo.jpg"',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)

结果:左边是原题,右边是结果图

2.膨胀

原理:与腐蚀相反,与卷积核对应的原图像的像素只要有一个是1,中心元素的像素值就是1。
效果:会增加白色的区域
用法:去噪声时先用腐蚀再用膨胀。也可用于连接两个分开的物体。

dilation = cv2.dilate(img, kernel, iterations=1)

结果:

3.开运算

先进行腐蚀再进行膨胀就叫开运算。

opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

结果:

4.闭运算

先膨胀再腐蚀。常被用来填充前景物中的小洞,或者小黑点。

closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

结果:

5.形态学梯度

一副图像膨胀与腐蚀的差别
结果看上去就像前景物体的轮廓

gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

结果:

6.礼帽

原始图像与进行开运算之后得到的图像的差

tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)

7.黑帽

进行闭运算之后得到的图像与原始图像的差

blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

PS:各形态学之间的关系

  • 开运算:先腐蚀,再膨胀
  • 闭运算:先膨胀,再腐蚀
  • 梯度:膨胀 - 腐蚀
  • 礼帽:原图 - 开运算
  • 黑帽:闭运算 - 原图

8.结构化元素

在前面的例子中我们使用 Numpy 构建了结构化元素,它是正方形的。但 有时我们需要构建一个椭圆形/圆形的核。为了实现这种要求,提供了 OpenCV 函数 cv2.getStructuringElement()。

#正方形核 
>>> cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
array([[1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1]], dtype=uint8)
#椭圆形核
>>> cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5)) 
array([[0, 0, 1, 0, 0],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [0, 0, 1, 0, 0]], dtype=uint8)
#十字交叉形核(Cross-shaped Kernel)
>>> cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5)) 
array([[0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0],
       [1, 1, 1, 1, 1],
       [0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0]], dtype=uint8)

图像梯度

图像梯度简单来说就是求导:

  • Sobel:高斯平滑与微分操作的结合体。抗噪声能力好。
  • Scharr:是Sobel卷积核为-1时使用。和sobel速度相同但效果更好。
  • Laplacian: 拉普拉斯算子,可假设其离散实现类似于二阶Sobel导数。

对同一副图像用三种滤波器进行操作。使用的卷积核都是5*5的。

#!/usr/bin/env python 
# -*- coding:utf-8 -*-
import cv2
from matplotlib import pyplot as plt
# 加载图像
img = cv2.imread("/Users/~~~/img.png",0)
# cv2.CV_64F 输出图像的深度(数据类型),可以使用-1, 与原图像保持一致 np.uint8
laplacian = cv2.Laplacian(img, cv2.CV_16U)
# 参数 1,0 为只在 x 方向求一阶导数,最大可以求 2 阶导数。
sobelx=cv2.Sobel(img, cv2.CV_16U, 1, 0, ksize=5)
# 参数 0,1 为只在 y 方向求一阶导数,最大可以求 2 阶导数。
sobely=cv2.Sobel(img, cv2.CV_16U, 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()

结果:
图像梯度

PS:为了不丢边界,输出的数据类型要比原图像要高。

Canny边缘检测

1.原理

Canny边缘建的是一种很流行的边缘检测算法。是一个很多步构成的算法,步骤如下:

  • 噪声去除:用5*5 高斯滤波器去除噪声;
  • 计算图像梯度:用Sobel算子计算水平和竖直方向的一阶导数。
  • 非极大值抑制:对整幅图像做一个扫描,去除那些非边界上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯 度方向的点中最大的。
  • 滞后阈值:确定真正的边界。当图像的灰度梯度高于 maxVal 时被认为是真的边界, 那些低于 minVal 的边界会被抛弃。如果介于两者之间的话,就要看这个点是 否与某个被确定为真正的边界点相连,如果是就认为它也是边界点,如果不是 就抛弃。

2.实现

cv2.Canny(img, minVal, maxVal, kernel, L2gradient):

  • kernel:卷积核大小,默认值是3;
  • 最后一个参数用来设定求梯度大小的方程。
#!/usr/bin/env python 
# -*- coding:utf-8 -*-
import cv2
from matplotlib import pyplot as plt
# 加载图像
img = cv2.imread("/Users/~~~/img.png",0)
edges = cv2.Canny(img, 100, 200)
plt.subplot(1,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 2, 2),plt.imshow(edges,cmap = 'gray')
plt.title('edges'), plt.xticks([]), plt.yticks([])
plt.show()

结果:
canny边缘检测

猜你喜欢

转载自blog.csdn.net/weixin_42902413/article/details/86691521