Learning OpenCV with iOS:掩膜操作

一、前言

上一篇我們簡單講解了OpenCV的概念和基礎(chǔ)架構(gòu)。本篇主要向大家介紹下圖像處理中一個比較重要的概念 -- 掩膜操作。開始前我們先看下利用矩陣掩膜操作來加強圖像對比度的效果。

增強對比度

二、開胃菜-Mat對象

Mat

我們用眼睛看到的是圖像,而計算機卻不認(rèn)識。于是,人們使用數(shù)值的形式來記錄圖像,比如用RGB值記錄圖像的每個點,以此來表示圖像。就如上圖,我們看到的是一輛車,而計算機“看到”的是一個包含圖像值的矩陣。OpenCV的Mat對象對應(yīng)的就是矩陣。Mat提供了許多便捷的API來創(chuàng)建、操作矩陣。

Mat基礎(chǔ)操作

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矩陣,把大小設(shè)為480×640,類型設(shè)為CV8UC3。

Mat image = Mat(3, 3, CV_32F, Scalar(5));

定義并初始化一個3×3的32bit浮點數(shù)矩陣,每個元素都設(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類由兩部分?jǐn)?shù)據(jù)組成:矩陣頭(包含矩陣尺寸、存儲方法、存儲地址等)和一個指向存儲所有像素值的矩陣(根據(jù)所選存儲方法的不同,矩陣可以是不同的維數(shù))的指針。Mat在進行賦值和拷貝時,只復(fù)制矩陣頭,而不復(fù)制矩陣,提高效率。如果矩陣屬于多個Mat對象,則通過引用計數(shù)來判斷,當(dāng)最后一個使用它的對象,則負責(zé)釋放矩陣。

  • 可以使用clone和copyTo函數(shù),不僅復(fù)制矩陣頭還復(fù)制矩陣。

三、掩膜操作

數(shù)字圖像處理中的掩膜的概念是借鑒于PCB制版的過程,在半導(dǎo)體制造中,許多芯片工藝步驟采用光刻技術(shù),用于這些步驟的圖形“底片”稱為掩膜(也稱作“掩模”),其作用是:在硅片上選定的區(qū)域中對一個不透明的圖形模板遮蓋,繼而下面的腐蝕或擴散將只影響選定的區(qū)域以外的區(qū)域。
圖像掩膜與其類似,用選定的圖像、圖形或物體,對處理的圖像(全部或局部)進行遮擋,來控制圖像處理的區(qū)域或處理過程。
光學(xué)圖像處理中,掩??梢允悄z片、濾光片等。數(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ū)域的所有像素值設(shè)置為255,也就是整個r區(qū)域變成了白色。

 Mat mask = Mat::zeros(src.size(), CV_8UC1);
 mask(r).setTo(255);
mask圖片

使用mask將原始圖src拷貝到目的圖dst上。

src.copyTo(dst, mask);

這個拷貝的動作完整版本是這樣的:

原圖(src)與掩膜(mask)進行與運算后得到了結(jié)果圖(dst)。

其實就是原圖中的每個像素和掩膜中的每個對應(yīng)像素進行與運算。比如1 & 1 = 1;1 & 0 = 0;

比如一個3 * 3的圖像與3 * 3的掩膜進行運算,得到的結(jié)果圖像就是:

mask.png

所以,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

紅色是中心像素,從上到下,從左到右對每個像素做同樣的處理操作,具體過程如下圖,深灰色底表示原圖像,每次移動kernel便根據(jù)公司計算新值并更新矩陣。最終得到的結(jié)果就是對比度提高之后的輸出圖像。

kernel

具體的代碼如下:

    // 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é)果。

demo1
demo2

我們可以大致看到若是中心點的值大于周圍,則計算后的結(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!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容