图像的形态转换主要是基于二值化图像,进行图像的形状操作。形态变换操作需要2个输入;一个是二值化图像,另外一个被称为“结构化元素”;主要的形态转换操作是复试与膨胀,其它形态转换操作都基于此两者操作。
对输入图像用特定结构元素进行腐蚀操作,表现出的现象是图像被缩小;此处的缩小不是指图像的尺寸变小,而是图像中的信息部分(主要是亮度较高的像素)缩小。
腐蚀算法:用结构元素对应的矩阵,例如3X3,扫描图像的每一个像素,用结构元素与其覆盖的图像做“与”运算,如果都为1,结构图像的该像素为1,否则为0;结果:使图像减小一圈。
import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv.erode(img,kernel,iterations = 1)
对输入图像用特定结构元素进行膨胀操作,表现出的现象是图像被放大;此处的放大不是指图像的尺寸变大,而是图像中的信息部分(主要是亮度较高的像素)放大。
膨胀算法:用结构元素对应的矩阵,例如3X3,扫描图像的每一个像素,用结构元素与其覆盖的图像做“或”运算,如果都为0,结构图像的该像素为0,否则为1;结果:使图像放大一圈。
import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
dilation = cv.dilate(img,kernel,iterations = 1)
图像的开操作是指,先腐蚀后膨胀,适合去除图像有效信息外的白噪点
import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
图像的闭操作是指,先膨胀后腐蚀,适合去除图像有效信息内的黑噪点
函数与开操作函数相同,只是op参数不同,取值为:cv.MORPH_CLOSE
import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
形态学梯度:膨胀图减去腐蚀图,dilation - erosion,这样会得到物体的轮廓
函数与开操作函数相同,只是op参数不同,取值为:cv.MORPH_GRADIENT
import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
顶帽(tophat):原图减去开操作后的图:src - open_dst
因为开操作带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
函数与开操作函数相同,只是op参数不同,取值为:cv.MORPH_TOPHAT
import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)
黑帽(blackhat):闭运算图像与原图像差值:close_dst - src
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。所以,黑帽运算用来分离比邻近点暗一些的斑块,显示出图像的轮廓。
函数与开操作函数相同,只是op参数不同,取值为:cv.MORPH_BLACKHAT
import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)
opencv提供了三种梯度过滤器或高通过滤器Sobel、Scharr、Laplacian.(高通滤波器high-pass fliter(HPF) 寻找图片的边界)。
图像上的高通滤波器怎么理解,暂时不得而知,
Sobel operators是高斯模糊操作 加上differentian(变异/分化);所以它对噪音更有抵抗;可以指明衍生物(derivatives)的方向,垂直或者是水平的(分别对应是yorder、xorder)。
在参数ksize中指明kernel 的大小。其中ksize=-1时,会使用3x3的Charr filter(在这个情况下,结果会更好)
Sobel算子是一阶导数的边缘检测算子,在算法实现过程中,通过3×3模板作为核与图像中的每个像素点做卷积和运算,然后选取合适的阈值以提取边缘。
采用3×3邻域可以避免在像素之间内插点上计算梯度。Sobel算子也是一种梯度幅值。
Sobel算子算法的优点是计算简单,速度快。但是由于只采用了2个方向的模板,只能检测水平和垂直方向的边缘,因此这种算法对于纹理较为复杂的图像,其边缘检测效果就不是很理想。
该算法认为:凡灰度新值大于或等于阈值的像素点时都是边缘点。这种判断欠合理,会造成边缘点的误判,因为许多噪声点的灰度值也很大。
scharr算子与Sobel的不同点是在平滑部分,这里所用的平滑算子是1/16∗[3,10,3],相比于1/4∗[1,2,1],中心元素占的权重更重,这可能是相对于图像这种随机性较强的信号,邻域相关性不大,所以邻域平滑应该使用相对较小的标准差的高斯函数,也就是更瘦高的模板
输入图像 | 输出图像 |
---|---|
CV_8U | -1/CV_16S/CV_32F/CV_64F |
CV_16U、CV_16S | -1/CV_32F/CV_64F |
CV_32F | -1/CV_32F/CV_64F |
CV_64F | -1/CV_64F |
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('dave.jpg',0)
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,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()
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('box.png',0)
# Output dtype = cv.CV_8U
sobelx8u = cv.Sobel(img,cv.CV_8U,1,0,ksize=5)
# 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=5)
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()
Laplacian 算子是n维欧几里德空间中的一个二阶微分算子,定义为梯度grad的散度div。
这里不甚明白,还需要多加学习与练习,先看效果吧
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('dave.jpg',0)
laplacian = cv.Laplacian(img,cv.CV_64F)
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.show()
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('messi5.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()
图像金字塔有两种:高斯金字塔、拉普拉斯金字塔
高斯金字塔函数说明:
img = cv.imread('messi5.jpg')
lower_reso1 = cv.pyrDown(img)
lower_reso2 = cv.pyrDown(lower_reso1)
lower_reso3 = cv.pyrDown(lower_reso2)
拉普拉斯金字塔函数说明:
img = cv.imread('messi5.jpg')
lower_reso = cv.pyrDown(higher_reso)
higher_reso2 = cv.pyrUp(lower_reso)
cv.findContours()函数可以查找图像的轮廓
import numpy as np
import cv2 as cv
im = cv.imread('test.jpg')
imgray = cv.cvtColor(im, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
im2, contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours函数用于画图像的轮廓
import numpy as np
import cv2 as cv
img = cv.imread('star.jpg',0)
ret,thresh = cv.threshold(img,127,255,0)
im2,contours,hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv.moments(cnt)
print( M )
area = cv.contourArea(cnt)
perimeter = cv.arcLength(cnt,True)
epsilon = 0.1*cv.arcLength(cnt,True)
approx = cv.approxPolyDP(cnt,epsilon,True)
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]
k = cv.isContourConvex(cnt)
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)
12.轮廓最椭圆 Fitting an Ellipse
(x,y),radius = cv.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv.circle(img,center,radius,(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)
x,y,w,h = cv.boundingRect(cnt)
aspect_ratio = float(w)/h
area = cv.contourArea(cnt)
x,y,w,h = cv.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
area = cv.contourArea(cnt)
hull = cv.convexHull(cnt)
hull_area = cv.contourArea(hull)
solidity = float(area)/hull_area
area = cv.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
(x,y),(MA,ma),angle = cv.fitEllipse(cnt)
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])
cv2.findContours()函数来找轮廓的时候,我们传入了一个参数,Contour Retrieval Mode。我们一般传的是cv2.RETR_LIST或者cv2.RETR_TREE这样就可以了。但是这个参数实际是什么意思呢?
函数输出时我们得到了三个数组,第一个是图像,第二个是我们的轮廓,第三个输出名字是hierarchy层次。
什么是层次呢?
用cv2.findContours()函数来检测图像里的轮廓,有时候轮廓在不同的地方,但是在有些情况下,有些图形在别的图形里面,就像图形嵌套,在这种情况下,我们把外面那层图形叫做parent,里面的叫child。这样图形里的轮廓之间就有了关系。我们可以指定一个轮廓和其他之间的是如何连接的,这种关系就是层级。
每个轮廓有他自己的关于层级的信息,谁是他的孩子,谁是他的父亲等。OpenCV用一个包含四个值得数组来表示:[Next, Previous, First_Child, Parent]"Next表明同一层级的下一个轮廓"比如,在我们的图片里的contour-0,水上hi他相同层级的下一个轮廓?是contour-1,所以Next=1,对于Contour-1,下一个是contour-2,所以Next=2那对于contour-2呢?没有同层级的下一个轮廓,所以Next=-1。那么对于contour-4呢?同层级的下一个是contour-5,所以下一个轮廓是contour-5.Next=5"Previous指同层级的前一个轮廓"和上面一样,contour-1的前一个是contour-0.contour-2的前一个contour-1.对于contour-0没有前序,所以-1"First_Child指它的第一个孩子轮廓"不用解释,对于contour-2,孩子是contour-2a,所以这里是contour-2a的索引,contour-3a有两个孩子,但我们只取第一个,是contour-4,所以First_Child=4."Parent指它的父轮廓索引"和First_Child相反,contour-4和contour-5的parent都是contour-3a,对于contour-3a,是contour-3注意:如果没有孩子或者父亲,就为-1
写到这里发现,OPENCV的内容越来越多,需要消化的时间越来越长,往后几篇时间也许会在1个月以后了,就现在这些写的东西也需要多次的练习与理解,继续努力吧。