![]() 圖像預(yù)處理對(duì)于整個(gè)圖像處理任務(wù)來講特別重要。如果我們沒有進(jìn)行恰當(dāng)?shù)念A(yù)處理,無論我們有多么好的數(shù)據(jù)也很難得到理想的結(jié)果。 本篇是視覺入門系列教程的第二篇。整個(gè)視覺入門系列內(nèi)容如下: 理解顏色模型與在圖像上繪制圖形(圖像處理基本操作)。 基本的圖像處理與濾波技術(shù)。 從特征檢測到人臉檢測。 圖像分割與分水嶺(Watershed)算法(TBU) 在邊緣和輪廓檢測中,噪聲對(duì)檢測的精度有很大的影響。因此,去除噪聲和控制像素值的大小可以幫助模型聚焦于整體特征,獲得更高的精度。對(duì)應(yīng)的圖像處理技術(shù)包括:模糊化(Blurring)、閾值化(thresholding)和形態(tài)轉(zhuǎn)換(morphological transformation)。本篇我們將詳細(xì)介紹這幾個(gè)常見的圖像預(yù)處理技術(shù)。(本文假設(shè)讀者已經(jīng)熟悉卷積的概念。) 模糊化(Blurring) 模糊化的目標(biāo)是實(shí)現(xiàn)降噪。我們必須格外注意的是:如果我們把邊緣檢測算法應(yīng)用到高分辨率的圖像上,我們就會(huì)得到很多我們不感興趣的檢測結(jié)果; ![]() 相反,如果我們把圖像模糊太多,我們就會(huì)丟失數(shù)據(jù)。因此,我們需要找到一個(gè)適當(dāng)?shù)哪:?,從而不失去理想的邊緣?/p> 有多種技術(shù)用于實(shí)現(xiàn)模糊效果,在這里我們討論OpenCV中常用的四種技術(shù):平均模糊(Averaging blurring)、高斯模糊(Gaussian blurring)、中值模糊(median blurring)和雙邊濾波(bilateral filtering)。這四種技術(shù)應(yīng)用一個(gè)共同的基本原理,即使用濾波器(內(nèi)核)對(duì)圖像進(jìn)行卷積運(yùn)算。不同的是,在四種模糊方法中使用的濾波器的值是不同的。 平均模糊(Average blurring)是取給定內(nèi)核(kernel)區(qū)域下所有像素值的平均值替換中心的值。例如,假設(shè)給定一個(gè)大小為5X5的內(nèi)核(kernel),我們計(jì)算卷積結(jié)果的平均值,并將結(jié)果放在給定區(qū)域的中心。示例如下: ![]() 如果我們?cè)黾觾?nèi)核的大小,像素值將更加歸一化。因此圖像也會(huì)變得越來越模糊。讓我們用下面的代碼對(duì)比處理結(jié)果。(為了便于比較,將把原始圖像加到結(jié)果中,進(jìn)行對(duì)比顯示。) # Import the image and convert to RGB img = cv2.imread('text.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Plot the image with different kernel sizes kernels = [5, 11, 17] fig, axs = plt.subplots(nrows = 1, ncols = 3, figsize = (20, 20)) for ind, s in enumerate(kernels): img_blurred = cv2.blur(img, ksize = (s, s)) ax = axs[ind] ax.imshow(img_blurred) ax.a(chǎn)xis('off') plt.show() ![]() 中值模糊(Medium blurring)和平均模糊(Average blurring)是一樣的,只是它使用的是中值而不是平均值。正由于這個(gè)特性,當(dāng)我們需要處理圖像中突然出現(xiàn)的噪音時(shí)(如“椒鹽噪音”),使用中值模糊(medium blurring)的效果要比平均模糊(average blurring)效果好。 ![]() 高斯模糊(Gaussian blurring)是使用“值”具有高斯分布的核函數(shù)。由于這些值是由高斯函數(shù)生成的,因此它的參數(shù)需要一個(gè)sigma值。如上圖,內(nèi)核的值在靠近中心的地方變高,在靠近角的地方變小。將該方法應(yīng)用于具有正態(tài)分布的噪聲,如白噪聲,效果較好。 雙邊濾波(Bilateral Filtering)是高斯模糊的一個(gè)高級(jí)版本。模糊化不僅可以溶解噪聲,而且還會(huì)平滑邊緣。而雙邊濾波器能在去除噪聲的同時(shí)保持邊緣銳化。這是由于它不僅使用高斯分布值,還同時(shí)考慮了距離和像素值的差異。因此,需要指定sigmaSpace和sigmaColor這兩個(gè)參數(shù)。 # Blur the image img_0 = cv2.blur(img, ksize = (7, 7)) img_1 = cv2.GaussianBlur(img, ksize = (7, 7), sigmaX = 0) img_2 = cv2.medianBlur(img, 7) img_3 = cv2.bilateralFilter(img, 7, sigmaSpace = 75, sigmaColor =75) # Plot the images images = [img_0, img_1, img_2, img_3] fig, axs = plt.subplots(nrows = 1, ncols = 4, figsize = (20, 20)) for ind, p in enumerate(images): ax = axs[ind] ax.imshow(p) ax.a(chǎn)xis('off') plt.show() ![]() 閾值化(Thresholding) 圖像的閾值化就是利用圖像像素點(diǎn)分布規(guī)律,設(shè)定閾值進(jìn)行像素點(diǎn)分割,進(jìn)而得到圖像的二值圖像。我們需要設(shè)置閾值和最大值,然后據(jù)此相應(yīng)地進(jìn)行像素值轉(zhuǎn)換。常用的閾值化包含有五種不同的類型:二進(jìn)制閾值化、反二進(jìn)制閾值化、閾值化到零、反閾值化到零,和閾值截?cái)唷?/strong> img = cv2.imread('gradation.png') # Thresholding _, thresh_0 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) _, thresh_1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) _, thresh_2 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO) _, thresh_3 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV) _, thresh_4 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC) # Plot the images images = [img, thresh_0, thresh_1, thresh_2, thresh_3, thresh_4] fig, axs = plt.subplots(nrows = 2, ncols = 3, figsize = (13, 13)) for ind, p in enumerate(images): ax = axs[ind//3, ind%3] ax.imshow(p) plt.show() ![]() ![]() 如上圖所示,每種類型的閾值都可以用數(shù)學(xué)公式表示,I(x, y)是像素點(diǎn)的強(qiáng)度(也稱為點(diǎn)(x, y)的像素值)。上圖中的圖像示例,可以更直觀的理解不同閾值化類型之間的區(qū)別。 只取一個(gè)閾值并將其應(yīng)用于圖像的所有部分并不能滿足我們的全部需求。如果我們有一張?jiān)诙鄠€(gè)不同區(qū)域亮度差異較多的圖片這種情況,將一個(gè)值應(yīng)用于整個(gè)圖像一般不利于我們的圖像處理任務(wù)。其對(duì)應(yīng)更好的方法是對(duì)圖像的每個(gè)部分使用不同的閾值。對(duì)應(yīng)這種情況還有另外一種閾值化技術(shù)稱為自適應(yīng)閾值化(Adaptive threshilding)。通過對(duì)圖像鄰域內(nèi)閾值的計(jì)算,可以得到不同光照條件下的較好結(jié)果。 # Convert the image to grayscale img = cv2.imread('text.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Adaptive Thresholding _, thresh_binary = cv2.threshold(img, thresh = 127, maxval = 255, type = cv2.THRESH_BINARY) adap_mean_2 = cv2.a(chǎn)daptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_M(jìn)EAN_C, cv2.THRESH_BINARY, 7, 2) adap_mean_2_inv = cv2.a(chǎn)daptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_M(jìn)EAN_C, cv2.THRESH_BINARY_INV, 7, 2) adap_mean_8 = cv2.a(chǎn)daptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_M(jìn)EAN_C, cv2.THRESH_BINARY, 7, 8) adap_gaussian_8 = cv2.a(chǎn)daptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 7, 8) 我們需要將顏色模式轉(zhuǎn)換為灰度來進(jìn)行自適應(yīng)閾值化。自適應(yīng)閾值的參數(shù)有maxValue(在上面的示例中設(shè)置為255)、adaptiveMethod、thresholdType、blocksize和C。這里使用的自適應(yīng)方法有兩種:adaptivethresholdmeanc和adaptivethresholdgaussianc。讓我們通過下方代碼對(duì)比自適應(yīng)閾值化的不同結(jié)果。 # Plot the images images = [img, thresh_binary, adap_mean_2, adap_mean_2_inv, adap_mean_8, adap_gaussian_8] fig, axs = plt.subplots(nrows = 2, ncols = 3, figsize = (15, 15)) for ind, p in enumerate(images): ax = axs[ind%2, ind//2] ax.imshow(p, cmap = 'gray') ax.a(chǎn)xis('off') plt.show() ![]() 如上圖所示,左邊為原始圖像與二進(jìn)制閾值化結(jié)果圖。對(duì)比二進(jìn)制閾值化結(jié)果圖與右上方兩張結(jié)果圖(由adaptivethresholdmeanc方法生成)可得,后者生成了更為詳細(xì)的結(jié)果。我們還可以看出,當(dāng)C值更大時(shí),圖像將變得更顯式。C代表從均值或加權(quán)均值中減去值的大小。通過觀察上圖右子圖上下兩幅圖像,我們還可以對(duì)比查看相同C值下adaptivethreshold meanc和adaptivethreshold _gaussianc兩種方法生成的不同效果圖。 梯度(Gradient) 在數(shù)學(xué)中,梯度用于幾何地表示多變量函數(shù)圖形的斜率。由于它是一個(gè)向量值函數(shù),代表著方向和大小兩種屬性。在這里,我們也可以將同樣的概念引入到圖像的像素值中。圖像梯度表示像素強(qiáng)度或顏色模式的方向變化,因此可以通過梯度來定位邊緣。 # Apply gradient filtering sobel_x = cv2.Sobel(img, cv2.CV_64F, dx = 1, dy = 0, ksize = 5) sobel_y = cv2.Sobel(img, cv2.CV_64F, dx = 0, dy = 1, ksize = 5) blended = cv2.a(chǎn)ddWeighted(src1=sobel_x, alpha=0.5, src2=sobel_y, beta=0.5, gamma=0) laplacian = cv2.Laplacian(img, cv2.CV_64F) Sobel運(yùn)算同時(shí)使用高斯平滑和微分。我們通過cv2.Sobel()函數(shù)使用它,可以定義兩個(gè)不同的方向:垂直方向(sobelx)和水平方向(sobely)。dx和dy表示導(dǎo)數(shù)。當(dāng)dx = 1時(shí),通過計(jì)算像素值沿水平方向的導(dǎo)數(shù),從而進(jìn)行圖像濾波。 通過函數(shù)cv2.a(chǎn)ddWeighted()對(duì)sobelx和sobely的兩種過濾器加權(quán)求和,可以實(shí)現(xiàn)兩個(gè)方向上的梯度求解及圖像濾波。上述代碼中兩種過濾器設(shè)定了相同的權(quán)重。 拉普拉斯運(yùn)算使用的是x和y的二階導(dǎo)數(shù),數(shù)學(xué)表達(dá)式如下。 ![]() 讓我們通過下方代碼更直觀的看看這些處理后圖像是什么樣的。 # Plot the images images = [sobel_x, sobel_y, blended, laplacian] plt.figure(figsize = (20, 20)) for i in range(4): plt.subplot(1, 4, i+1) plt.imshow(images[i], cmap = 'gray') plt.a(chǎn)xis('off') plt.show() ![]() 如上圖所示,第一幅和第二幅圖像均含有一個(gè)方向圖樣。在第一張圖中,我們可以清楚地看到垂直方向上的邊緣。在第二幅圖中,我們可以看到水平線。第三幅和第四幅圖像,兩個(gè)方向的邊緣都凸顯出來了。 形態(tài)轉(zhuǎn)換(Morpgological transformations) 通過濾波操作來轉(zhuǎn)換圖像的形態(tài)的技術(shù)稱為形態(tài)變換(morphological transformation)。首先,讓我們了解下腐蝕(erosion)和擴(kuò)張(dilation)。 腐蝕(Erosion) 是一種縮小圖形形態(tài)的技術(shù),通常被應(yīng)用在灰度圖上。過濾器的形狀可以是矩形、橢圓和交叉形狀。通過過濾器刪除給定區(qū)域下的全部0值。 ![]() 代碼實(shí)現(xiàn)如下: img = cv2.imread('simpson.jpg') # Create erosion kernels kernel_0 = np.ones((9, 9), np.uint8) kernel_1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 9)) kernel_2 = cv2.getStructuringElement(cv2.MORPH_CROSS, (9, 9)) kernels = [kernel_0, kernel_1, kernel_2] # Plot the images plt.figure(figsize = (20, 20)) for i in range(3): img_copy = img.copy() img_copy = cv2.erode(img_copy, kernels[i], iterations = 3) plt.subplot(1, 3, i+1) plt.imshow(img_copy) plt.a(chǎn)xis('off') plt.show() ![]() 上圖形象的展示出不同濾波器下的不同縮放結(jié)果。我們可以看到三張分別使用基礎(chǔ)(方形)濾波器、橢圓形濾波器和交叉濾波器處理過的結(jié)果圖。可以看出其分別以“圓形”、“線性”和“對(duì)角線”的方式進(jìn)行收縮。 擴(kuò)張(Dilation)與侵蝕是相反的。它是一種對(duì)圖形形態(tài)進(jìn)行放大的操作。其作用也與侵蝕相反。實(shí)現(xiàn)代碼如下。 # Apply dilation kernel = np.ones((9, 9), np.uint8) img_dilate = cv2.dilate(img, kernel, iterations = 3) plt.figure(figsize = (20, 10)) plt.subplot(1, 2, 1); plt.imshow(img, cmap="gray") plt.subplot(1, 2, 2); plt.imshow(img_dilate, cmap="gray") plt.show() ![]() 開閉運(yùn)算是侵蝕和擴(kuò)張的混合形式。開運(yùn)算是指先進(jìn)行侵蝕,然后對(duì)侵蝕結(jié)果進(jìn)行擴(kuò)張操作。相對(duì)應(yīng)的,閉運(yùn)算是指先進(jìn)行擴(kuò)張,再進(jìn)行侵蝕。 ![]() 正如上圖所示,閉運(yùn)算一般用于檢測圖形的整體輪廓,開運(yùn)算用于檢測圖形的子模式(subpatterns)??梢允褂煤瘮?shù)cv2.morphologyEx()來實(shí)現(xiàn)這些操作。參數(shù)op用于指定使用哪種運(yùn)算類型(開/閉)。完整代碼如下所示。 # Apply the operations kernel = np.ones((9, 9), np.uint8) img_open = cv2.morphologyEx(img, op= cv2.MORPH_OPEN, kernel) img_close = cv2.morphologyEx(img, op= cv2.MORPH_CLOSE, kernel) img_grad = cv2.morphologyEx(img, op= cv2.MORPH_GRADIENT, kernel) img_tophat = cv2.morphologyEx(img, op= cv2.MORPH_TOPHAT, kernel) img_blackhat = cv2.morphologyEx(img, op= cv2.MORPH_BLACKHAT, kernel) # Plot the images images = [img, img_open, img_close, img_grad, img_tophat, img_blackhat] fig, axs = plt.subplots(nrows = 2, ncols = 3, figsize = (15, 15)) for ind, p in enumerate(images): ax = axs[ind//3, ind%3] ax.imshow(p, cmap = 'gray') ax.a(chǎn)xis('off') plt.show() ![]() 注意,原圖中的手在分別使用開閉操作進(jìn)行處理時(shí)會(huì)產(chǎn)生不同的結(jié)果。梯度濾波(MORPHCGRADIENT)運(yùn)算是計(jì)算擴(kuò)張結(jié)果圖與腐蝕結(jié)果圖之差。頂帽(Top-h(huán)at)運(yùn)算(MORPHTOPHAT)是計(jì)算開運(yùn)算結(jié)果圖與原始圖像之差,黑帽(Black Hot)運(yùn)算(MORPH_BLACKHAT)是計(jì)算閉運(yùn)算結(jié)果圖與原始圖像之差。形態(tài)學(xué)運(yùn)算詳細(xì)介紹參看(https://homepages.inf.ed.a(chǎn)c.uk/rbf/HIPR2/morops.htm)。 總結(jié)與展望 本篇介紹了OpenCV中幾項(xiàng)比較常用的運(yùn)算。下篇將介紹輪廓檢測和人臉檢測等檢測技術(shù)。歡迎批評(píng)指正。 |
|