一朱巨、前言
上一篇我們簡單講解了OpenCV的概念和基礎架構(gòu)之拨。本篇主要向大家介紹下圖像處理中一個比較重要的概念 -- 掩膜操作捅位。開始前我們先看下利用矩陣掩膜操作來加強圖像對比度的效果骤肛。
二诱咏、開胃菜-Mat對象
我們用眼睛看到的是圖像昌粤,而計算機卻不認識既绕。于是,人們使用數(shù)值的形式來記錄圖像涮坐,比如用RGB值記錄圖像的每個點凄贩,以此來表示圖像。就如上圖袱讹,我們看到的是一輛車疲扎,而計算機“看到”的是一個包含圖像值的矩陣。OpenCV的Mat對象對應的就是矩陣。Mat提供了許多便捷的API來創(chuàng)建椒丧、操作矩陣壹甥。
Mat基礎操作
Mat image = Mat(240, 320, CV_8UC3);
第一個參數(shù)是rows,該矩陣的行數(shù)瓜挽;第二個參數(shù)是cols盹廷,該矩陣的列數(shù);第三個參數(shù)是該矩陣元素的類型久橙。這句話表示創(chuàng)建一個大小為240×320的矩陣俄占,里面的元素為8位unsigned型,通道數(shù)(channel)有3個淆衷。
image.create(480, 640, CV_8UC3);
分配(或重新分配)image矩陣缸榄,把大小設為480×640,類型設為CV8UC3祝拯。
Mat image = Mat(3, 3, CV_32F, Scalar(5));
定義并初始化一個3×3的32bit浮點數(shù)矩陣甚带,每個元素都設為5。
uchar* ptr = image.ptr(row);
指針操作佳头,表示拿到image第row行的指針
uchar* output = image.ptr(row);
output[1] = value;
利用指針修改圖像鹰贵,表示修改image第row行的第2個數(shù)據(jù)為value。
Mat常用成員介紹
1康嘉、data
Mat對象中的一個指針碉输,指向存放矩陣數(shù)據(jù)的內(nèi)存(uchar* data)
2、dims
矩陣的維度亭珍,34的矩陣維度為2維敷钾,34*5的矩陣維度為3維
3、channels
矩陣通道肄梨,矩陣中的每一個矩陣元素擁有的值的個數(shù)阻荒,比如說 3 * 4 矩陣中一共 12 個元素,如果每個元素有三個值众羡,那么就說這個矩陣是 3 通道的侨赡,即 channels = 3。常見的是一張彩色圖片有紅粱侣、綠羊壹、藍三個通道。
4甜害、rows
矩陣的行數(shù)
5舶掖、cols
矩陣的列數(shù)
Mat與IplImage
OpenCV1使用基于C接口定義的圖像存儲格式IplImage存儲圖像。IplImage直接暴露內(nèi)存尔店,如果忘記釋放內(nèi)存眨攘,就會造成內(nèi)存泄漏主慰。
從OpenCV2開始,開始使用Mat類存儲圖像鲫售,具有以下優(yōu)勢:
圖像的內(nèi)存分配和釋放由Mat類自動管理
Mat類由兩部分數(shù)據(jù)組成:矩陣頭(包含矩陣尺寸共螺、存儲方法、存儲地址等)和一個指向存儲所有像素值的矩陣(根據(jù)所選存儲方法的不同情竹,矩陣可以是不同的維數(shù))的指針藐不。Mat在進行賦值和拷貝時,只復制矩陣頭秦效,而不復制矩陣雏蛮,提高效率。如果矩陣屬于多個Mat對象阱州,則通過引用計數(shù)來判斷挑秉,當最后一個使用它的對象,則負責釋放矩陣苔货。
可以使用clone和copyTo函數(shù)犀概,不僅復制矩陣頭還復制矩陣。
三夜惭、掩膜操作
數(shù)字圖像處理中的掩膜的概念是借鑒于PCB制版的過程姻灶,在半導體制造中,許多芯片工藝步驟采用光刻技術诈茧,用于這些步驟的圖形“底片”稱為掩膜(也稱作“掩牟恚”),其作用是:在硅片上選定的區(qū)域中對一個不透明的圖形模板遮蓋若皱,繼而下面的腐蝕或擴散將只影響選定的區(qū)域以外的區(qū)域镊叁。
圖像掩膜與其類似尘颓,用選定的圖像走触、圖形或物體,對處理的圖像(全部或局部)進行遮擋疤苹,來控制圖像處理的區(qū)域或處理過程互广。
光學圖像處理中,掩模可以是膠片卧土、濾光片等惫皱。數(shù)字圖像處理中,掩模為二維矩陣數(shù)組,有時也用多值圖像。
是不是概念看得一頭霧水尤莺,沒事的旅敷,我第一次看概念的也是一樣樣的。下面我以例子來輔助大家了解掩膜颤霎。
摳下鎧的頭
接下來我們以代碼角度分析下究竟什么是掩膜媳谁。
// image為鎧的圖片
Mat src;
UIImageToMat(image, src);
Mat mask = Mat::zeros(src.size(), CV_8UC1);
Rect2i r = Rect2i(120, 80, 100, 100);
mask(r).setTo(255);
Mat dst;
src.copyTo(dst, mask);
第一步建立與原圖一樣大小的mask圖像涂滴,并將所有像素初始化為0,因此全圖成了一張全黑色圖晴音。
第二步將mask圖中的r區(qū)域的所有像素值設置為255,也就是整個r區(qū)域變成了白色柔纵。
Mat mask = Mat::zeros(src.size(), CV_8UC1);
mask(r).setTo(255);
使用mask將原始圖src拷貝到目的圖dst上。
src.copyTo(dst, mask);
這個拷貝的動作完整版本是這樣的:
原圖(src)與掩膜(mask)進行與運算后得到了結(jié)果圖(dst)锤躁。
其實就是原圖中的每個像素和掩膜中的每個對應像素進行與運算搁料。比如1 & 1 = 1;1 & 0 = 0系羞;
比如一個3 * 3的圖像與3 * 3的掩膜進行運算郭计,得到的結(jié)果圖像就是:
所以,mask就是位圖椒振,來過濾像素拣宏。如果mask像素的值是非0的,我就保留杠人,否則就丟棄勋乾。
因為我們上面得到的mask中,感興趣的區(qū)域是白色的嗡善,表明感興趣區(qū)域的像素都是非0辑莫,而非感興趣區(qū)域都是黑色,表明那些區(qū)域的像素都是0罩引。一旦原圖與mask圖進行與運算后各吨,得到的結(jié)果圖只留下原始圖感興趣區(qū)域的圖像了。也正剩下鎧的頭部了袁铐。
增強對比度
矩陣的掩膜操作就是根據(jù)掩膜來重新計算每個像素的像素值揭蜒,掩膜(mask)也被稱為kernel。
通過掩膜操作實現(xiàn)圖像對比度提高的公式如下剔桨。
I(i,j) = 5*I(i,j) - [I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]
注:這里看不懂不要緊屉更,先看具體的實現(xiàn),回頭我們再一起回顧這里洒缀。
上面的公式瑰谜,轉(zhuǎn)換成矩陣就如下圖所示
紅色是中心像素,從上到下树绩,從左到右對每個像素做同樣的處理操作萨脑,具體過程如下圖,深灰色底表示原圖像饺饭,每次移動kernel便根據(jù)公司計算新值并更新矩陣渤早。最終得到的結(jié)果就是對比度提高之后的輸出圖像。
具體的代碼如下:
// image為鎧的圖片
Mat src;
UIImageToMat(image, src);
int cols = (src.cols-1) * src.channels();
int offset = src.channels();
int rows = src.rows;
Mat dst = Mat(src.size(), src.type());
for (int row = 1; row < rows-1; row++) {
uchar* previous = src.ptr(row-1);
uchar* current = src.ptr(row);
uchar* next = src.ptr(row+1);
uchar* output = dst.ptr(row);
for (int col = offset; col < cols; col++) {
output[col] = saturate_cast<uchar>(5*current[col] - (current[col-offset] + current[col +offset] + previous[col] + next[col]));
}
}
/*
注:
saturate_cast<uchar>(-100)瘫俊,返回0
saturate_cast<uchar>(288)鹊杖,返回255
saturate_cast<uchar>(100)提鸟,返回100
這個函數(shù)的功能是確保RGB值范圍在0~255之間。
*/
效果
接下來我們來回顧下上面的那個公式
I(i,j) = 5*I(i,j) - [I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]
其實這個公式就是5倍的中心像素減去周邊的四個像素之和仅淑。
我們舉兩個例子來看下這個公式的結(jié)果称勋。
我們可以大致看到若是中心點的值大于周圍,則計算后的結(jié)果會將中心點與周圍的值差距拉得更大涯竟;
若是中心點的值小于周圍赡鲜,則計算后的結(jié)果也會將中心點與周圍的值差距拉大。這樣“大的大庐船,小的小”結(jié)果不就是對比明顯了嗎银酬,也就是提高了對比度。
大家會發(fā)現(xiàn)這樣做掩膜操作也太麻煩了筐钟。這個時候我們就找OpenCV來幫個忙揩瞪,看看它是怎么實現(xiàn)的。
Mat src;
UIImageToMat(image, src);
Mat dst;
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
filter2D(src, dst, src.depth(), kernel);
一個filter2D搞定篓冲!定義如下:
void filter2D( InputArray src, OutputArray dst, int ddepth,
InputArray kernel, Point anchor=Point(-1,-1),
double delta=0, int borderType=BORDER_DEFAULT );
其中src與dst是Mat類型變量李破、depth表示位圖深度,有32壹将、24嗤攻、8等。
四诽俯、小結(jié)
本篇主要介紹了Mat對象的基本用法妇菱,并通過兩個例子講解了掩膜操作的原理和實現(xiàn)。下一篇還是會以這樣的形式講解OpenCV的其他知識暴区,有更好建議的朋友可以給我留言闯团,see you later!