1.在OpenCV中我們經常會遇到一個名字:Mask(掩膜)。很多函數都使用到它,那麼這個Mask到底什麼呢?
2.如果我們想要裁剪圖像中任意形狀的區域時,應該怎麼辦呢?
答案是,使用掩膜(masking)。
我們先看一下掩膜的基礎。圖像的位運算。
圖像基本運算
圖像的基本運算有很多種,比如兩幅圖像可以相加、相減、相乘、相除、位運算、平方根、對數、絕對值等;圖像也可以放大、縮小、旋轉,還可以截取其中的一部分作為ROI(感興趣區域)進行操作,各個顔色通道還可以分别提取及對各個顔色通道進行各種運算操作。總之,對于圖像可以進行的基本運算非常的多,隻是挑了些常用的操作詳解。
- void add(InputArray src1, InputArray src2, OutputArray dst,InputArray mask=noArray(), int dtype= ); //dst = src1 + src2
- void subtract(InputArray src1, InputArray src2, OutputArray dst,InputArray mask=noArray(), int dtype= ); //dst = src1 - src2
- void multiply(InputArray src1, InputArray src2,OutputArray dst, double scale=, int dtype=); //dst = scale*src1*src2
- void divide(InputArray src1, InputArray src2, OutputArray dst,double scale=, int dtype=); //dst = scale*src1/src2
- void divide(double scale, InputArray src2,OutputArray dst, int dtype=); //dst = scale/src2
- void scaleAdd(InputArray src1, double alpha, InputArray src2, OutputArray dst); //dst = alpha*src1 + src2
- void addWeighted(InputArray src1, double alpha, InputArray src2,double beta, double gamma, OutputArray dst, int dtype=); //dst = alpha*src1 + beta*src2 + gamma
- void sqrt(InputArray src, OutputArray dst); //計算每個矩陣元素的平方根
- void pow(InputArray src, double power, OutputArray dst); //src的power次幂
- void exp(InputArray src, OutputArray dst); //dst = e**src(**表示指數的意思)
- void log(InputArray src, OutputArray dst); //dst = log(abs(src))
上述的基本操作中都屬于将基礎數學運算應用于圖像像素的進行中,下面将着重介紹
- bitwise_and、bitwise_or、bitwise_xor、bitwise_not這四個按位操作函數。
- void bitwise_and(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray()); //dst = src1 & src2
- void bitwise_or(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray()); //dst = src1 | src2
- void bitwise_xor(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray()); //dst = src1 ^ src2
- void bitwise_not(InputArray src, OutputArray dst,InputArray mask=noArray()); //dst = ~src
bitwise_and是對二進制資料進行“與”操作,即對圖像(灰階圖像或彩色圖像均可)每個像素值進行二進制“與”操作,1&1=1,1&0=0,0&1=0,0&0=0
bitwise_or是對二進制資料進行“或”操作,即對圖像(灰階圖像或彩色圖像均可)每個像素值進行二進制“或”操作,1|1=1,1|0=0,0|1=0,0|0=0
bitwise_xor是對二進制資料進行“異或”操作,即對圖像(灰階圖像或彩色圖像均可)每個像素值進行二進制“異或”操作,1^1=0,1^0=1,0^1=1,0^0=0
bitwise_not是對二進制資料進行“非”操作,即對圖像(灰階圖像或彩色圖像均可)每個像素值進行二進制“非”操作,~1=0,~0=1
為了便于大家進一步了解,下面給出測試代碼:
# opencv 圖像的基本運算
# 導入庫
import numpy as np
import argparse
import cv2
# 建構參數解析器
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="Path to the image")
args = vars(ap.parse_args())
# 加載圖像
image = cv2.imread(args["image"])
cv2.imshow("image loaded", image)
# 建立矩形區域,填充白色255
rectangle = np.zeros(image.shape[0:2], dtype="uint8")
cv2.rectangle(rectangle, (25, 25), (275, 275), 255, -1) # 修改這裡
cv2.imshow("Rectangle", rectangle)
# 建立圓形區域,填充白色255
circle = np.zeros(image.shape[0:2], dtype="uint8")
cv2.circle(circle, (150, 150), 150, 255, -1) # 修改
cv2.imshow("Circle", circle)
# 在此例(二值圖像)中,以下的0表示黑色像素值0, 1表示白色像素值255
# 位與運算,與常識相同,有0則為0, 均無0則為1
bitwiseAnd = cv2.bitwise_and(rectangle, circle)
cv2.imshow("AND", bitwiseAnd)
cv2.waitKey(0)
# 非運算,非0為1, 非1為0
bitwiseNot = cv2.bitwise_not(circle)
cv2.imshow("NOT", bitwiseNot)
cv2.waitKey(0)
# 或運算,有1則為1, 全為0則為0
bitwiseOr = cv2.bitwise_or(rectangle, circle)
cv2.imshow("OR", bitwiseOr)
cv2.waitKey(0)
# 異或運算,不同為1, 相同為0
bitwiseXor = cv2.bitwise_xor(rectangle, circle)
cv2.imshow("XOR", bitwiseXor)
cv2.waitKey(0)
可以看到,原圖是一張星空夜景圖。
效果如下:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX90kMihGbXJWds1GZoRGMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jN3cDMyYjM0ETOyYDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
為了便于展示,後面我隻截取部分區域效果:
掩膜(mask)
在有些圖像處理的函數中有的參數裡面會有mask參數,即此函數支援掩膜操作,首先何為掩膜以及有什麼用,如下:
數字圖像進行中的掩膜的概念是借鑒于PCB制版的過程,在半導體制造中,許多晶片工藝步驟采用光刻技術,用于這些步驟的圖形“底片”稱為掩膜(也稱作“掩模”),其作用是:在矽片上標明的區域中對一個不透明的圖形模闆遮蓋,繼而下面的腐蝕或擴散将隻影響標明的區域以外的區域。
圖像掩膜與其類似,用標明的圖像、圖形或物體,對處理的圖像(全部或局部)進行遮擋,來控制圖像處理的區域或處理過程。
數字圖像進行中,掩模為二維矩陣數組,有時也用多值圖像,圖像掩模主要用于:
①提取感興趣區,用預先制作的感興趣區掩模與待處理圖像相乘,得到感興趣區圖像,感興趣區内圖像值保持不變,而區外圖像值都為0。
②屏蔽作用,用掩模對圖像上某些區域作屏蔽,使其不參加處理或不參加處理參數的計算,或僅對屏蔽區作處理或統計。
③結構特征提取,用相似性變量或圖像比對方法檢測和提取圖像中與掩模相似的結構特征。
④特殊形狀圖像的制作。
在所有圖像基本運算的操作函數中,凡是帶有掩膜(mask)的處理函數,其掩膜都參與運算(輸入圖像運算完之後再與掩膜圖像或矩陣運算)。
掩膜執行個體
如開篇所提問題2,要對一幅圖進行摳圖(裁剪)操作,這就要用到Mask了,那麼就以摳圖為例,解釋Mask在裡面的作用。同樣以上圖為例,從原圖中裁剪小樹。
我們利用OR結果(其他結果也行),修改調整後,
代碼如下:
# opencv 圖像的基本運算
# 導入庫
import numpy as np
import argparse
import cv2
# 建構參數解析器
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="Path to the image")
args = vars(ap.parse_args())
# 加載圖像
image = cv2.imread(args["image"])
cv2.imshow("image loaded", image)
# 建立矩形區域,填充白色255
rectangle = np.zeros(image.shape[0:2], dtype="uint8")
cv2.rectangle(rectangle, (360, 348), (660, 570), 255, -1) # 修改這裡
cv2.imshow("Rectangle", rectangle)
# 建立圓形區域,填充白色255
circle = np.zeros(image.shape[0:2], dtype="uint8")
cv2.circle(circle, (520, 455), 140, 255, -1) # 修改
cv2.imshow("Circle", circle)
'''
# 在此例(二值圖像)中,以下的0表示黑色像素值0, 1表示白色像素值255
# 位與運算,與常識相同,有0則為0, 均無0則為1
bitwiseAnd = cv2.bitwise_and(rectangle, circle)
cv2.imshow("AND", bitwiseAnd)
cv2.waitKey(0)
# 非運算,非0為1, 非1為0
bitwiseNot = cv2.bitwise_not(circle)
cv2.imshow("NOT", bitwiseNot)
cv2.waitKey(0)
# 異或運算,不同為1, 相同為0
bitwiseXor = cv2.bitwise_xor(rectangle, circle)
cv2.imshow("XOR", bitwiseXor)
cv2.waitKey(0)
'''
# 或運算,有1則為1, 全為0則為0
bitwiseOr = cv2.bitwise_or(rectangle, circle)
cv2.imshow("OR", bitwiseOr)
cv2.waitKey(0)
# 使用mask
mask = bitwiseOr
cv2.imshow("Mask", mask)
# Apply out mask -- notice how only the person in the image is cropped out
masked = cv2.bitwise_and(image, image, mask=mask)
cv2.imshow("Mask Applied to Image", masked)
cv2.waitKey(0)
結果展示:
簡單說就是:
- 與或非異或運算與我們的常識類似。
- 掩膜操作就是兩幅圖像(numpy數組)的位運算操作。
附錄:C++ 版
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
int main()
{
Mat image, mask;
Rect r1(100, 100, 250, 300);
Mat img1, img2, img3, img4;
image = imread("lol17.jpg");
mask = Mat::zeros(image.size(), CV_8UC1);
mask(r1).setTo(255);
img1 = image(r1);
image.copyTo(img2, mask);
image.copyTo(img3);
img3.setTo(0, mask);
imshow("Image sequence", image);
imshow("img1", img1);
imshow("img2", img2);
imshow("img3", img3);
imshow("mask", mask);
waitKey();
return 0;
}
原始圖:
注意程式中的這兩句關于Mask的操作。
mask = Mat::zeros(image.size(), CV_8UC1);
mask(r1).setTo(); //r1是設定好的感興趣區域
解釋一下上面兩句的操作。
- 第一步建立與原圖一樣大小的mask圖像,并将所有像素初始化為0,是以全圖成了一張全黑色圖。
- 第二步将mask圖中的r1區域的所有像素值設定為255,也就是整個r1區域變成了白色。
這樣就能得到Mask圖像了。
注意這句,哪個圖像拷貝到哪個圖像?
image.copyTo(img2, mask);
當然是原始圖image拷貝到目的圖img2上。
其實拷貝的動作完整版本是這樣的:
原圖(image)與掩膜(mask)進行與運算後得到了結果圖(img2)。
何為圖與掩膜的與運算?
其實就是原圖中的每個像素和掩膜中的每個對應像素進行與運算。比如1 & 1 = 1;1 & 0 = 0;
比如一個3 * 3的圖像與3 * 3的掩膜進行運算,得到的結果圖像就是:
說白了,mask就是位圖啊,來選擇哪個像素允許拷貝,哪個像素不允許拷貝。如果mask像素的值是非0的,我就拷貝它,否則不拷貝。
因為我們上面得到的mask中,感興趣的區域是白色的,表明感興趣區域的像素都是非0,而非感興趣區域都是黑色,表明那些區域的像素都是0。一旦原圖與mask圖進行與運算後,得到的結果圖隻留下原始圖感興趣區域的圖像了。也正如下圖所示。
image.copyTo(img2, mask);
如果想要直接摳出目标區域,直接這樣寫就OK了:
參考文獻:
1.https://www.cnblogs.com/skyfsm/p/6894685.html