天天看點

opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

【前言】圖像預處理對于整個圖像處理任務來講特别重要。如果我們沒有進行恰當的預處理,無論我們有多麼好的資料也很難得到理想的結果。

本篇是視覺入門系列教程的第二篇。整個視覺入門系列内容如下:

  1. 了解顔色模型與在圖像上繪制圖形(圖像處理基本操作)。
  2. 基本的圖像處理與濾波技術。
  3. 從特征檢測到人臉檢測。
  4. 圖像分割與分水嶺(Watershed)算法(TBU)

在邊緣和輪廓檢測中,噪聲對檢測的精度有很大的影響。是以,去除噪聲和控制像素值的大小可以幫助模型聚焦于整體特征,獲得更高的精度。對應的圖像處理技術包括:模糊化(Blurring)、門檻值化(thresholding)和形态轉換(morphological transformation)。本篇我們将詳細介紹這幾個常見的圖像預處理技術。(本文假設讀者已經熟悉卷積的概念。)

模糊化(Blurring)

模糊化的目标是實作降噪。我們必須格外注意的是:如果我們把邊緣檢測算法應用到高分辨率的圖像上,我們就會得到很多我們不感興趣的檢測結果;

opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

相反,如果我們把圖像模糊太多,我們就會丢失資料。是以,我們需要找到一個适當的模糊量,進而不失去理想的邊緣。

有多種技術用于實作模糊效果,在這裡我們讨論OpenCV中常用的四種技術:

平均模糊(Averaging blurring)、高斯模糊(Gaussian blurring)、中值模糊(median blurring)和雙邊濾波(bilateral filtering)

。這四種技術應用一個共同的基本原理,即使用濾波器(核心)對圖像進行卷積運算。不同的是,在四種模糊方法中使用的濾波器的值是不同的。

平均模糊(Average blurring)

是取給定核心(kernel)區域下所有像素值的平均值替換中心的值。例如,假設給定一個大小為5X5的核心(kernel),我們計算卷積結果的平均值,并将結果放在給定區域的中心。示例如下:

opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

如果我們增加核心的大小,像素值将更加歸一化。是以圖像也會變得越來越模糊。讓我們用下面的代碼對比處理結果。(為了便于比較,将把原始圖像加到結果中,進行對比顯示。)

# 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.axis('off')
plt.show()
           
opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)
中值模糊(Medium blurring)

和平均模糊(Average blurring)是一樣的,隻是它使用的是中值而不是平均值。正由于這個特性,當我們需要處理圖像中突然出現的噪音時(如“椒鹽噪音”),使用中值模糊(medium blurring)的效果要比平均模糊(average blurring)效果好。

opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)
高斯模糊(Gaussian blurring)

是使用“值”具有高斯分布的核函數。由于這些值是由高斯函數生成的,是以它的參數需要一個sigma值。如上圖,核心的值在靠近中心的地方變高,在靠近角的地方變小。将該方法應用于具有正态分布的噪聲,如白噪聲,效果較好。

雙邊濾波(Bilateral Filtering)

是高斯模糊的一個進階版本。模糊化不僅可以溶解噪聲,而且還會平滑邊緣。而雙邊濾波器能在去除噪聲的同時保持邊緣銳化。這是由于它不僅使用高斯分布值,還同時考慮了距離和像素值的差異。是以,需要指定sigmaSpace和sigmaColor這兩個參數。

# 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.axis('off')
plt.show()
           
opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

門檻值化(Thresholding)

圖像的門檻值化就是利用圖像像素點分布規律,設定門檻值進行像素點分割,進而得到圖像的二值圖像。我們需要設定門檻值和最大值,然後據此相應地進行像素值轉換。常用的門檻值化包含有五種不同的類型:

二進制門檻值化、反二進制門檻值化、門檻值化到零、反門檻值化到零,和門檻值截斷。
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()
           
opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)
opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

如上圖所示,每種類型的門檻值都可以用數學公式表示,I(x, y)是像素點的強度(也稱為點(x, y)的像素值)。上圖中的圖像示例,可以更直覺的了解不同門檻值化類型之間的差別。

隻取一個門檻值并将其應用于圖像的所有部分并不能滿足我們的全部需求。如果我們有一張在多個不同區域亮度差異較多的圖檔這種情況,将一個值應用于整個圖像一般不利于我們的圖像處理任務。其對應更好的方法是對圖像的每個部分使用不同的門檻值。對應這種情況還有另外一種門檻值化技術稱為

自适應門檻值化(Adaptive threshilding)

。通過對圖像鄰域内門檻值的計算,可以得到不同光照條件下的較好結果。

# 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.adaptiveThreshold(img, 255, 
                                    cv2.ADAPTIVE_THRESH_MEAN_C, 
                                    cv2.THRESH_BINARY, 7, 2)
adap_mean_2_inv = cv2.adaptiveThreshold(img, 255, 
                                        cv2.ADAPTIVE_THRESH_MEAN_C, 
                                        cv2.THRESH_BINARY_INV, 7, 2)
adap_mean_8 = cv2.adaptiveThreshold(img, 255, 
                                    cv2.ADAPTIVE_THRESH_MEAN_C, 
                                    cv2.THRESH_BINARY, 7, 8)
adap_gaussian_8 = cv2.adaptiveThreshold(img, 255, 
                                    cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 
                                    cv2.THRESH_BINARY, 7, 8)
           

我們需要将顔色模式轉換為灰階來進行自适應門檻值化。自适應門檻值的參數有maxValue(在上面的示例中設定為255)、adaptiveMethod、thresholdType、blocksize和C。這裡使用的自适應方法有兩種:adaptive_threshold_mean_c和adaptive_threshold_gaussian_c。讓我們通過下方代碼對比自适應門檻值化的不同結果。

# 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.axis('off')
plt.show()
           
opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

如上圖所示,左邊為原始圖像與二進制門檻值化結果圖。對比二進制門檻值化結果圖與右上方兩張結果圖(由adaptive_threshold_mean_c方法生成)可得,後者生成了更為詳細的結果。我們還可以看出,當C值更大時,圖像将變得更顯式。C代表從均值或權重均值中減去值的大小。通過觀察上圖右子圖上下兩幅圖像,我們還可以對比檢視相同C值下adaptive_threshold _mean_c和adaptive_threshold _gaussian_c兩種方法生成的不同效果圖。

梯度(Gradient)

在數學中,梯度用于幾何地表示多變量函數圖形的斜率。由于它是一個向量值函數,代表着方向和大小兩種屬性。在這裡,我們也可以将同樣的概念引入到圖像的像素值中。圖像梯度表示像素強度或顔色模式的方向變化,是以可以通過梯度來定位邊緣。

# 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.addWeighted(src1=sobel_x, alpha=0.5, src2=sobel_y,
                          beta=0.5, gamma=0)
laplacian = cv2.Laplacian(img, cv2.CV_64F)
           

Sobel運算同時使用高斯平滑和微分。我們通過cv2.Sobel()函數使用它,可以定義兩個不同的方向:垂直方向(sobel_x)和水準方向(sobel_y)。dx和dy表示導數。當dx = 1時,通過計算像素值沿水準方向的導數,進而進行圖像濾波。

通過函數cv2.addWeighted()對sobel_x和sobel_y的兩種過濾器權重求和,可以實作兩個方向上的梯度求解及圖像濾波。上述代碼中兩種過濾器設定了相同的權重。

拉普拉斯運算

使用的是x和y的二階導數,數學表達式如下。

opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

讓我們通過下方代碼更直覺的看看這些處理後圖像是什麼樣的。

# 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.axis('off')
plt.show()
           
opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

如上圖所示,第一幅和第二幅圖像均含有一個方向圖樣。在第一張圖中,我們可以清楚地看到垂直方向上的邊緣。在第二幅圖中,我們可以看到水準線。第三幅和第四幅圖像,兩個方向的邊緣都凸顯出來了。

形态轉換(Morpgological transformations)

通過濾波操作來轉換圖像的形态的技術稱為

形态變換(morphological transformation)

。首先,讓我們了解下腐蝕(erosion)和擴張(dilation)。

腐蝕(Erosion)

是一種縮小圖形形态的技術,通常被應用在灰階圖上。過濾器的形狀可以是矩形、橢圓和交叉形狀。通過過濾器删除給定區域下的全部0值。

opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

代碼實作如下:

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.axis('off')
plt.show()
           
opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

上圖形象的展示出不同濾波器下的不同縮放結果。我們可以看到三張分别使用基礎(方形)濾波器、橢圓形濾波器和交叉濾波器處理過的結果圖。可以看出其分别以“圓形”、“線性”和“對角線”的方式進行收縮。

擴張(Dilation)

與侵蝕是相反的。它是一種對圖形形态進行放大的操作。其作用也與侵蝕相反。實作代碼如下。

# 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()
           
opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

開閉運算是侵蝕和擴張的混合形式。開運算是指先進行侵蝕,然後對侵蝕結果進行擴張操作。相對應的,閉運算是指先進行擴張,再進行侵蝕。

opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

正如上圖所示,閉運算一般用于檢測圖形的整體輪廓,開運算用于檢測圖形的子模式(subpatterns)。可以使用函數cv2.morphologyEx()來實作這些操作。參數op用于指定使用哪種運算類型(開/閉)。完整代碼如下所示。

# 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.axis('off')
plt.show()
           
opencv 自适應門檻值化_使用Python+OpenCV進行圖像處理(二)

注意,原圖中的手在分别使用開閉操作進行處理時會産生不同的結果。梯度濾波(MORPH_CGRADIENT)運算是計算擴張結果圖與腐蝕結果圖之差。頂帽(Top-hat)運算(MORPH_TOPHAT)是計算開運算結果圖與原始圖像之差,黑帽(Black Hot)運算(MORPH_BLACKHAT)是計算閉運算結果圖與原始圖像之差。形态學運算詳細介紹參看(https://homepages.inf.ed.ac.uk/rbf/HIPR2/morops.htm)。

總結與展望

本篇介紹了OpenCV中幾項比較常用的運算。下篇将介紹輪廓檢測和人臉檢測等檢測技術。歡迎批評指正。

繼續閱讀