Opencv16_21章,学习笔记

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import cv2

16.平滑处理,模糊处理

使用低通滤波器可以达到图像模糊的目的。这对与去除噪音很有帮助。其实就是去除图像中的高频成分

自定义滤波器

img = cv2.imread("001.PNG")
cv2.imshow("img1",img)
cv2.imshow("img2",dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

kernel = np.ones((5,5),np.float32) / 25
dst = cv2.filter2D(img,-1,kernel)
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()

png

dst.shape
(312, 600, 3)
img.shape
(312, 600, 3)

平均

归一化卷积框,与上面的例子相似

blur = cv2.blur(img,(5,5))#利用5 * 5的卷积核进行优化

高斯模糊

构建高斯核,需要输入高宽,以及x,y的标准差

blur = cv2.GaussianBlur(img,(5,5),0)# 0表示标准差由函数自己定义

中值模糊

卷积框对应像素的中值来替代中心像素的值,常用来去除椒盐噪声

blur = cv2.medianBlur(img,5)

双边滤波(双重高斯,厉害)

在保持边界清晰的情况下有效的去除噪音。但是这种操作与其他滤波器相比会比较慢。
双边滤波在同时使用空间高斯权重和灰度值相似性高斯权重,灰度值相似性高斯函数确保只有
与中心像素灰度值相近的才会被用来做模糊运算。

blur = cv2.bilateralFilter(img,9,75,75)#9 邻域直径,两个75 分别是空间高斯函数标准差,灰度值相似性高斯函数标准差
cv2.imshow("img1",img)
cv2.imshow("img2",blur)
cv2.waitKey(0)
cv2.destroyAllWindows()

17.形态学转换

一般情况下对二值化图像进行的操作。
只有0和1才能进行相关操作计算

腐蚀

卷积核沿着图像滑动,如果与卷积核对应的原图像的所有像素值都是1,那么中心元素就保持原来的像素值,否则就变为零。

img1 = cv2.imread("001.PNG",0)
kernel = np.ones((3,3),np.uint8) * 255
erosion = cv2.erode(th_img1,kernel,iterations = 1)

膨胀

卷积核对应的原图像的像素值中只要有一个是1,中心元素的像素值就是1

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

开运算

先进性腐蚀再进行膨胀就叫做开运算。被用来去除噪声

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

闭运算

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

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

形态学梯度

就是一幅图像膨胀与腐蚀的差别,看上去就像前景物体的轮廓

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

礼帽

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

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

黑帽

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

blackhat = cv2.morphologyEx(img1, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow("img1",th_img1)
cv2.imshow("img2",tophat)
cv2.waitKey(0)
cv2.destroyAllWindows()
ret1, th_img1 = cv2.threshold(img1, 200, 255, cv2.THRESH_BINARY)

结构化元素

构建一个椭圆形/圆形的核

# Rectangular Kernel
cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
# Elliptical Kernel
cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
# 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)
img2 = np.ones((300,300),np.uint8)
cv2.imshow("img2",img2 * 150)
cv2.waitKey(0)
cv2.destroyAllWindows()

总结:

关于灰度图:
0-255表示的是不同灰度,0表示纯黑,255表示白色,150表示灰度

总结:

高斯平滑与微分操作的结合体,所以它的抗噪声能力很好,这句话很有意思,即每个核都有抗噪能力,只是大小,由核本身的原因,

问题:

对于二值图的核都是1,而二值图都是255,那么它的核应该是255吗

18.图像梯度

OpenCV 提供了三种不同的梯度滤波器,或者说高通滤波器:Sobel,
Scharr 和Laplacian。我们会意义介绍他们。
Sobel,Scharr 其实就是求一阶或二阶导数。Scharr 是对Sobel(使用
小的卷积核求解求解梯度角度时)的优化。Laplacian 是求二阶导数

img=cv2.imread('001.PNG',0)
#cv2.CV_64F 输出图像的深度(数据类型),可以使用-1, 与原图像保持一致np.uint8
laplacian=cv2.Laplacian(img,cv2.CV_64F)
# 参数1,0 为只在x 方向求一阶导数,最大可以求2 阶导数。
sobelx=cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
# 参数0,1 为只在y 方向求一阶导数,最大可以求2 阶导数。
sobely=cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)
plt.figure(figsize=(12, 10))
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()

png

当我们可以通过参
数-1 来设定输出图像的深度(数据类型)与原图像保持一致,但是我们在代
码中使用的却是cv2.CV_64F。如果原图像的深度是
np.int8 时,所有的负值都会被截断变成0,换句话说就是把把边界丢失掉。
所以如果这两种边界你都想检测到,最好的的办法就是将输出的数据类型
设置的更高,比如cv2.CV_16S,cv2.CV_64F 等。取绝对值然后再把它转回
到cv2.CV_8U。

总结:

特别注意:对于求导后输出的数值,需要比原来的数值范围大,否则如果用-1的默认,那么在边界上会少了负值的那一部分

19.Canny 边缘检测

1.噪声去除
2.计算图像梯度
3.非极大值抑制
4.滞后阈值

img = cv2.imread('001.PNG',0)
edges = cv2.Canny(img,100,200)
plt.figure(figsize=(12, 10))
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()

png

问题:

非极大值抑制的理解

20.图像金字塔

需要对同一图像的不同分辨率的子图像进行处理。们需要创建创建一组图像,这些图像是具有不同分辨率的原始图像

1.高斯金字塔 和 拉普拉金字塔

Li = Gi – PyrUp (Gi+1)
拉普拉斯金字塔可以有高斯金字塔计算
拉普拉金字塔的图像看起来就像边界图,其中很多像素都是0。他们经常
被用在图像压缩中。

img = cv2.imread('001.PNG')
lower_reso = cv2.pyrDown(lower_reso)#四分之一
higher_reso2 = cv2.pyrUp(img)#四倍
height,width  = img.shape[:2]
res=cv2.resize(img,(2*width,2*height),interpolation=cv2.INTER_CUBIC)#通过其他方式进行缩放
cv2.imshow("img",img)
cv2.imshow("img1",higher_reso2)
cv2.imshow("img2",res)
cv2.waitKey(0)
cv2.destroyAllWindows()

问题:

img2(resize)的放缩比img1(pyrUp)的放缩更加清晰,pyrUp的填充方式是什么

2 使用金字塔进行图像融合

高斯金字塔计算拉普拉斯金字塔,每一层进行融合

A = cv2.imread("apple.jpg")
B = cv2.imread("orange.jpg")

#构建高斯金字塔
G = A.copy()
gpA = [G]
for i in range(6):
    G = cv2.pyrDown(G)
    gpA.append(G)

G = B.copy()
gpB = [G]
for i in range(6):
    G = cv2.pyrDown(G)
    gpB.append(G)


i = 1
GE = cv2.pyrUp(gpA[i])
L = cv2.subtract(gpA[i - 1],GE)
lpA = [gpA[5]]#此时为最小高斯
for i in range(5,0,-1):
    GE = cv2.pyrUp(gpA[i])
    L = cv2.subtract(gpA[i - 1],GE)
    lpA.append(L)

lpB = [gpB[5]]
for i in range(5,0,-1):
    GE = cv2.pyrUp(gpB[i])
    L = cv2.subtract(gpB[i - 1],GE)
    lpB.append(L)

## 此时在拉普拉斯的图像矩阵中,大部分都是很接近0在100以内,因此从视觉上,人眼无法明确感知,但是对于机器他们真实存在
## 正是这种强大,数学量级决定了,机器的强大,神经网络捕捉到人眼所不能感知的特征
## 将两个拉普拉斯进行拼接
LS = []
for la,lb in zip(lpA,lpB):
    rows,cols,dpt = la.shape
    ls = np.hstack((la[:,0:int(cols/2)],lb[:,int(cols/2):]))
    LS.append(ls)
## 此时在LS中显示的还是人眼不可识别的图案,还是低量    
ls_ = LS[0]
for i in range(1,6):
    ls_ = cv2.pyrUp(ls_)
    ls_ = cv2.add(ls_,LS[i])
## 卧槽这里是将拉普拉斯金字塔进行累加直接就得到一个肉眼可见的举证
b= ls
for _ in range(1,6):
    b = cv2.add(b,b)
cv2.imshow("Pyramid_blending2.jpg",LS[0])
cv2.imshow("Direct_blending.jpg",real)
cv2.waitKey(0)
cv2.destroyAllWindows()

总结:

关于图形融合的原理:是得到一个LS的列表,在LS[0],是高斯金字塔的最小一个分辨率原图,主要是提供颜色的,此时已经拼接好了,
其他几层则是拉普拉斯的金字塔图,主要是提供类似边界的,因为直接将LS[0]放大会很模糊,所以利用拉普拉斯提供组合图的边界
其中一层的拉普拉斯图人眼不可观察,需要多层进行累加才能观察正如上面b

21.OpenCV 中的轮廓

1.查找轮廓和画出轮廓

im = cv2.imread('001.PNG')
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
#第三个参数为simple是会将不必要点不存储
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
img = cv2.drawContours(im, contours, -1, (0,0,0), 3)#在原始图像中画出
contours[0]
array([[[599, 124]],

       [[598, 125]],

       [[599, 125]]], dtype=int32)
plt.figure(figsize=(12,7))
plt.subplot(121),plt.imshow(image,cmap = 'gray'),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB),cmap = 'gray'),plt.xticks([]),plt.yticks([])
(<matplotlib.axes._subplots.AxesSubplot at 0x18308d13080>,
 <matplotlib.image.AxesImage at 0x18308b62ac8>,
 ([], <a list of 0 Text xticklabel objects>),
 ([], <a list of 0 Text yticklabel objects>))

png

总结:

在用plt显示3通道图的时候,需要进行转换cv2.COLOR_BGR2RGB

2.轮廓特征

1.矩

cv2.moments() 会将计算得到的矩以一个字典的形式返回

img = cv2.imread('001.PNG',0)
ret,thresh = cv2.threshold(img,127,255,0)
_,contours,hierarchy = cv2.findContours(thresh,1,2)
cnt = contours[0]
M = cv2.moments(cnt)#此时的质为其中一个轮廓的质
print(M)
{'m00': 28.0, 'm10': 16506.0, 'm01': 8596.0, 'm20': 9730746.666666666, 'm11': 5067342.0, 'm02': 2638981.0, 'm30': 5736817107.0, 'm21': 2987339226.6666665, 'm12': 1555679299.5, 'm03': 810172693.0, 'mu20': 459.6666666660458, 'mu11': 0.0, 'mu02': 9.0, 'mu30': 9.5367431640625e-07, 'mu21': 3.166496753692627e-08, 'mu12': 0.0, 'mu03': 0.0, 'nu20': 0.5863095238087318, 'nu11': 0.0, 'nu02': 0.011479591836734693, 'nu30': 2.2988202195339494e-10, 'nu21': 7.632801510171317e-12, 'nu12': 0.0, 'nu03': 0.0}
# 计算重心
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
(cx,cy)
(589, 307)
#计算轮廓的面积
area = cv2.contourArea(cnt)
area
28.0
# 计算周长
perimeter = cv2.arcLength(cnt,True)
perimeter
31.656854152679443

轮廓近似

img = cv2.imread('002.jpg')
img1 = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(img1,127,255,0)

#在将轮廓提取的时候,必须是二值图(3通道,到灰度图,再到二值图)
_,contours,hierarchy = cv2.findContours(thresh,1,2)
cnt = contours[0]
epsilon = 0.02*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
#第二个参数叫epsilon,它是从原始轮廓到近似轮廓的最大距离。它是一个准确度参数。第三个参数设定弧线是否闭合
img2 = img.copy()
im = cv2.drawContours(img,[cnt] , -1, (0,255,0), 3)
imm = cv2.drawContours(img2,[approx] , -1, (0,0,255), 3)
plt.subplot(121),plt.imshow(cv2.cvtColor(im,cv2.COLOR_BGR2RGB)),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(cv2.cvtColor(imm,cv2.COLOR_BGR2RGB)),plt.xticks([]),plt.yticks([])

(<matplotlib.axes._subplots.AxesSubplot at 0x1830e87ffd0>,
 <matplotlib.image.AxesImage at 0x1830e89f4e0>,
 ([], <a list of 0 Text xticklabel objects>),
 ([], <a list of 0 Text yticklabel objects>))

png

总结:

  1. 在使用cv2.drawContours(img,[cnt] , -1, (0,255,0), 3)进行画图的时候,需要注意两个点,1.是它会在输入的img上
    直接操作,2.它对于输入的轮廓上特征点,必须是list的形式,否则它只会画出单独的点
  2. 可以利用epsilon = 0.1*cv2.arcLength(cnt,True)
    approx = cv2.approxPolyDP(cnt,epsilon,True),可以将轮廓的特征点进一步压缩

凸包

hull = cv2.convexHull(cnt)
hull
array([[[199, 139]],

       [[ 16, 139]],

       [[ 16,  15]],

       [[199,  15]]], dtype=int32)
hull = cv2.convexHull(cnt,returnPoints= False)
#hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]
#points 我们要传入的轮廓
# • hull 输出,通常不需要
# • clockwise 方向标志。如果设置为 True,输出的凸包是顺时针方向的。
# 否则为逆时针方向。
# • returnPoints 默认值为 True。它会返回凸包上点的坐标。如果设置
# 为 False,就会返回与凸包点对应的轮廓上的点。
hull
array([[128],
       [ 69],
       [  0],
       [143]], dtype=int32)
#凸包检测
cv2.isContourConvex(cnt)
False

边界矩形

x,y,w,h = cv2.boundingRect(cnt)#计算出轮廓的边界点
img = cv2.imread('002.jpg')
img2 = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
plt.imshow(cv2.cvtColor(img2,cv2.COLOR_BGR2RGB)),plt.xticks([]),plt.yticks([])
(<matplotlib.image.AxesImage at 0x1830e935978>,
 ([], <a list of 0 Text xticklabel objects>),
 ([], <a list of 0 Text yticklabel objects>))

png

旋转的边界矩形

# x,y,w,h = cv2.boundingRect(cnt)
# img3 = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)

rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
im = cv2.drawContours(im,[box],0,(0,0,255),2)
plt.imshow(cv2.cvtColor(img3,cv2.COLOR_BGR2RGB)),plt.xticks([]),plt.yticks([])
(<matplotlib.image.AxesImage at 0x1830e958b70>,
 ([], <a list of 0 Text xticklabel objects>),
 ([], <a list of 0 Text yticklabel objects>))

png

最小外接圆

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

椭圆拟合

ellipse = cv2.fitEllipse(cnt)
img5 = cv2.ellipse(img,ellipse,(0,255,0),2)

直线拟合

[vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
rows,cols = img.shape[:2]
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
img = cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)

总结:

上面的图形都是将轮廓提取出一些点,来画出外接的一些图形,可以在物体追踪的时候,标记目标目标

轮廓的性质

img = cv2.imread('002.jpg',0)
ret,thresh = cv2.threshold(img,127,255,0)
_,contours,hierarchy = cv2.findContours(thresh, 1, 2)
cnt = contours[0]

#长宽比
x,y,w,h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h

#Extent:轮廓面积与边界矩形面积的比
area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
#Solidity:轮廓面积与凸包面积的比
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area
#Equivalent Diameter:与轮廓面积相等的圆形的直径
equi_diameter = np.sqrt(4*area/np.pi)

#方向:对象的方向,下面的方法还会返回长轴和短轴的长度
(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)

mask = np.zeros(thresh.shape,np.uint8)
# 这里一定要使用参数-1, 绘制填充的的轮廓
plt.subplot(121),plt.imshow(mask,"gray"),plt.xticks([]),plt.yticks([])
cv2.drawContours(mask,[cnt],0,255,3)
plt.subplot(122),plt.imshow(mask,"gray"),plt.xticks([]),plt.yticks([])
(<matplotlib.axes._subplots.AxesSubplot at 0x204e105ccc0>,
 <matplotlib.image.AxesImage at 0x204e11ed3c8>,
 ([], <a list of 0 Text xticklabel objects>),
 ([], <a list of 0 Text yticklabel objects>))

png

#【7】最大值和最小值及它们的位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(thresh,mask = mask)
#【8】平均颜色及平均灰度
mean_val = cv2.mean(thresh,mask = mask)
#【9】极点:一个对象最上面,最下面,最左边,最右边的点
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])

img = cv2.imread('002.jpg')
cv2.circle(img,leftmost, 4,255, 5)
cv2.circle(img,rightmost, 4,255, 5)
cv2.circle(img,topmost, 4,255, 5)
cv2.circle(img,bottommost, 4,255, 5)
# cv2.drawContours(img,[list(leftmost),rightmost,topmost,bottommost],0,255,3)
plt.imshow(img),plt.xticks([]),plt.yticks([])
(<matplotlib.image.AxesImage at 0x204e0df62e8>,
 ([], <a list of 0 Text xticklabel objects>),
 ([], <a list of 0 Text yticklabel objects>))

png

轮廓:更多函数m

画出凸缺陷

img = cv2.imread('002.jpg')
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(img_gray, 127, 255,0)
_,contours,hierarchy = cv2.findContours(thresh,2,1)
cnt = contours[0]
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv2.line(img,start,end,[0,255,0],2)
    cv2.circle(img,far,5,[0,0,255],-1)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

defects[1,0]
array([ 609,    0,  661, 7168], dtype=int32)
defects.shape
(4, 1, 4)

点到轮廓的距离

dist = cv2.pointPolygonTest(cnt,(50,50),True)

轮廓匹配

匹配原则:Hu 矩是归一化中心矩的线性组合,之所以这样做是为了能够获取代表
图像的某个特征的矩函数,这些矩函数对某些变化如缩放,旋转,镜像映射(除
了 h1)具有不变形。强大的性质(不变形性)决定了强大的用途

img1 = cv2.imread('002.jpg',0)
img2 = cv2.imread('001.jpg',0)
ret, thresh = cv2.threshold(img1, 127, 255,0)
ret, thresh2 = cv2.threshold(img2, 127, 255,0)
_,contours,hierarchy = cv2.findContours(thresh,2,1)
cnt1 = contours[0]
_,contours,hierarchy = cv2.findContours(thresh2,2,1)
cnt2 = contours[0]
ret = cv2.matchShapes(cnt1,cnt2,1,0.0)
print(ret)

0.0767338447391066
plt.subplot(121),plt.imshow(img1,"gray"),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(img2,"gray"),plt.xticks([]),plt.yticks([])
(<matplotlib.axes._subplots.AxesSubplot at 0x204e5888080>,
 <matplotlib.image.AxesImage at 0x204e58d6240>,
 ([], <a list of 0 Text xticklabel objects>),
 ([], <a list of 0 Text yticklabel objects>))

png

轮廓的层次结构

需要注意的是它进行轮廓划分的时候,会将一个轮廓,当它包含着其他轮廓时,划分成两个例如3,3a
在模式选择里,RETR_EXTERNAL,用这种模式的话只会返回最外边的轮廓(第 0 级):轮廓
0,1,2
RETR_LIST,是提取所有的轮廓
RETR_CCOMP,在这种模式下会返回所有的轮廓并将轮廓分为两级组织结
构,闭环内轮廓为2,外为1,不闭环的为1
RETR_TREE,返回
所有轮廓,并且创建一个完整的组织结构列表。
[Next,Previous,First_Child,Parent]