如果需要處理的原圖及程式碼,請移步小編的GitHub地址
傳送門:請點選我
如果點選有誤:https://github.com/LeBron-Jian/ComputerVisionPractice
在OpenCV中我們經常會遇到一個名字:Mask(掩膜)。很多函式都使用到它,那麼這個Mask到底是什麼呢,下面我們從影像基本運算開始,一步一步學習掩膜。
1,影像算術運算
影像的算術運算有很多種,比如兩幅影像可以相加,相減,相乘,相除,位運算,平方根,對數,絕對值等;影像也可以放大,縮小,旋轉,還可以擷取其中的一部分作為ROI(感興趣區域)進行操作,各個顏色通道還可以分別提取對各個顏色通道進行各種運算操作。總之,對影像可以進行的算術運算非常的多。這裡先學習圖片間的數學運算,影像混合,按位運算。
1.1 圖片加法
要疊加兩張圖片,可以用 cv2.add() 函式,相加兩幅圖片的形狀(高度/寬度/通道數)必須相同, numpy中可以用 res = img1 + img2 相加,但這兩者的結果並不相同。
x = np.uint8([250]) y = np.uint8([10]) print(cv2.add(x, y)) # 250+10 = 260 => 255 print(x + y) # 250+10 = 260 % 256 = 4
如果是二值化圖片(只有0和255),兩者結果是一樣的(用 numpy的方式更簡便一些)。
這裡我們代入影像中看一下:
#encoding:utf-8 import cv2 import numpy as np import matplotlib.pyplot as plt # 舉一個極端的例子,真的只是運氣好,遇到了。。。。 img = cv2.imread('lena.jpg') img_add = img + 10 img_add2 = cv2.add(img, img_add) print(img[0:4, :, 0]) print(img_add[0:4, :, 0]) print(img_add2[0:4, :, 0]) ''' 這個是 logo1.jpg 的效果 [[246 246 246 ... 246 246 246] [246 246 246 ... 246 246 246] [246 246 246 ... 246 246 246] [246 246 246 ... 246 246 246]] [[0 0 0 ... 0 0 0] [0 0 0 ... 0 0 0] [0 0 0 ... 0 0 0] [0 0 0 ... 0 0 0]] [[246 246 246 ... 246 246 246] [246 246 246 ... 246 246 246] [246 246 246 ... 246 246 246] [246 246 246 ... 246 246 246]] 這個是 lena.jpg 的效果 [[126 125 124 ... 128 120 90] [127 126 124 ... 135 131 96] [124 123 121 ... 144 138 96] [116 119 116 ... 73 56 35]] [[136 135 134 ... 138 130 100] [137 136 134 ... 145 141 106] [134 133 131 ... 154 148 106] [126 129 126 ... 83 66 45]] [[255 255 255 ... 255 250 190] [255 255 255 ... 255 255 202] [255 255 252 ... 255 255 202] [242 248 242 ... 156 122 80]] # 我們發現 使用numpy庫的加法,則運算結果取模 使用opencv的add()函式,則運算結果當大於255,則取255 '''
注意:OpenCV中的加法與Numpy的加法是有所不同的,OpenCV的加法是一種飽和操作,而Numpy的加法是一種模操作。
Numpy庫的加法
其運算方法是:目標影像 = 影像1 + 影像2,運算結果進行取模運算
- 當畫素值 小於等於 255 時,結果為:“影像1 + 影像2”,例如:120+48=168
- 當畫素值 大於255 時,結果為:對255取模的結果,例如:(255 + 64) % 255 = 64
OpenCV的加法
其運算方法是:目標影像 = cv2.add(影像1, 影像2)
- 當畫素值 小於等於 255 時,結果為:“影像1 + 影像2”,例如:120+48=168
- 當畫素值 大於255 時,結果為:255,例如:255 + 64 = 255
兩種方法對應的程式碼如下:
# encoding:utf-8 import cv2 import numpy as np import matplotlib.pyplot as plt # 讀取圖片 img = cv2.imread('logo1.jpg') test = img # 方法一:Numpy加法運算 result1 = img + test # 方法二:OpenCV加法運算 result2 = cv2.add(img, test) all_pic = np.column_stack((img, result1, result2)) # 顯示影像 cv2.imshow('img result1 result2', all_pic) # cv2.imshow("original", img) # cv2.imshow("result1", result1) # cv2.imshow("result2", result2) # 等待顯示 cv2.waitKey(0) cv2.destroyAllWindows()
原圖及其效果圖如下:
其中,result1為Numpy的方法,result2為OpenCV的方法。
1.2 影像混合
影像融合通常是指將2張或者兩張以上的影像資訊融合到1張影像上,融合的影像含有更多的資訊,能夠更方便人們觀察或計算機處理。
影像融合是在影像加法的基礎上增加了係數和亮度調節量。
影像融合:目標影像 = 影像1*係數1 + 影像2*係數2 + 亮度調節量
影像混合 cv2.addWeighted() 也是一種圖片相加的操作,只不過兩幅圖片的權重不一樣, y 相當於一個修正值:
dst = α*img1 + β*img2 + γ
PS:當 alpha 和 beta 都等於1,則相當於圖片相加。
程式碼如下:
import cv2 import numpy as np img1 = cv2.imread('lena_small.jpg') img2 = cv2.imread('opencv_logo_white.jpg') # print(img1.shape, img2.shape) # (187, 186, 3) (184, 193, 3) img2 = cv2.resize(img2, (186, 187)) # print(img1.shape, img2.shape) res = cv2.addWeighted(img1, 0.6, img2, 0.4, 0) cv2.imshow("res", res) cv2.waitKey(0) cv2.destroyAllWindows()
注意這裡,兩張圖片的尺寸必須一致。原圖和結果圖如下:
1.3 影像矩陣減法
影像矩陣減法與加法其實類似,我們這不多做說明,只貼函式:
函式原型:cv2.subtract(src1, src2, dst=None, mask=None, dtype=None) src1:影像矩陣1 src1:影像矩陣2 dst:預設選項 mask:預設選項 dtype:預設選項
1.4 按位運算
按位操作有:AND ,OR, NOT,XOR 等。cv2.bitwise_and(), cv2.bitwise_not(), cv2.bitwise_or(), cv2.bitwise_xor()分別執行按位與/或/非/異或運算。下面我們貼一下opencv中的函式
bitwise_or—影像或運算 函式原型:cv2.bitwise_or(src1, src2, dst=None, mask=None) src1:影像矩陣1 src1:影像矩陣2 dst:預設選項 mask:預設選項 bitwise_xor—影像異或運算 函式原型:bitwise_xor(src1, src2, dst=None, mask=None) src1:影像矩陣1 src1:影像矩陣2 dst:預設選項 mask:預設選項 bitwise_not—影像非運算 函式原型:bitwise_not(src1, src2, dst=None, mask=None) src1:影像矩陣1 src1:影像矩陣2 dst:預設選項 mask:預設選項
掩膜就是用來對圖片進行全域性或區域性的遮擋,當我們提取影像的一部分,選擇非矩陣ROI時這些操作會很有用,常用於Logo投射。
通過 threshold 函式將圖片固定閾值二值化(影像二值化定義:將影像上的畫素點的灰度值設定為0或255,也就是將整個影像呈現出明顯的黑和白的視覺效果)
一幅影像包括目標物體,背景還有噪聲,要想從多值的數字影像中直接提取出目標物體,常用的方法就是設定一個閾值T,用 T 將影像的資料分為兩部分:大於 T 的畫素群和小於 T 的畫素群。這是研究灰度變換的最特殊的方法,稱為影像二值化(Binarization)
下面做一個例子,關於Logo投射。(下面首先展示兩張照片,一張原圖,一張logo圖,目的是投射logo到原圖上)
思路如下:我們的目的是把 logo 放在左邊,所以我們只關心這一塊區域,下面我們的目的是建立掩碼(這是在Logo圖上),並且保留除了logo以外的背景(這是在原圖),然後進行融合(這是在原圖),最後融合放在原圖。
程式碼如下:
# _*_coding:utf-8_*_ import cv2 import numpy as np img_photo = cv2.imread('james.jpg') img_logo = cv2.imread('logo1.jpg') print(img_logo.shape, img_photo.shape) # (615, 327, 3) (640, 1024, 3) rows, cols, channels = img_logo.shape photo_roi = img_photo[0:rows, 0:cols] gray_logo = cv2.cvtColor(img_logo, cv2.COLOR_BGR2GRAY) # 中值濾波 midian_logo = cv2.medianBlur(gray_logo, 5) # mask_bin 是黑白掩膜 ret, mask_bin = cv2.threshold(gray_logo, 127, 255, cv2.THRESH_BINARY) # mask_inv 是反色黑白掩膜 mask_inv = cv2.bitwise_not(mask_bin) # 黑白掩膜 和 大圖切割區域 去取和 img_photo_bg_mask = cv2.bitwise_and(photo_roi, photo_roi, mask=mask_bin) # 反色黑白掩膜 和 logo 取和 img2_photo_fg_mask = cv2.bitwise_and(img_logo, img_logo, mask=mask_inv) dst = cv2.add(img_photo_bg_mask, img2_photo_fg_mask) img_photo[0:rows, 0:cols] = dst cv2.imshow("mask_bin", mask_bin) cv2.imshow("mask_inv", mask_inv) cv2.imshow("img_photo_bg_mask", img_photo_bg_mask) cv2.imshow("img2_photo_fg_mask", img2_photo_fg_mask) cv2.imshow("img_photo", img_photo) cv2.waitKey(0) cv2.destroyAllWindows()
圖示過程如下:
下面第一張是黑色是因為 背景圖中 ,左邊就是黑色,所以這裡不顯示而已。
最終形態如下:
2,掩膜(mask)
在有些影像處理的函式中有的引數裡面會有 mask 引數,即此函式支援掩膜操作。
首先我們要理解什麼是掩膜?,其次掩膜有什麼作用呢?
2.1 掩膜(mask)的概念
簡單來說:掩膜是用一副二值化圖片對另外一幅圖片進行區域性的遮擋。
首先我們從物理的角度來看看 mask 到底是什麼過程。
數字影像處理中的掩膜的概念是借鑑於 PCB 製版的過程,在半導體制作中,許多晶片工藝步驟採用光刻技術,用於這些步驟的圖形”底片”稱為掩膜(也稱為“掩模”),其作用是:在矽片上選定的區域中對一個不透明的圖形模板遮蓋,繼而下面的腐蝕或擴散將隻影響選定的區域意外的區域。
圖形掩膜(Image mask)與其類似,用選定的圖形,圖形或物體,對處理的影像(全部或區域性)進行遮擋,來控制影像處理的區域或處理過程。用於覆蓋的特點影像或物體稱為掩膜或模板。光學影像處理中,掩膜可以足膠片,濾光片等。掩膜是由0和1組成的一個二進位制影像。當在某一功能中應用掩膜時,1值區域被處理,被遮蔽的0值區域不被包括在計算中。通過制定的資料值,資料範圍,有限或無限值,感興趣區和註釋檔案來定義影像掩膜,也可以應用上述選項的任意組合作為輸入來建立掩膜。
2.2 掩膜的作用
數字影像處理中,掩膜為二維矩陣陣列,有時也用多值影像,影像掩膜主要用於:
- 1,提取感興趣區,用預先製作的感興趣區掩膜與待處理影像相乘,得到感興趣區影像,感興趣區內影像值保持不變,而區外影像值都為零。
- 2,遮蔽作用,用掩膜對影像上某些區域做遮蔽,使其不參加處理或不參加處理引數的計算,或僅對遮蔽區做處理或統計。
- 3,結構特徵提取,用相似性變數或影像匹配方法檢測和提取影像中與掩膜相似的結構特徵。
- 4,特殊性質影像的製作
掩膜是一種影像濾鏡的模板,試用掩膜經常處理的是遙感影像。當提取道路或者河流,或者房屋時,通過一個 N*N 的矩陣來對影像進行畫素過濾,然後將我們需要的地物或者標誌突出顯示出來,這個矩陣就是一種掩膜。在OpenCV中,掩膜操作時相對簡單的。大致的意思是,通過一個掩膜矩陣,重新計算影像中的每一個畫素值。掩膜矩陣控制了舊影像當前位置以及周圍位置畫素對新影像當前位置畫素值的影響力度。用數學術語將,即我們自定義一個權重表。
在所有影像基本運算的操作函式中,凡是帶有掩膜(mask)的處理函式,其掩膜都參與運算(輸入影像運算完之後再與掩膜影像或矩陣運算)。
2.3 通過掩膜操作實現影像對比圖的改變
矩陣的掩膜操作非常簡單,根據掩膜來重新計算每個畫素的畫素值,掩膜(mask)也被稱為核心。
什麼是圖和掩膜的與運算呢?
其實就是原圖中的每個畫素和掩膜中的每個對應畫素進行與運算。比如1 & 1 = 1;1 & 0 = 0;
比如一個 3*3 的影像與 3*3 的掩膜進行運算,得到的結果影像就是:
說白了,mask就是點陣圖,來選擇哪個畫素允許拷貝,哪個畫素不允許拷貝,如果mask畫素的值時非0的,我們就拷貝它,否則不拷貝。
2.4 mask小結
1,影像中,各種位運算,比如與,或,非運算與普通的位運算類似。
2,如果用一句話總結,掩膜就是兩幅影像之間進行的各種位運算操作。
程式碼:
#_*_coding:utf-8_*_ import cv2 import numpy as np def mask_processing(path): image = cv2.imread(path) # 讀圖 # cv2.imshow("Oringinal", image) #顯示原圖 print(image.shape[:2]) # (613, 440) # 輸入影像是RGB影像,故構造一個三維陣列,四個二維陣列是mask四個點的座標, site = np.array([[[300, 280], [150, 280], [150, 50], [300, 50]]], dtype=np.int32) im = np.zeros(image.shape[:2], dtype="uint8") # 生成image大小的全白圖 cv2.polylines(im, site, 1, 255) # 在im上畫site大小的線,1表示線段閉合,255表示線段顏色 cv2.fillPoly(im, site, 255) # 在im的site區域,填充顏色為255 mask = im cv2.namedWindow('Mask', cv2.WINDOW_NORMAL) # 可調整視窗大小,不加這句不可調整 cv2.imshow("Mask", mask) masked = cv2.bitwise_and(image, image, mask=mask) # 在模板mask上,將image和image做“與”操作 cv2.namedWindow('Mask to Image', cv2.WINDOW_NORMAL) # 同上 cv2.imshow("Mask to Image", masked) cv2.waitKey(0) # 影像一直顯示,鍵盤按任意鍵即可關閉視窗 cv2.destroyAllWindows() if __name__ == '__main__': path = 'irving.jpg' mask_processing(path)
程式碼說明:
1,考慮到當影像尺寸太大,所以我們用 cv2.namedWindow() 函式可以指定視窗是否可以調整大小。在預設情況下,標誌為 cv2.WINDOW_AUTOSIZE。但是,如果指定標誌為 cv2.WINDOW_Normal,則可以調整視窗的大小,這些操作可以讓我們的工作更方便一些。
2,對座標軸的理解,上面程式碼中的四個座標從第一個到最後一個分別對應下圖中的 x1 x2 x4 x3。(我實際實驗是這樣的,如果有不同想法,可以交流)。
原圖如下:
mask與處理後圖的結果如下:
3,邊界填充
在做深度學習的時候,難免遇到需要填充邊界。邊緣填充是什麼呢?
因為對於影像的卷積操作,最邊緣的畫素一般無法處理,所以卷積核中心倒不了最邊緣畫素。這就需要先將影像的邊界填充,再根據不同的填充演算法進行卷積操作,得到的新影像就是填充後的影像。
如果你想在影像周圍建立一個邊,就像相框一樣,你可以使用 cv2.copyMakeBorder() 函式,這經常在卷積運算或 0 填充時被用到,這個函式如下:
def copyMakeBorder(src, top, bottom, left, right, borderType, dst=None, value=None):
引數解釋:
- src:輸入影像
- top,buttom,left,right 對應邊界的畫素數目(分別為影像上面, 下面, 左面,右面填充邊界的長度)
- borderType 要新增哪種型別的邊界,型別如下:
——cv2.BORDER_CONSTANT 新增有顏色的常數值邊界,還需要下一個引數(value)
——cv2.BORDER_REFLECT 邊界元素的映象,反射法,即以最邊緣的畫素為對稱軸。比如: fedcba|abcdefgh|hgfedcb
——cv2.BORDER_REFLECT_101 or cv2.BORDER_DEFAULT跟BORDER_REFLECT類似,但是由區別。例如: gfedcb|abcdefgh|gfedcba
——cv2.BORDER_REPLICATE 複製法,重複最後一個元素。例如: aaaaaa|abcdefgh|hhhhhhh
——cv2.BORDER_WRAP 不知道怎麼說了, 就像這樣: cdefgh|abcdefgh|abcdefg
- value 邊界顏色,通常用於常量法填充中,即邊界的型別是 cv2.BORDER_CONSTANT,
為了更好的理解這幾種型別,請看下面程式碼演示:
import cv2 import numpy as np import matplotlib.pyplot as plt # 讀取圖片 img = cv2.imread('kd1.jpg') # (221, 405, 3) # 各個邊界需要填充的值 top_size, bottom_size, left_size, right_size = (50, 50, 50, 50) # 複製法 replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE) # 反射法 reflect = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT) # 反射法 reflect101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT_101) # 外包裝法 wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_WRAP) # 常量法,常數值填充 constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_CONSTANT, value=(0, 255, 0)) plt.subplot(231) plt.imshow(img, 'gray') plt.title('origin') plt.subplot(232) plt.imshow(replicate, 'gray') plt.title('replicate') plt.subplot(233) plt.imshow(reflect, 'gray') plt.title('reflect') plt.subplot(234) plt.imshow(reflect101, 'gray') plt.title('reflect101') plt.subplot(235) plt.imshow(wrap, 'gray') plt.title('wrap') plt.subplot(236) plt.imshow(constant, 'gray') plt.title('constant')
原圖如下:
處理的效果圖如下:
3.1 細節函式
為了能快速對比出各個方法得出的影像的區別,可以使用np.vstack()或者np.hstack()對比,將影像放在同一個視窗。
rec=np.hstack((replicate,reflect)) cv_show("replicate_reflect",rec)
注意:使用np.vstack()或者np.hstack()函式時,影像的大小必須一致,不然會報錯。
使用np.vstack()或者np.hstack()函式時,可能會出現影像顯示不完全情況
尷尬,之前做過的示例,又寫了一次,就說這麼熟悉,整理後找到了,捨不得扔,就貼在這了。
import cv2 import matplotlib.pyplot as plt img = cv2.imread('lena.jpg') print(img.shape) # (263, 263, 3) # 為了展示效果,這裡填充的大一些 top_size, bottom_size, left_size, right_size = (50, 50, 50, 50) # 重複邊界,填充 即複製最邊緣畫素 replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE) # 反射邊界,填充 即對感興趣的影像中的畫素在兩邊進行復制,例如 fedcba|abcdefgh|hgfedcb reflect = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT) # 反射101邊界,填充 這個是以最邊緣為軸,對稱 ,例如 gfedcb|abcdefg|gfedcba reflect_101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT_101) # 外包裝 填充 例如 cdefgh|abcdefgh|abcdegf wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_WRAP) # 常量填充,常量值可以自己設定 constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_CONSTANT, value=0) plt.subplot(231), plt.imshow(img), plt.title('ORIGINAL') plt.subplot(232), plt.imshow(replicate), plt.title('REPLICATE') plt.subplot(233), plt.imshow(reflect), plt.title('REFLECT') plt.subplot(234), plt.imshow(reflect_101), plt.title('REFLECT_101') plt.subplot(235), plt.imshow(wrap), plt.title('WRAP') plt.subplot(236), plt.imshow(constant), plt.title('CONSTANT') plt.show()
效果如下:
注意:plt.imshow() 顯示圖片色差問題
我們都知道 cv2.imshow() 顯示的原始圖片是BGR格式,即原圖如下所示:
那通過opencv將BGR格式轉換為RGB格式,圖顯示如下:
這就解釋了為什麼plt.imshow()顯示圖片色差問題,原因就是讀取圖片的通道不同。
參考文獻:https://blog.csdn.net/weixin_42338058/article/details/88568704
按位運算參考:https://blog.51cto.com/devops2016/2088574
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_core/py_image_arithmetics/py_image_arithmetics.html