OpenCV(二)如何對圖像的像素進行操作

對圖像的像素進行操作,我們可以實現空間增強,反色等目的。讓我們先來看一下內存空間中圖像矩陣,也就是Mat的矩陣數值部分是怎麼存儲的:

如果圖像是一幅灰度圖像,他就像這樣,從左到右,從上到下,依次是矩陣的每一行每一列,這時候矩陣M(i,j)的值自然就是當前點的灰度值了。

而對於一幅彩色圖像,由於它的像素分量channel並不是一個,所以每一列又分爲了幾個channel。拿常見的RGB圖像來說,就像這樣:

從這張圖上,就可以比較清楚地看出來在內存中矩陣是如何存儲多channel圖像的了。這裏要注意的是在RGB模型中,每一個子列依次爲BGR,也就是正好是顛倒的,第一個分量是藍色,第二個是綠色,第三個是紅色。

清楚了圖像在內存中的存儲方式,我們也就可以來進行像素值的操作了。在這裏,我們舉這樣一個例子。我們對一幅灰度圖像的灰度值進行變換:

小於100的灰度值被統一映射爲0;100到200之間的灰度值被映射爲100;大於200的灰度值被映射爲200.

主函數如下:

int main()
{
	string picName="lena.jpg";
	Mat A=imread (picName,CV_LOAD_IMAGE_GRAYSCALE);    //讀入灰度圖像
	uchar table[256];          //映射表,規定了變換前後灰度值的對應關係 table[gray_value_before]=gray_value_after
	for (int i=0;i<256;i++)
	{
		table[i]=i/100*100;   //這裏利用了C++的語言特性i/100只會留下整數部分
	}
	imshow("變換前",A);
	Mat B=ChangeImg (A,table);    //變換函數
	imshow ("變換後",B);
	waitKey ();
	return 0;
}


首先,我們用指針方式對圖像的像素點灰度值進行操作:

Mat  ChangeImg(Mat &img,const uchar* table)
{
	CV_Assert(img.depth ()!=sizeof(uchar));  //聲明只對深度8bit的圖像操作
	int channels=img.channels ();            //獲取圖像channel
	int nrows=img.rows;                     //矩陣的行數
	int ncols=img.cols*channels;             //矩陣的總列數=列數*channel分量數
	if (img.isContinuous ())               //判斷矩陣是否連續,若連續,我們相當於只需要遍歷一個一維數組
	{
		ncols*=nrows;
		nrows=1;                 //一維數組
	}
	//遍歷像素點灰度值
	for (int i=0;i<nrows;i++)
	{
		uchar *p=img.ptr<uchar>(i);    //獲取行地址
		for (int j=0;j<ncols;j++)
		{
			p[j]=table[p[j]];           //修改灰度值
		}
	}
	return img;
}

這裏,我們獲取了每一行開始處的指針,然後遍歷至該行末尾。如果矩陣是以連續方式存儲的,我們只需請求一次指針、然後一路遍歷下去就行。彩色圖像的情況有必要加以注意:因爲三個通道的原因,我們需要遍歷的元素數目也是3倍。

或者,我們可以使用data。data會從Mat中返回指向矩陣第一行第一列的指針。注意如果該指針爲NULL則表明對象裏面無輸入,所以這是一種簡單的檢查圖像是否被成功讀入的方法。當矩陣是連續存儲時,我們就可以通過遍歷 data 來掃描整個圖像。例如,一個灰度圖像,其操作如下:

uchar* p = img.data;
for( unsigned int i =0; i < ncol*nrows; ++i)
	*p++ = table[*p];

或者,更安全的方法,我們可以使用迭代器。在迭代法中,所需要做的僅僅是獲得圖像矩陣的begin和end,然後增加迭代直至從begin到end。將*操作符添加在迭代指針前,即可訪問當前指向的內容。

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     
    
    const int channels = I.channels();
    switch(channels)
    {
    case 1: 
        {
            MatIterator_<uchar> it, end; 
            for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
                *it = table[*it];
            break;
        }
    case 3: 
        {
            MatIterator_<Vec3b> it, end; 
            for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
            {
                (*it)[0] = table[(*it)[0]];
                (*it)[1] = table[(*it)[1]];
                (*it)[2] = table[(*it)[2]];
            }
        }
    }
    
    return I; 
}

注意,在這裏對3通道的圖像進行操作的時候,使用到了Vec3b。Vec3b作爲一個對三元向量的數據結構,用在這裏正好是能夠表示RGB的三個分量。如果對於彩色圖像,仍然用uchar的話,則只能獲得3通道中的B分量。比如我們可以這樣打印出圖像的RGB三個分量:

for (int i=0;i<img.rows;i++)
	{
		const Vec3b* Mpoint=img.ptr <Vec3b>(i);
		for (int j=0;j<img.cols;j++)
		{
			Vec3b intensity=*(Mpoint+j);
			cout<<"R:"<<int(intensity[2])<<" G"<<int(intensity[1])<<" B"<<int(intensity[0])<<"    ";
		}
		cout<<endl;
	}

這裏使用指針,當然也可以使用上面的迭代器。

然而,OpenCV裏面已經有了相應函數可以讓我們更加方便地對像素進行操作,那便是LUT函數,而且推薦使用OpenCV的內建函數,因爲已經針對芯片做了優化設計,使得速度有很大提升。

函數原型爲:void LUT(InputArray src, InputArray lut, OutputArray dst, int interpolation=0 )

實現的映射關係如下所示:


也就是說比如原來src中值爲1會映射爲table[1]所對應的值再加上d。

所以上面的操作,我們其實只需要使用LUT函數就可以了。結合我們自己設計的table表,就能夠實現對圖像的操作。

int main()
{
	string picName="lena.jpg";
	Mat A=imread (picName,CV_LOAD_IMAGE_GRAYSCALE);    //讀入灰度圖像
	Mat lookUpLut(1,256,CV_8UC1);   //建立一個256個元素的映射表
	imshow ("變換前",A);
	for (int i=0;i<256;i++)
	{
		lookUpLut.at<uchar>(i)=i/100*100;
	}
	Mat B;
	LUT (A,lookUpLut,B);
	imshow ("變換後",B);
	waitKey ();
	return 0;
}

下面的圖就是效果啦~~~



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章