OpenCV學習心得(3) -- 在MFC上顯示圖像

在MFC上显示OpenCV图像需要将数据转换为BitMap格式,特别是调整行字节数为4的倍数,以避免图像倾斜和失真。图像的顶部和底部需要翻转,对于不同通道数的图像有不同的处理方式。对于4通道图像,可以直接翻转显示;对于2或3通道图像,可以转换为4通道再进行显示;1通道图像则需要创建新的缓冲区并复制数据。此外,`cv::cvtColor`函数用于色彩空间转换,`cv::mixChannels`函数用于通道数据的拷贝。最后,通过准备BITMAP信息头并使用`StretchDIBits`函数绘制图像。
摘要由CSDN通过智能技术生成

轉載請注明出處和作者

在MFC上顯示圖像最重要的部份就是將數據轉換為BitMap格式.一開始我一直在找如果將cv::Mat中的數據直接顯示在MFC上.結果找了很久也沒找到.最后發現如果行的字節數不是4的倍數,顯示的圖像就一定會是傾斜并失真的...所以如果行的字節數不是4的倍數就一定會發生數據拷貝(因為要準備一個行是4的倍數的容器來裝這些數據)...看來想省事是不可能了..

所以在MFC上顯示圖像的要點就是將行的字節數調整為4的倍數.

還有一點需要注意的就是cv::Mat是以左上角為頂點,而BITMAP是以左下角為頂點.所以顯示的時候需要將數據進行翻轉.

我這裡採用了一個省事的辦法,

對於4 channel的圖,因為是4個channel,所以一定是4的倍數,直接翻轉并顯示.

對於2,3 channel的圖,可能不是4的倍數,將其轉換為4channel,然後翻轉并顯示.

對於1channel的圖,先申請一個行的字節數是4的倍數的空間,進行像素點拷貝,最後翻轉并顯示.這個方法會在圖像的最右邊產生一個邊界.如果不想要邊界的話其實也可以使用轉換為4channel的方式.但是這個時候就會產生一個問題,那就是這1個channel的圖像要顯示成灰度圖呢還是紅/綠/藍單色的通道圖呢?如果是灰度圖的話就要將這個單channel的數據拷貝3分,分別放到0~2這三個channel上. 如果是紅/綠/藍單色的通道圖就只需要將這個channel的數據拷貝到對應的channel中即可,至於怎麼拷貝,可以參看cv::mixChannels函數.

順便說明一下,默認情況下4channel的顏色順序是BGRA(B=藍色,G=綠色,R=紅色,A=透明度)


至於在MFC上顯示就是先準備BITMAP信息頭,然後調用StretchDIBits函數將之前準備好的4channel數據進行繪圖.

public: //HDC Draw
	//!在HDC上畫圖.如果m_src有更改,則先準備m_draw,再進行畫圖,否則直接畫m_draw中的圖像.
	//!這個函數會將src_r範圍內的圖像經過伸縮后顯示為dest_r大小的圖像.
	//!src_r為源圖像中要顯示的範圍, dest_r為顯示圖像的畫布的大小(也就是HDC的rect),filltype為調色板填充類型(只有為單通道(256色圖像)的時候才起作用)
	void Draw2HDC(HDC dc,const cv::Rect& src_r,const cv::Rect& dest_r,RGBQUAD_FILLTYPE filltype=RGBQUAD_FILLTYPE_GRAY);
	//!將整個圖片縮放到dest_r大小后顯示在HDC上.
	void Draw2HDC(HDC dc,const cv::Rect& dest_r);
	//!將原始圖像m_src轉換為Windows可以顯示的數據并保存到m_draw中,以便接下來的顯示動作
	//!只有當m_modifyed_for_draw為true的時候才進行轉換,轉換完成后將m_modifyed_for_draw設為false.
	bool PrepareDrawData();

bool COpenCVImage::PrepareDrawData()
{
	if(m_modifyed_for_draw)
	{
		
		m_modifyed_for_draw=false;
		if(m_src.empty()) return false;

		cv::Mat m_src_tmp;
		if(m_src.depth()!=CV_8U)
		{
			m_src.convertTo(m_src_tmp,CV_8U);
		}
		else
		{
			m_src_tmp=m_src;
		}
		switch(m_src_tmp.channels())
		{
			case 4: //4channel直接翻轉.
			{
				//m_draw=m_src_tmp;
				cv::flip(m_src_tmp,m_src_tmp,0);
				m_draw=m_src_tmp;
				break;
			}
			case 1:	//1channel,
			{
				cv::Mat tmp(m_src.rows,(m_src_tmp.cols+3)/4*4,CV_8UC1);//申請4byte字節倍數的空間.
				for(int i=0;i<m_src_tmp.cols;++i)//數據拷貝.
					for(int j=0;j<m_src_tmp.rows;++j)
					{
						tmp.at<uchar>(j,i)= m_src_tmp.at<uchar>(j,i);
					}
				cv::flip(tmp,tmp,0);
				m_draw=tmp;
				break;
			}

			case 3:	//3channel
			{
				cv::Mat tmp;
				cv::cvtColor(m_src,tmp,CV_BGR2BGRA);
				cv::flip(tmp,tmp,0);
				m_draw=tmp;
				break;
			}
			default://channels=2,3
			{
				cv::Mat tmp(m_src.rows,m_src.cols,CV_8UC4);
				int *fromto=new int[m_src_tmp.channels()*2];
				for(int i=0;i<m_src_tmp.channels();++i)
				{
					fromto[i*2]=fromto[i*2+1]=i;
				}
				cv::mixChannels(&m_src_tmp,1,&tmp,1,fromto,m_src_tmp.channels());
				cv::flip(tmp,tmp,0);
				m_draw=tmp;
				if(fromto) delete [] fromto;
				
			}
			
		}
		
	}
	return true;
}
void COpenCVImage::Draw2HDC(HDC dc,const cv::Rect& src_r,const cv::Rect& dest_r,RGBQUAD_FILLTYPE filltype)
{
	if(!PrepareDrawData()) return;
	if(m_draw.depth()==CV_8U && (!m_draw.empty()))
	{
		uchar *buffer=NULL;
		
		if(m_draw.channels()>1)
		{
			buffer=new uchar[sizeof(BITMAPINFOHEADER)];
		}
		else
		{
			buffer=new uchar[sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD)];
			Fill_RGBQUAD((BITMAPINFO*)buffer,filltype);
		}
		Fill_BITMAPINFOHEADER((BITMAPINFOHEADER*)buffer,m_draw.cols,m_draw.rows,m_draw.elemSize1()* 8*m_draw.channels());
		SetStretchBltMode(dc, COLORONCOLOR ); 
		::StretchDIBits(  dc,  
         dest_r.x, dest_r.y, dest_r.width, dest_r.height,  
		 src_r.x, src_r.y, src_r.width, src_r.height,  
		 m_draw.data, (BITMAPINFO*)buffer, DIB_RGB_COLORS, SRCCOPY ); 
		if(buffer) delete [] buffer;
	}
}
void COpenCVImage::Draw2HDC(HDC dc,const cv::Rect& dest_r)
{
	Draw2HDC(dc,cv::Rect(0,0,m_src.cols,m_src.rows),dest_r,m_rgbQuad_filltype);
}

這裡需要說明一下:

cv::flip(m_src_tmp,m_src_tmp,0); 翻轉圖像.
這個函數的第三個參數是翻轉方式,

等於0: 延x軸上下翻轉, 

大於0為延y軸左右翻轉,

小於0為同時延x,y軸翻轉(效果等同于旋轉180度)

此函數可以進行In-Place操作.


在處理3channel圖像的時候使用了一個技巧,就是cv::cvtColor函數,

cv::cvtColor(m_src,tmp,CV_BGR2BGRA); 這個函數可以進行不同色彩空間的轉換.

第三個參數是轉換類型,使用預定義的宏,格式類似于CV_源空間格式2新空間格式.

第四個參數是轉換后的channel數.默認情況下是0,即根據源格式和新格式自動確定.

這裡使用這個函數直接將cv::Mat中的BGR 3channel格式轉換為BGRA的4channel格式.


default中的處理方式也可以適用於2,3,4channel的情況.其中主要使用的函數就是cv::mixChannels

void mixChannels(const Mat* src, int nsrc, Mat* dst, int ndst, const int* fromTo, size_t npairs)

這個函數可以將源Mat中的某些channel的數據按指定的方式拷貝到目的Mat中.

src參數為源Mat數組,

nsrc參數為源Mat數組中有多少個Mat.

dst為目的Mat數組,

ndst參數為目的Mat數組中有多少個Mat.

fromTo參數是一個int型指針,也就是一個int型的數組.它表明yuanMat中的channel要拷貝到目的Mat中的那些channel中.這個參數是2個一組的,即fromTo[0]和fromTo[1]為一組,表明從源Mat中的第fromTo[0]個channel拷貝到目的Mat中的第fromTo[1]個channel中. fromTo[2]和fromTo[3]為一組..依次類推.

npairs參數表明fromTo數組中一共有多少對(注意:不是多少個)數據.

src與dst都是Mat數組..那么當src與dst中不止一個Mat的時候channel的index是怎麼計算的呢?很簡單,就是向後延續,例如src中有3個mat, Mat1有2個channel,Mat2有3個channel,Mat3有2個channel,則Mat1的channel index分別為 0,1 ,Mat2的channel index分別為 2,3,4,Mat3的channel index分別為 5,6



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值