OpenCV-Python實戰(5)——OpenCV影像運算(??萬字長文,含大量示例??)
- 0. 前言
- 1. 飽和運算
- 2. 影像加減法與影像混合
- 3. 按位運算
- 4. 形態變換
- 4.1 膨脹運算與腐蝕運算
- 4.2 開運算與閉運算
- 4.3 形態梯度運算
- 4.4 頂帽運算與低帽(黑帽)運算
- 4.5 結構元素
- 4.6 應用形態變換
- 總結
- 相關鏈接
0. 前言
影像處理技術是計算機視覺專案的核心,通常是計算機視覺專案中的關鍵工具,可以使用它們來完成各種計算機視覺任務,因此,如果要構建計算機視覺專案,就需要對影像處理有足夠的了解,影像運算也是影像處理技術的一種,在本文中,將介紹可以對影像執行的常見算術運算,例如按位運算、加減法、形態變換等,
1. 飽和運算
飽和運算( saturated operation )是一種算術運算,其通過限制運算可以采用的最大值和最小值將運算限制在固定范圍內,例如,對影像的某些操作(例如插值等)可能會產生超出可用范圍的值,使用飽和運算就可以解決這個問題,
例如,要存盤影像 img,它是對 8 位影像(值范圍為 0 到 255 )執行特定操作的結果,則飽和運算計算公式如下:
r
e
s
u
l
t
(
x
,
y
)
=
m
i
n
(
m
a
x
(
r
o
u
n
d
(
i
m
g
)
,
0
)
,
255
)
result(x,y)=min(max(round(img),0),255)
result(x,y)=min(max(round(img),0),255)
通過以下簡單示例以更好的理解:
x = np.uint8([250])
y = np.uint8([50])
# OpenCV中加法:250+50 = 300 => 255:
result_opencv = cv2.add(x, y)
print("cv2.add(x:'{}' , y:'{}') = '{}'".format(x, y, result_opencv))
# Numpy中加法:250+50 = 300 % 256 = 44:
result_numpy = x + y
print("x:'{}' + y:'{}' = '{}'".format(x, y, result_numpy))
在 OpenCV 中,這些值會被裁剪至 [0, 255] 范圍內,這種運算就稱為飽和操作,而在 NumPy 中,值是環繞的,這也稱為模運算,
2. 影像加減法與影像混合
影像加法和減法可以分別使用 cv2.add() 和 cv2.subtract() 函式執行,這些函式兩個陣列執行逐元素求和/相減,也可用于對陣列和標量求和/相減,例如,對影像的所有像素添加 60,首先要構建影像以添加到原始影像:
M = np.ones(image.shape, dtype="uint8") * 60
然后,使用以下代碼執行加法或減法:
added_image = cv2.add(image, M)
subtracted_image = cv2.subtract(image, M)
也可以創建一個標量并將其添加到原始影像中,例如,要給影像的所有像素加上 110,首先構建標量:
scalar = np.ones((1, 3), dtype="float") * 110
然后,使用以下代碼執行加法或減法:
added_image_2 = cv2.add(image, scalar)
subtracted_image_2 = cv2.subtract(image, scalar)
代碼的執行結果如下圖所示:

自上圖中,可以清楚地看到加減預定義值的效果,
影像混合也是影像相加的一種,只是其可以賦予相加以影像不同的權重,可以得到類似透明的效果,可以使用 cv2.addWeighted() 函式進行影像混合,
接下來,結合 Sobel 算子來觀察 cv2.addWeighted() 函式效果,
Sobel 算子用于邊緣檢測,它創建一個檢測到圖中邊緣的影像, Sobel 算子使用兩個 3 × 3 核,它們與原始影像卷積以計算導數的近似值,捕獲水平和垂直梯度:
# 輸出深度設定為CV_16S,以避免溢位
# CV_16S是由2位元組有符號整數(16位有符號整數)組成的通道
gradient_x = cv2.Sobel(gray_image, cv2.CV_16S, 1, 0, 3)
gradient_y = cv2.Sobel(gray_image, cv2.CV_16S, 0, 1, 3)
在計算出水平和垂直梯度后,可以使用函式 cv2.addWeighted() 將它們混合成影像,如下所示:
abs_gradient_x = cv2.convertScaleAbs(gradient_x)
abs_gradient_y = cv2.convertScaleAbs(gradient_y)
# 使用相同的權重混合兩個影像
sobel_image = cv2.addWeighted(abs_gradient_x, 0.5, abs_gradient_y, 0.5, 0)
最后繪制影像:
def show_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(1, 4, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
plt.figure(figsize=(10, 4))
plt.suptitle("Sobel operator and cv2.addWeighted() to show the output", fontsize=14, fontweight='bold')
show_with_matplotlib(image, "Image", 1)
show_with_matplotlib(cv2.cvtColor(abs_gradient_x, cv2.COLOR_GRAY2BGR), "Gradient x", 2)
show_with_matplotlib(cv2.cvtColor(abs_gradient_y, cv2.COLOR_GRAY2BGR), "Gradient y", 3)
show_with_matplotlib(cv2.cvtColor(sobel_image, cv2.COLOR_GRAY2BGR), "Sobel output", 4)
# Show the Figure:
plt.show()
代碼的運行結果如下圖所示:

3. 按位運算
OpenCV 中包含一些操作可以使用按位運算子在位級別執行,這些按位運算很簡單,計算速度很快,因此,它們也是處理影像時的有用工具,
按位運算包括 AND、OR、NOT 和 XOR,
為了進行演示按位運算,我們首先創建一些影像:
img_1 = np.zeros((300, 300), dtype='uint8')
cv2.rectangle(img_1, (10, 10), (110, 110), (255, 255, 255), -1)
cv2.circle(img_1, (200, 200), 50, (255, 255, 255), -1)
img_2 = np.zeros((300, 300), dtype='uint8')
cv2.rectangle(img_2, (50, 50), (150, 150), (255, 255, 255), -1)
cv2.circle(img_2, (225, 200), 50, (255, 255, 255), -1)
image = cv2.imread('sigonghuiye.jpeg')
image = cv2.resize(image,(300, 300))
img_3 = np.zeros((300, 300), dtype="uint8")
cv2.circle(img_3, (150, 150), 150, (255, 255, 255), -1)
然后對所創建的影像執行按位運算:
# OR
bitwise_or = cv2.bitwise_or(img_1, img_2)
# AND
bitwise_and = cv2.bitwise_and(img_1, img_2)
# XOR
bitwise_xor = cv2.bitwise_xor(img_1, img_2)
# NOT
bitwise_not_1 = cv2.bitwise_not(img_1)
bitwise_not_2 = cv2.bitwise_not(img_2)
# AND with mask
bitwise_and_example = cv2.bitwise_and(image, image, mask=img_3)
顯示運算結果:

接下來,我們使用真實影像,進一步使用按位運算,需要注意的是,加載的真實影像應該具有相同的形狀:
image = cv2.imread('8.png')
binary_image = cv2.imread('250.png')
image = image[250:500,170:420]
bitwise_and = cv2.bitwise_and(image, binary_image)
bitwise_or = cv2.bitwise_or(image, binary_image)
bitwise_xor = cv2.bitwise_xor(image, binary_image)
代碼的運行結果如下,可以看到執行按位運算后看到生成的影像:

4. 形態變換
形態變換( Morphological transformations )通常是在二值影像上執行、基于影像形狀的操作,其具體的操作由核結構元素決定,它決定了操作的性質,膨脹和腐蝕是形態變換領域的兩個基本算子,此外,開運算和閉運算是兩個重要的運算,它們可以通過上述兩個運算(膨脹和腐蝕)獲得,最后,還有其他三個常用的變換操作,是基于之前的一些操作的變體或結合,
4.1 膨脹運算與腐蝕運算
二值影像的膨脹運算的主要作用是逐漸擴大前景物件的邊界區域,這意味著前景物件的區域會變大,而這些區域內的孔會縮小:
dilation = cv2.dilate(image, kernel, iterations=1)
腐蝕操作對二值影像的主要作用是逐漸侵蝕掉前景物件的邊界區域,這意味著前景物件的區域會變小,而這些區域內的空洞會變大:
erosion = cv2.erode(image, kernel, iterations=1)
接下來,使用膨脹運算與腐蝕運算:
image_names = ['test1.png', 'test2.png', 'test3.png']
path = 'morpho_test_imgs'
kernel_size_3_3 = (3, 3)
kernel_size_5_5 = (5, 5)
def load_all_test_images():
test_morph_images = []
for index_image, name_image in enumerate(image_names):
image_path = os.path.join(path, name_image)
test_morph_images.append(cv2.imread(image_path))
return test_morph_images
def show_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(3, 3, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
def erode(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
erosion = cv2.erode(image, kernel, iterations=1)
return erosion
def dilate(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
dilation = cv2.dilate(image, kernel, iterations=1)
return dilation
test_images = load_all_test_images()
for index_image,image in enumerate(test_images):
show_with_matplotlib(image, 'test img_{}'.format(index_image + 1), index_image * 3 + 1)
img_1 = erode(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_1, 'erode_{}'.format(index_image + 1), index_image * 3 + 2)
img_2 = dilate(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_2, 'dilate_{}'.format(index_image + 1), index_image * 3 + 3)

4.2 開運算與閉運算
開運算先執行腐蝕,然后使用相同的結構元素(或核)進行膨脹,通過這種方式,可以應用腐蝕來消除一小組不需要的像素(例如,椒鹽噪聲),
腐蝕會不分青紅皂白地影響影像的所有區域,通過在腐蝕后執行擴張操作,可以減少腐蝕過度的一些影響:
opening = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
閉預算同樣也可以從腐蝕和膨脹操作中推匯出來,該操作先執行膨脹,然后執行腐蝕,膨脹操作通常用于填充影像中的小孔,然而,膨脹操作也會使一小群噪聲像素變大,通過在膨脹后對影像應用腐蝕操作,將減少膨脹帶來的這種影響:
closing = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
接下來,實際使用開運算與閉運算:
# build_kernel() 和 show_with_matplotlib() 函式與4.1中相同
def closing(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
clos = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
return clos
def opening(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
ope = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
return ope
for index_image,image in enumerate(test_images):
show_with_matplotlib(image, 'test img_{}'.format(index_image + 1), index_image * 3 + 1)
img_1 = closing(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_1, 'closing_{}'.format(index_image + 1), index_image * 3 + 2)
img_2 = opening(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_2, 'opening_{}'.format(index_image + 1), index_image * 3 + 3)
plt.show()

4.3 形態梯度運算
形態梯度運算定義為輸入影像的膨脹和腐蝕之間的差異:
morph_gradient = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel)
形態梯度運算的用法:
# build_kernel() 函式與4.1中相同
def show_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(2, 3, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
def morphological_gradient(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
morph_gradient = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel)
return morph_gradient
for index_image,image in enumerate(test_images):
print(index_image)
show_with_matplotlib(image, 'test img_{}'.format(index_image + 1), index_image + 1)
img = morphological_gradient(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img, 'gradient_{}'.format(index_image + 1), index_image + 4)

4.4 頂帽運算與低帽(黑帽)運算
頂帽運算被定義為輸入影像和影像開運算之間的差:
top_hat = cv2.morphologyEx(image, cv2.MORPH_TOPHAT, kernel)
黑帽操作被定義為輸入影像和輸入影像閉運算的差:
black_hat = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)
頂帽運算與低帽運算的用法:
# build_kernel() 和 show_with_matplotlib() 函式與4.1中相同
def black_hat(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
black = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)
return black
def opening_and_closing(image, kernel_type, kernel_size):
opening_img = opening(image, kernel_type, kernel_size)
closing_img = closing(opening_img, kernel_type, kernel_size)
return closing_img
for index_image,image in enumerate(test_images):
show_with_matplotlib(image, 'test img_{}'.format(index_image + 1), index_image * 3 + 1)
img_1 = top_hat(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_1, 'top_hat_{}'.format(index_image + 1), index_image * 3 + 2)
img_2 = black_hat(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_2, 'black_hat_{}'.format(index_image + 1), index_image * 3 + 3)
plt.show()

4.5 結構元素
OpenCV 提供了 cv2.getStructuringElement() 函式用于構造結構元素,此函式輸出所需的核( uint8 型別的 NumPy 陣列),該函式接收兩個引數——核的形狀和大小, OpenCV 中提供了以下三種核形狀:
| 核形狀 | 說明 |
|---|---|
| cv2.MORPH_RECT | 矩形核 |
| cv2.MORPH_ELLIPSE | 橢圓核 |
| cv2.MORPH_CROSS | 十字形核 |
4.6 應用形態變換
可以使用不同的核大小和形狀、形態變換和影像,測驗不同的核形狀和大小,例如,下圖是使用核大小 (3, 3) 和矩形核 (cv2.MORPH_RECT) 時的輸出:

使用核大小 (5, 5) 和矩形核 (cv2.MORPH_RECT) 時的輸出:

使用核大小 (3, 3) 和十字形核 (cv2.MORPH_CROSS) 時的輸出:

使用核大小 (5, 5) 和十字形核 (cv2.MORPH_CROSS) 時的輸出:

在預處理影像時,形態學操作是一種非常有用的技術,可以使用形態學操作消除一些干擾影像正確處理的噪聲,或者處理影像結構中的缺陷,
總結
影像運算也是一種常見的影像處理技術,在本文中,介紹了可用于 對影像執行的常見算術運算,例如按位運算、加減法、形態變換等,
相關鏈接
OpenCV-Python實戰(1)——OpenCV簡介與影像處理基礎(內含大量示例,📕建議收藏📕)
OpenCV-Python實戰(2)——影像與視頻檔案的處理(兩萬字詳解,?📕建議收藏📕)
OpenCV-Python實戰(3)——OpenCV中繪制圖形與文本(萬字總結,?📕建議收藏📕)
OpenCV-Python實戰(4)——OpenCV常見影像處理技術(??萬字長文,含大量示例??)
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/300522.html
標籤:其他
上一篇:VS | 一些小細節
