OpenCV中的图像处理之一
一、改变颜色空间
使用cv.cvtColor,cv.inRange等函数三种色彩空间的转换:BGR、GRAY、HSV
提取视频中的彩色对象
#获取颜色转换的所有标记
import cv2 as cv
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
print(flags)
对象追踪
方法: 1、取视频的每一帧 - 2、转换从BGR到HSV颜色空间 - 3、我们对HSV图像设置蓝色范围的阈值 - 4、现在单独提取蓝色对象
import cv2 as cv
import numpy as np
cap = cv.VideoCapture(0)
while(1):
#读取帧
_,frame = cap.read()
#转换颜色空间BGR到HSV
hsv = cv.cvtColor(frame,cv.COLOR_BGR2HSV)
#定义HSV中蓝色的范围
lower_blue = np.array([100,50,50])
upper_blue = np.array([130,255,255])
#设置HSV的阈值使得只取蓝色
mask = cv.inRange(hsv,lower_blue,upper_blue)
#将掩膜和图像逐像素相加
res = cv.bitwise_and(frame,frame,mask=mask)
cv.imshow('frame',frame)
cv.imshow('mask',mask)
cv.imshow('res',res)
k = cv.waitKey(5) & 0xFF
if k == 27:
break
cv.destoryAllWindows()
#如何找到要追踪的HSV值
#要查找绿色的HSV值
import numpy as np
import cv2 as cv
green = np.uint8([[[0,255,0]]])
hsv_green = cv.cvtColor(green,cv.COLOR_BGR2HSV)
print(hsv_green)
课后练习题:尝试一种方法来提取多个彩色对象,例如同时提取红色、蓝色、绿色
import cv2 as cv
import numpy as np
cap = cv.VideoCapture(0)
while(1):
#读取帧
_,frame = cap.read()
#转换颜色空间BGR到HSV
hsv = cv.cvtColor(frame,cv.COLOR_BGR2HSV)
#定义HSV中蓝色的范围
lower_blue = np.array([100,50,50])
upper_blue = np.array([130,255,255])
#设置HSV的阈值使得只取蓝色
mask_B = cv.inRange(hsv,lower_blue,upper_blue)
lower_green = np.array([35,43,46])
upper_green = np.array([77,255,255])
mask_G = cv.inRange(hsv,lower_green,upper_green)
lower_red = np.array([0,43,46])
upper_red = np.array([20,255,255])
mask_R = cv.inRange(hsv,lower_red,upper_red)
mask = mask_B+mask_G+mask_R
#将掩膜和图像逐像素相加
res = cv.bitwise_and(frame,frame,mask=mask)
cv.imshow('frame',frame)
cv.imshow('mask',mask)
cv.imshow('res',res)
k = cv.waitKey(5) & 0xFF
if k == 27:
break
cv.destoryAllWindows()
二、图像几何变换
学习目标:使用函数cv.getPerspectiveTransform将不同的几何变换应用到图像上,如平移、旋转和放射变换等。
1、变换
opencv提供了两个转换函数cv.warpAffine和cv.warpPerspective,使用他们进行转换,cv.warpAffine采样23转换矩阵,而cv.warpPerspective采样33转换矩阵作为输入。
2、缩放
OpenCV通过函数cv.resize()实现图片的缩放,也可以指定缩放比例。也可使用插值方法是cv.INTER_AREA用于缩小,cv.INTER_CUBIC(慢)和cv.INTER_LINEAR**用于缩放。
import numpy as np
import cv2 as cv
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
res = cv.resize(img,None,fx=2,fy=2,interpolation=cv.INTER_CUBIC)
height, width = img.shape[:2]
res = cv.resize(img,(2*width,2*height),interpolation = cv.INTER_CUBIC)
print(img.size,img.shape)
print(res.size,res.shape)
3、平移
使用cv.warpAffine函数进行平移
import numpy as np
import cv2 as cv
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg',0)
rows,cols = img.shape
M = np.float32([[1,0,200],[0,1,100]]) #定义平移尺寸
dst = cv.warpAffine(img,M,(cols,rows)) #cv.warpAffine包含三个参数,原始图像,平移位移和输出图像的尺寸大小
cv.imshow("img",dst)
cv.waitKey(0)
cv.destroyAllWindows()
3、旋转
旋转的转换函数:cv.getRotationMatrix2D
#将图像相对于中心旋转90度而没有任何缩放比例
import numpy as np
import cv2 as cv
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg',0)
rows,cols = img.shape
M = cv.getRotationMatrix2D(((cols-1)/2,(rows-1)/2),90,1)
dst = cv.warpAffine(img,M,(cols,rows))
cv.imshow("img",dst)
cv.waitKey(0)
cv.destroyAllWindows()
4、仿射变换
在仿射变换中,原始图像中的所有平行线在输出图像中仍然平行,为了找到变换矩阵,我们需要输入图像中的三个点及其在输出图像中的对于位置,然后cv.getAffineTransform将创建一个2*3矩阵,将该矩阵传递给cv.warpAffine
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
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')
4、透视变换
透视变换通过函数cv.getPerspectiveTransform找到变换矩阵。然后将cv.warpPerspective应用于此3x3转换矩阵。直线依旧保持直线,要找到变换矩阵,需要输入图像上有四个点,到输出图像上需要相应的点,在这四个点中三个点不共线。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
rows,cols,ch = img.shape
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,(cols,rows))
plt.subplot(121),plt.imshow(img),plt.title('input')
plt.subplot(122),plt.imshow(dst),plt.title('output')
plt.show()
三、图像阈值
图像阈值主要包含:简单阈值、自适应阈值和Otsu的二值化阈值
1、简单阈值
对于每个像素,应用相同的阈值,如果像素值小于某个阈值设置为0,否则设置为最大值,cv.threshold包含四个参数:
1、源图像,灰度图像;
2、阈值,对像素值进行分类
3、分配给超过阈值的像素值的最大值;
4、不同类型的阈值,简单类型的阈值有:
cv.THRESH_BINARY
cv.THRESH_BINARY_INV
cv.THRESH_TRUNC
cv.THRESH_TOZERO
cv.THRESH_TOZERO_INV
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg',0)
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')
plt.title(titles[i])
plt.xticks([])
plt.yticks([])
plt.show()
2、自适应阈值
如果图像在不同区域具有不同的光照条件这种情况下可使用自适应阈值cv.adaptiveThreshold,包含三个输入参数。
阈值的计算方法:
cv.ADAPTIVE_THRESH_MEAN_C::阈值是邻近区域的平均值减去常数C。 cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域值的高斯加权总和减去常数C。
3、Otsu的二值化
Otsu的方法从图像直方图中确定最佳全局阈值。为此,使用了cv.threshold作为附加标志传递。阈值可以任意选择。然后,算法找到最佳阈值,该阈值作为第一输出返回。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg',0)
#全局阈值
ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
#Otsu阈值
ret,thresh2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
#高斯滤波后再采用Otsu阈值
blur = cv.GaussianBlur(img,(5,5),0)
ret,thresh3 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
#绘制所有图像及其直方图
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",
'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
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二值化的实现过程
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg',0)
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.max()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in range(1,256):
p1,p2 = np.hsplit(hist_norm,[i]) #概率
q1,q2 = Q[i],Q[255]-Q[i] #对类求和
b1,b2 = np.hsplit(bins,[i]) #权重
#寻找均值和方差
m1,m2 = np.sum(p1*b1)/q1,np.sum(p2*b2)/q2
v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2
#计算最小化函数
fn = v1*q1 +v2*q2
if fn <fn_min:
fn_min = fn
thresh = i
#使用opencv找到otsu的阈值
ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
print("{} {}".format(thresh,ret))
四、图像平滑
使用各种低通滤波模糊图像,将定制的滤波应用于图像(2D卷积)。
1、2D卷积(图像过滤)
可以使用各种低通滤波器(LPF)、高通滤波器(HPF)等对图像进行滤波,LPF有助于消除噪声,是图像模糊,HPF滤波器有助于找到边缘,函数cv.filter2D来将内核与图像进行卷积。
滤波器的操作如下:保持这个内核在一个像素上,将所有低于这个内核的25个像素相加,取其平均值,然后用新的平均值替换中心像素。
#低通滤波器
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
kernel = np.ones((5,5),np.float32)/25
dst = cv.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()
2、图像模糊(图像平滑)
通过将图像与低通滤波器内核进行卷积来实现图像模糊,从图像中消除高频部分(噪声、边缘),四种类型的模糊技术
1、平均
通过图像与归一化滤镜进行卷积来完成,它通过获取内核区域下所有像素的平均值,并替换中心元素,通过cv.blur()或者cv.boxFilter()完成
#平均低通滤波器
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
blur = cv.blur(img,(5,5))
plt.subplot(131),plt.imshow(img),plt.title("original")
plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(blur),plt.title('blurred')
plt.xticks([]), plt.yticks([])
plt.show()
2、高斯模糊
代替盒式滤波器使用高斯核,通过函数cv.GaussianBlur()完成,我们应该指定内核的宽度和高度,为正数和奇数,还应该指定X和Y方向的标准差,如果两个都为0,则根据内核大小进行计算。
#高斯模糊低通滤波器
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
blur = cv.GaussianBlur(img,(5,5),0)
plt.subplot(131),plt.imshow(img),plt.title("original")
plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(blur),plt.title('blurred')
plt.xticks([]), plt.yticks([])
plt.show()
3、中位模糊
cv.medianBlur()提取内核区域下所有像素的中值,并将中心元素替换该中值。
#中位模糊低通滤波器
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
blur = cv.medianBlur(img,5)
plt.subplot(131),plt.imshow(img),plt.title("original")
plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(blur),plt.title('blurred')
plt.xticks([]), plt.yticks([])
plt.show()
4、双边滤波
cv.bilateralFilter()在去除噪声的同时保持边缘清晰锐利非常有效,双边滤波器在空间中也采用高斯率比起,但是又有一个高斯滤波器,他是像素差的函数.
#双边滤波低通滤波器
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
blur = cv.bilateralFilter(img,9,75,75)
plt.subplot(131),plt.imshow(img),plt.title("original")
plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(blur),plt.title('blurred')
plt.xticks([]), plt.yticks([])
plt.show()
五、形态转换
不同的形态学操作:侵蚀、膨胀、开运算、闭运算等,不同的函数:cv.erode(),cv.dilate(),cv.morphologyEx()等。
1、侵蚀、扩张、开运算、闭运算
侵蚀:侵蚀前景物体的边界,原始图像的一个像素只有当内核下的所有元素都是1才被认为是1,否则就会被侵蚀,根据内核大小,边界附近的所有像素都会被丢弃,有助于去除小的白色噪声,分离两个连接的对象。
扩张:与侵蚀想法相反,如果内核下至少一个像素为1则像素元素为1,它会增加图像中的白色区域或增加前景对象的大小,在连接对象的损坏部分也很有用。
开放:使用函数cv.morphplogyEx()是侵蚀后扩张,对消除噪音很有用;
闭运算:使用函数cv.morphplogyEx()是扩张后侵蚀,对关闭前景对象内部的小孔或对象上的小黑点很有用;
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
kernel = np.ones((5,5),np.uint8)
erosion = cv.erode(img,kernel,iterations=1) #侵蚀
dilation = cv.dilate(img,kernel,iterations=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) #形态学梯度,扩张和侵蚀的区别,结果更像对象的轮廓
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel) #顶帽,输入图像和图像开运算之差;
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel) #黑帽,输入图像和图像闭运算之差;
plt.subplot(141),plt.imshow(img),plt.title("original")
plt.xticks([]), plt.yticks([])
plt.subplot(142),plt.imshow(erosion),plt.title("erosion")
plt.xticks([]), plt.yticks([])
2、结构元素
opencv使用函数cv.getStructuringElement(),只需要传递内核的形状和大小,即可获得所需的内核。
#矩形内核
cv.getStructuringElement(cv.MORPH_RECT,(5,5))
#椭圆内核
cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
#十字内核
cv.getStructuringElement(cv.MORPH_CROSS,(5,5))
六、图像梯度
opencv提供三种类型的梯度滤波器或者高通滤波器,使用如下函数cv.Sobel()、cv.Scharr()、cv.Laplacian()查找图像梯度,边缘等;
Sobel算子是高斯平滑加微分运算的联合运算,通过参数ksize指定内核的大小,ksize=-1,则使用33 Scharr滤波器,比33sobel滤波器有更黑的效果。
Laplacian算子是二阶导数给出的图像的拉普拉斯图,一阶导数通过sobel算子计算,拉普拉斯内核:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
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(221),plt.imshow(img,cmap='gray')
plt.title('original'),plt.xticks([]),plt.yticks([])
plt.subplot(222),plt.imshow(laplacian,cmap='gray')
plt.title('laplacian'),plt.xticks([]),plt.yticks([])
plt.subplot(223),plt.imshow(sobelx,cmap='gray')
plt.title('sobelx'),plt.xticks([]),plt.yticks([])
plt.subplot(224),plt.imshow(sobely,cmap='gray')
plt.title('sobely'),plt.xticks([]),plt.yticks([])
plt.show()
黑色到白色的过渡被视为政协率,白色到黑色的过渡被视为负斜率,因此,当你将数据转换为np.uint8时,所有负斜率均被设置为0,所以会错过这个边缘信息,如果要检测两个边缘,更好的选择是将输出数据类型保留为更高的形式,例如:cv.CV_16S, cv.CV_64F等,取其绝对值,然后转换回cv.CV_8U
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg',0)
sobelx8u = cv.Sobel(img,cv.CV_8U,1,0,ksize=5)
sobelx64f = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
abs_sobelx64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobelx64f)
plt.subplot(221),plt.imshow(img,cmap='gray')
plt.title('original'),plt.xticks([]),plt.yticks([])
plt.subplot(222),plt.imshow(sobelx8u,cmap='gray')
plt.title('sobelx8u'),plt.xticks([]),plt.yticks([])
plt.subplot(223),plt.imshow(abs_sobelx64f,cmap='gray')
plt.title('abs_sobelx64f'),plt.xticks([]),plt.yticks([])
plt.subplot(224),plt.imshow(sobel_8u,cmap='gray')
plt.title('sobel_8u'),plt.xticks([]),plt.yticks([])
plt.show()
七、Canny边缘检测
canny是一种流行的边缘检测算法;
由于边缘检测容易受到噪声的影响,因此第一步是使用5*5高斯滤波器消除图像中的噪声。
opencv使用cv.Canny()进行边缘检测,第一个参数是输入图像,第二和第三个参数分别为最小和最大值,第三个参数是图片大小,即sobel内核大小,默认为3,最后一个参数是L2gradient,指定用于查找梯度幅度的方程式。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg',0)
edges = cv.Canny(img,100,200)
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()
八、图像金字塔
使用函数cv.pyrUp(), cv.pyrDown();
我们需要创建一组具有不同分辨率的相同图像,并在所有图像中搜索对象,这种具有不同分辨率的图像集成为“图像金字塔”,两种金字塔:高斯金字塔和拉普拉斯金字塔;
使用金字塔进行图像融合,图像融合的步骤:
1、加载两个需要融合的图像;
2、查找两个图像的高斯金字塔
3、在高斯金字塔中,找到其拉普拉斯金字塔;
4、在每个拉普拉斯金字塔级别中加入第一个图像的左半部分和第二个图象的右半部分;
5、从联合图像金字塔中重建原始图像
#图像融合
import numpy as np
import cv2 as cv,sys
from matplotlib import pyplot as plt
A = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
B = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
#生成A的高斯金字塔
G = A.copy()
gpA = [G]
for i in range(6):
G = cv.pyrDown(G)
gpA.append(G)
#生成B的高斯金字塔
G = B.copy()
gpB = [G]
for i in range(6):
G = cv.pyrDown(G)
gpB.append(G)
#生成A的拉普拉斯金字塔
lpA = [gpA[5]]
for i in range(5,0,-1):
GE = cv.pyrUp(gpA[i])
L = cv.subtract(gpA[i-1],GE)
lpA.append(L)
#生成B的拉普拉斯金字塔
lpB = [gpB[5]]
for i in range(5,0,-1):
GE = cv.pyrUp(gpB[i])
L = cv.subtract(gpB[i-1],GE)
lpB.append(L)
#现在在每个级别中添加左右半个图像
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)
#现在重建
ls_ = LS[0]
for i in range(1,6):
ls_ = cv.pyrUp(ls_)
ls_ = cv.add(ls_,LS[i])
#图像与直接连接的每一半
real = np.hstack((A[:,:cols/2],B[:,cols/2:]))
plt.imshow(real,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
九、OpenCV中的轮廓
学习通过cv.findContours()和cv.drawContours()查找轮廓和绘制轮廓。轮廓指为具有相同颜色或者强度的所有的连续点(沿边界)的曲线,轮廓对形状分析和对象的检测和识别有用。
在找到轮廓之前,使用阈值或canny进行边缘检测;
找到轮廓即找到白色的对象,背景是黑色。
#图像轮廓
import numpy as np
import cv2 as cv,sys
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
imgray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray,127,255,0)
contours,hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)#第一个参数是源图像,第二个是轮廓检索模式,第三个是轮廓逼近方法。输出等高线和层次结构。轮廓是图像中所有轮廓的python列表,每个单独的轮廓是一个(X,Y)坐标的numpy的边界点的对象。
cv.drawContours(img,contours,-1,(0,255,0),3) #绘制所有轮廓
#绘制单个轮廓,比如第四个轮廓 #方法一
cv.drawContours(img, contours, 3, (0,255,0), 3)
#绘制单个轮廓,比如第四个轮廓 #方法二
cnt = contours[4]
cv.drawContours(img,[cnt],0,(0,255,0),3)
1、轮廓特征
轮廓的不同特征包括:面积、周长、质心、边界框等
特征矩:cv.moments()提供了所有计算出矩阵的字典
cv.convexHull()函数检查曲线是否存在凸凹缺陷并对其进行校正
边界矩形:有两种边界矩形
#图像轮廓
import numpy as np
import cv2 as cv,sys
from matplotlib import pyplot as plt
img = cv.imread('C:\\Users\\Administrator\\Desktop\\123.jpg')
imgray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray,127,255,0)
contours,hierarchy = cv.findContours(thresh,1,2)
cnt = contours[0]
#moments计算出所有矩值的字典
M = cv.moments(cnt)
#质心
c = (int(M['m10']/M['m00']),int(M['m01']/M['m00']))
#轮廓面积
area = cv.contourArea(cnt)
#轮廓周长
perimeter = cv.arcLength(cnt,True)
#轮廓近似
epsilon = 0.1*cv.arcLength(cnt,True)
approx = cv.approxPolyDP(cnt,epsilon,True)#epsilon是从轮廓到近似轮廓的最大距离;第三个参数指定曲线是否闭合
#轮廓凸包
hull = cv.convexHull(cnt)
#检测凸度cv.isContourConvex()具有检查曲线是否凸出的功能
k = cv.isContourConvex(cnt)
直角矩形:他是一个矩形,不考虑物体的旋转,所以边界矩形的面积不是最小的,由函数cv.boundingRect()
旋转矩形:边界矩形是用最小面积绘制的,所以它也考虑了旋转,使用的函数是cv.minAreaRect(),它返回一个Box2D结构,其中包含以下细节(中心(X,Y),(宽度,高度),旋转角度)
#直角矩形
x,y,w,h = cv.boundingRect(cnt)
cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
#旋转矩形
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img,[box],0,(0,0,255),2)
最小闭合圈:使用函数**cv.minEnclosingCircle(*()查找对象的圆周。它是一个以最小面积完全覆盖物体的圆。
#最小闭合圈
(x,y),radius = cv.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv.circle(img,center,radius,(0,255,0),2)
拟合一个椭圆
#拟合一个椭圆
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img,ellipse,(0,255,0),2)
拟合直线
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)
2、轮廓属性
#长宽比
x,y,w,h = cv.boundingRect(cnt)
aspect_ratio = float(w)/h
print(aspect_ratio)
#范围:轮廓区域与边界矩形区域的比值
area = cv.contourArea(cnt)
x,y,w,h = cv.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
print(extent)
#坚实度:等高线面积与其凸包面积之比
area = cv.contourArea(cnt)
hull = cv.convexHull(cnt)
hull_area = cv.contourArea(hull)
solidity =float(area)/hull_area
print(solidity)
#等效直径:面积与轮廓面积相同的圆的直径
area = cv.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
print(equi_diameter)
#取向:物体指向的角度
(x,y),(MA,ma),angle = cv.fitEllipse(cnt)
print(angle,(MA,ma),(x,y))
#掩码和最小像素点
imgray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
mask = np.zeros(imgray.shape,np.uint8)
cv.drawContours(mask,[cnt],0,255,-1)
pixelpoints = np.transpose(np.nonzero(mask))
print(pixelpoints)
#最大值、最小值和他们的位置
min_val,max_val,min_loc,max_loc = cv.minMaxLoc(imgray,mask=mask)
print(min_val,max_val,min_loc,max_loc)
#平均颜色或平均强度
mean_val = cv.mean(img,mask=mask)
print(mean_val)
#极端点:对象的最顶部、最底部、最右侧和最左侧的点
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmmax()][0])
print(leftmost,rightmost,topmost,bottommost)
凸性缺陷:凸包上的任何偏差都是凸性缺陷,通过函数cv.convexityDefects()来计算;
import cv2 as cv
import numpy as np
img = cv.imread("C:\\Users\\Administrator\\Desktop\\star.jpg")
img_gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(img_gray, 127, 255,0)
contours,hierarchy = cv.findContours(thresh,2,1)
cnt = contours[0]
hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.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])
cv.line(img,start,end,[0,255,0],2)
cv.circle(img,far,5,[0,0,255],-1)
cv.imshow('img',img)
#点多边形测试:找到图像上一点到轮廓线的最短距离,点在轮廓线内为正
dist = cv.pointPolygonTest(cnt,(50,50),True)#第三个参数找到距离。
plt.imshow(img,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
cv.waitKey(0)
cv.destroyAllWindows()
形状轮廓:cv.matchShapes比较两个形状或者两个轮廓,并返回一个显示相似度的度量,结果越低,匹配越好。
import cv2 as cv
import numpy as np
img1 = cv.imread("C:\\Users\\Administrator\\Desktop\\star.jpg")
img2 = cv.imread("C:\\Users\\Administrator\\Desktop\\star.jpg")
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)
3、轮廓分层
轮廓检索模式:
1、RETR_LIST检索所有的轮廓,不创建任何亲子关系,在返回的数组中第三项和第四项都是-1
2、RETR_EXTERNAL:他只返回极端的外部标志,只提取外部轮廓的时候可以使用该函数
3、RETR_CCOMP:检测所有轮廓并将其排列为2层结构
4、RETR_TREE:检索所有的轮廓并创建一个完整的家族层次结构列表。