形態學變換除了OpenCV-Python教程:形態學變換~腐蝕和膨脹介紹的腐蝕和膨脹還有開操作、閉操作、頂帽變換、黑帽變換等,這些變換都是以morphologyEx()的介面函式呼叫的,該函式的介面形式如下:
cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst
- 引數含義:
- src:源影像,通道數任意;影像深度只能是CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;其中op為cv2.MORPH_HITMISS時僅支持CV_8UC1;
- op:變換方式;
- kernel:可以由getStructuringElement()構建,op為cv2.MORPH_HITMISS時則由子圖構建;
- dst:輸出影像,通道數和資料型別同src;
- anchor:錨點,默認使用(-1,-1)表示中心點;
- iterations:迭代次數;
- borderType:邊界型別;
- borderValue:邊界值;
引數含義和腐蝕、膨脹幾乎一樣,僅僅多了個op入參,用來表示形態學變換的方式,op的值和erode,dilate的關系如下:
形態學變換 | op標志位 | 與erode和dilate關系 |
腐蝕 | cv2.MORPH_ERODE | dst=erode(src,element) |
膨脹 | cv2.MORPH_DILATE | dst=dilate(src,element) |
開操作 | cv2.MORPH_OPEN | dst=dilate(erode(src,element)) |
閉操作 | cv2.MORPH_CLOSE | dst=erode(dilate(src,element)) |
梯度 | cv2.MORPH_GRADIENT | dst=dilate(src,element)?erode(src,element) |
頂帽 | cv2.MORPH_TOPHAT | dst=src?open(src,element) |
黑帽 | cv2.MORPH_BLACKHAT | dst=close(src,element)?src |
擊中擊不中 | cv2.MORPH_HITMISS | dst=erode(src,element) & erode(~src,~element) |
1、開操作
開操作的實質是先進行腐蝕再膨脹,可以用來消除小于結構元大小的細小區域,在OpenCV-Python教程:形態學變換~腐蝕和膨脹(erode,dilate)一文中關于五線譜的例子先腐蝕后膨脹的程序可以用開操作實作,下面的例子分別進行腐蝕-膨脹和開操作,對比變換后的影像差異:
import matplotlib.pyplot as plt
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
img = cv2.imread('..\\samples\\data\\notes.png',cv2.IMREAD_GRAYSCALE)
_,img_bin = cv2.threshold(img,127,255,1)#二值反色
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(1,7))
img_erode = cv2.erode(img_bin,kernel,iterations=1)
img_dilate = cv2.dilate(img_erode,kernel,iterations=1)
img_open = cv2.morphologyEx(img_bin,cv2.MORPH_OPEN,kernel,iterations=1)
img_diff = cv2.absdiff(img_dilate,img_open)
print('countNonZero(img_diff):',cv2.countNonZero(img_diff))
#顯示影像
fig,ax = plt.subplots(3,1)
ax[0].set_title('原圖 (juzicode.com)')
ax[0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib顯示影像為rgb格式
ax[1].set_title('img_erode_dilate')
ax[1].imshow(cv2.cvtColor(img_dilate,cv2.COLOR_BGR2RGB))
ax[2].set_title('img_open')
ax[2].imshow(cv2.cvtColor(img_open,cv2.COLOR_BGR2RGB))
ax[0].axis('off');ax[1].axis('off');ax[2].axis('off')
plt.show()
運行結果:
VX公眾號: 桔子code / juzicode.com
cv2.__version__: 4.5.3
countNonZero(img_diff): 0
這里用img_diff = cv2.absdiff(img_dilate,img_open)得到的2幅影像的差異,再用cv2.countNonZero(img_diff)統計差異影像的非0值,得到的數值為0,可以看到2個影像是完全相同的,另外從影像對比看開操作和先腐蝕后膨脹效果也是一樣的,
在OpenCV4.5.3 morphologyEx()的原始碼中可以看到MORPH_OPEN分支表示的開操作實際就是先腐蝕后膨脹得到的,同樣地MORPH_CLOSE分支表示的閉操作則是先膨脹再腐蝕得到的:
void morphologyEx( InputArray _src, OutputArray _dst, int op,
InputArray _kernel, Point anchor, int iterations,
int borderType, const Scalar& borderValue )
{
//......
Mat src = _src.getMat(), temp;
_dst.create(src.size(), src.type());
Mat dst = _dst.getMat();
//......
switch( op )
{
//......
case MORPH_OPEN:
erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_CLOSE:
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
//......
default:
CV_Error( CV_StsBadArg, "unknown morphological operation" );
}
}
2、閉操作
閉操作實際上是先進行膨脹再腐蝕,因為膨脹可以用來填充孔洞、修復缺失的連接,但是同時也會導致白色輪廓增大,當用同樣的結構元(kernel)再進行一次腐蝕操作后,就可以保持外形輪廓和原來的一致,下面是一個膨脹-腐蝕和閉操作修復孔洞對比的例子:
import matplotlib.pyplot as plt
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
img = cv2.imread('..\\samples\\picture\\mnist-7-hole.jpg',cv2.IMREAD_GRAYSCALE)
_,img_bin = cv2.threshold(img,127,255,0)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(11,11))
img_dilate = cv2.dilate(img_bin,kernel,iterations=1)
img_erode = cv2.erode(img_dilate,kernel,iterations=1)
img_close = cv2.morphologyEx(img_bin,cv2.MORPH_CLOSE,kernel,iterations=1)
img_diff = cv2.absdiff(img_close,img_close)
print('countNonZero(img_diff):',cv2.countNonZero(img_diff))
#顯示影像
fig,ax = plt.subplots(2,2)
ax[0][0].set_title('原圖 (juzicode.com)')
ax[0][0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib顯示影像為rgb格式
ax[0][1].set_title('img_dilate')
ax[0][1].imshow(cv2.cvtColor(img_dilate,cv2.COLOR_BGR2RGB))
ax[1][0].set_title('img_erode_dilate')
ax[1][0].imshow(cv2.cvtColor(img_erode,cv2.COLOR_BGR2RGB))
ax[1][1].set_title('img_close')
ax[1][1].imshow(cv2.cvtColor(img_close,cv2.COLOR_BGR2RGB))
ax[0][0].axis('off');ax[0][1].axis('off');ax[1][0].axis('off');ax[1][1].axis('off')
plt.show()
運行結果:
原圖數字7中存在黑色“孔洞”(圖1),為了消除“孔洞”如果只做膨脹處理會導致外形增大(圖2),但是在膨脹的基礎上再進行一次腐蝕就能保證外形和原圖一樣(圖3),閉操作的效果和圖3一樣(圖4),
3、形態學梯度
形態學梯度操作是用膨脹影像減去腐蝕影像的結果,因為膨脹可以增大邊沿,腐蝕會縮小邊沿,所以形態學梯度變換就能將輪廓提取出來:
import matplotlib.pyplot as plt
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
img = cv2.imread('..\\samples\\picture\\mnist-7.jpg',cv2.IMREAD_GRAYSCALE)
_,img_bin = cv2.threshold(img,127,255,0)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(11,11))
img_dilate = cv2.dilate(img_bin,kernel,iterations=1)
img_erode = cv2.erode(img_bin,kernel,iterations=1)
img_dilate_erode = img_dilate - img_erode
img_gradient = cv2.morphologyEx(img_bin,cv2.MORPH_GRADIENT,kernel,iterations=1)
#顯示影像
fig,ax = plt.subplots(2,2)
ax[0][0].set_title('原圖 (juzicode.com)')
ax[0][0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib顯示影像為rgb格式
ax[0][1].set_title('img_dilate')
ax[0][1].imshow(cv2.cvtColor(img_dilate,cv2.COLOR_BGR2RGB))
ax[1][0].set_title('img_erode')
ax[1][0].imshow(cv2.cvtColor(img_erode,cv2.COLOR_BGR2RGB))
ax[1][1].set_title('img_gradient')
ax[1][1].imshow(cv2.cvtColor(img_gradient,cv2.COLOR_BGR2RGB))
ax[0][0].axis('off');ax[0][1].axis('off');ax[1][0].axis('off');ax[1][1].axis('off')
plt.show()
運行結果:
4、頂帽
頂帽變換是用原圖減去開操作影像,因為開操作會去除小于結構元的小區域,原圖減去開操作影像后,會將開操作去除的小區域保留下來:
import matplotlib.pyplot as plt
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
img = cv2.imread('..\\samples\\picture\\mnist-7-noise.jpg',cv2.IMREAD_GRAYSCALE)
_,img_bin = cv2.threshold(img,127,255,0)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(11,11))
img_open = cv2.morphologyEx(img_bin,cv2.MORPH_OPEN,kernel,iterations=1)
img_src_open = img_bin - img_open
img_tophat = cv2.morphologyEx(img_bin,cv2.MORPH_TOPHAT,kernel,iterations=1)
#顯示影像
fig,ax = plt.subplots(2,2)
ax[0][0].set_title('原圖 (juzicode.com)')
ax[0][0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib顯示影像為rgb格式
ax[0][1].set_title('img_open')
ax[0][1].imshow(cv2.cvtColor(img_open,cv2.COLOR_BGR2RGB))
ax[1][0].set_title('img_src_open')
ax[1][0].imshow(cv2.cvtColor(img_src_open,cv2.COLOR_BGR2RGB))
ax[1][1].set_title('img_tophat')
ax[1][1].imshow(cv2.cvtColor(img_tophat,cv2.COLOR_BGR2RGB))
ax[0][0].axis('off');ax[0][1].axis('off');ax[1][0].axis('off');ax[1][1].axis('off')
plt.show()
運行結果:
5、黑帽
黑帽變換和頂帽變換則相反,是將閉操作后的影像減去原圖,因為閉操作會填充孔洞(小的黑色區域),孔洞部分變成白色,而原圖中仍然為黑色,這樣就會將原圖中的孔洞保留下來并變為白色區域,
import matplotlib.pyplot as plt
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
img = cv2.imread('..\\samples\\picture\\mnist-7-hole.jpg',cv2.IMREAD_GRAYSCALE)
_,img_bin = cv2.threshold(img,127,255,0)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(11,11))
img_close = cv2.morphologyEx(img_bin,cv2.MORPH_CLOSE,kernel,iterations=1)
img_close_src = img_close-img_bin
img_blackhat = cv2.morphologyEx(img_bin,cv2.MORPH_BLACKHAT,kernel,iterations=1)
#顯示影像
fig,ax = plt.subplots(2,2)
ax[0][0].set_title('原圖 (juzicode.com)')
ax[0][0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib顯示影像為rgb格式
ax[0][1].set_title('img_close')
ax[0][1].imshow(cv2.cvtColor(img_close,cv2.COLOR_BGR2RGB))
ax[1][0].set_title('img_close_src')
ax[1][0].imshow(cv2.cvtColor(img_close_src,cv2.COLOR_BGR2RGB))
ax[1][1].set_title('img_blackhat')
ax[1][1].imshow(cv2.cvtColor(img_blackhat,cv2.COLOR_BGR2RGB))
ax[0][0].axis('off');ax[0][1].axis('off');ax[1][0].axis('off');ax[1][1].axis('off')
plt.show()
運行結果:
6、擊中擊不中
擊中擊不中變換可以用來在原圖中查找子圖,假設要查找的影像中包含了多種子圖,可以利用某個子圖構造出kernel,經過擊中擊不中變換就能在該子圖中心保留一個非零的點,注意這里構造kernel不再是使用getStructuringElement(),而是需要用子圖構造,一個構造kernel的例子如下,首先從子圖中讀取影像,然后和要做變換的原圖做一樣的閾值化,接下來構造一個和子圖大小一樣型別為np.int8型的kernel,其中子圖閾值化后值為255的位置設定為1,閾值化后值為0的位置設定為-1:
#構建kernel
img_kernel = cv2.imread('..\\samples\\picture\\hitmiss-kernel.bmp',cv2.IMREAD_GRAYSCALE)
_,img_kernel_bin = cv2.threshold(img_kernel,193,255,1) #閾值化,閾值和要做變換的原圖一致
kernel = img_kernel.astype(np.int8) #構造一個和子圖大小但是型別為int8型,可以保存負數
kernel[img_kernel_bin == 255] = 1
kernel[img_kernel_bin == 0] = -1
完整的例子如下,首先讀取原圖并進行閾值化,然后按照前面的方法構建kernel,接下來用morphologyEx()進行擊中擊不中變換,再用findNonZero()查找非零點并用circle()繪圖顯示出來:
import numpy as np
import matplotlib.pyplot as plt
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
img_src = cv2.imread('..\\samples\\picture\\hitmiss.bmp',cv2.IMREAD_GRAYSCALE)
_,img_src_bin = cv2.threshold(img_src,193,255,1)
#構建kernel
img_kernel = cv2.imread('..\\samples\\picture\\hitmiss-kernel.bmp',cv2.IMREAD_GRAYSCALE)
_,img_kernel_bin = cv2.threshold(img_kernel,193,255,1) #閾值化,閾值和要做變換的原圖一致
kernel = img_kernel.astype(np.int8)#構造一個和子圖大小但是型別為int8型,可以保存負數
kernel[img_kernel_bin == 255] = 1
kernel[img_kernel_bin == 0] = -1
#擊中擊不中變換
img_hitmiss = cv2.morphologyEx(img_src_bin,cv2.MORPH_HITMISS ,kernel,iterations=1)
print('countNonZero(img_hitmiss):',cv2.countNonZero(img_hitmiss))
#繪制中心點
locations = cv2.findNonZero(img_hitmiss)
img_hitmiss_color = cv2.cvtColor(img_hitmiss,cv2.COLOR_GRAY2BGR)
if locations is not None:
print(type(locations), locations.shape ,locations)
print('locations:',locations[0][0]) # 第2個[0]固定,第1個[0]表示找到位置的個數
center=locations[0][0][0],locations[0][0][1]
cv2.circle(img_hitmiss_color,center, 15, (0,255,255), 5)
else:
print('未擊中')
#顯示影像
fig,ax = plt.subplots(2,3)
ax[0][0].set_title('原圖 (juzicode.com)')
ax[0][0].imshow(cv2.cvtColor(img_src,cv2.COLOR_BGR2RGB)) #matplotlib顯示影像為rgb格式
ax[0][1].set_title('img_src_bin')
ax[0][1].imshow(cv2.cvtColor(img_src_bin,cv2.COLOR_BGR2RGB))
ax[0][2].set_title('img_kernel')
ax[0][2].imshow(cv2.cvtColor(img_kernel,cv2.COLOR_BGR2RGB))
ax[1][0].set_title('img_kernel_bin')
ax[1][0].imshow(cv2.cvtColor(img_kernel_bin,cv2.COLOR_BGR2RGB))
ax[1][1].set_title('img_hitmiss')
ax[1][1].imshow(cv2.cvtColor(img_hitmiss,cv2.COLOR_BGR2RGB))
ax[1][2].set_title('img_hitmiss_color')
ax[1][2].imshow(cv2.cvtColor(img_hitmiss_color,cv2.COLOR_BGR2RGB))
plt.show()
運行結果:
VX公眾號: 桔子code / juzicode.com
cv2.__version__: 4.5.3
countNonZero(img_hitmiss): 1
<class 'numpy.ndarray'> (1, 1, 2) [[[319 91]]]
locations: [319 91]
小結:開操作,閉操作,頂帽,黑帽,形態學梯度,擊中擊不中變換都是以膨脹、腐蝕為基礎,其中擊中擊不中變換需要從子圖構造kernel,而其他幾種形態學變換則用getStructuringElement()構造,
擴展閱讀:
- OpenCV-Python教程
- OpenCV-Python教程:形態學變換~腐蝕和膨脹(erode,dilate)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/295509.html
標籤:其他