Opencv 例程講解 6 ---- 圖片融合 addWeighted到底有多快?

    這次介紹opencv中一個簡單的點運算函數,用來實現圖片合成。 對應於例程中的 (TUTORIAL) AddingImages 和 (TUTORIAL) AddingImagesTrackbar。

Opencv中提供的函數是addWeighted,用法很簡單,爲了比較性能了,我們手工實現了一段相同功能的代碼,並比較兩者的性能,到底快了多少,且看下文分解。

圖像處理中一個簡單而有趣的點運算操作可以用以下的公式表示,可以實現兩張圖片的線性融合。

這裏α 表示兩種圖片的融合比例,這個g(x) 表示 融合圖片中的像素點,f0(x) 和 f1(x) 分別表示背景和前景圖片中的像素點。

下面爲例程中的函數調用,

   beta = ( 1.0 - alpha );
   addWeighted( src1, alpha, src2, beta, 0.0, dst);
opencv 通過 addWeighted 函數實現圖片的線性融合,這個函數在之前的例程中也有提到過。

這個函數的原型如下所示,可以看出這個函數最小需要6個參數。

1、 第1個參數,輸入圖片1,

2、第2個參數,圖片1的融合比例

3、第3個參數,輸入圖片2

4、第4個參數,圖片2的融合比例

5、第5個參數,偏差

6、第6個參數,輸出圖片

//! computes weighted sum of two arrays (dst = alpha*src1 + beta*src2 + gamma)
CV_EXPORTS_W void addWeighted(InputArray src1, double alpha, InputArray src2,
                              double beta, double gamma, OutputArray dst, int dtype=-1);
 下面是程序運行結果

程序會提示輸入第一個圖片的融合比例,輸入0.5時,圖片的效果。

不過有時候會想,調節alpha 的值,查看不同情況下的融合結果。這個也不難做到,在例程(TUTORIAL) AddingImagesTrackbar中已經幫我實現了

先看看運行的結果。

相比之前的結果,可以看出在圖像上方多了一個滑動條,用來改變alpha的值,範圍從0~100。

相應的代碼如下:

   /// Create Windows
   namedWindow("Linear Blend", 1);

   /// Create Trackbars
   char TrackbarName[50];
   sprintf( TrackbarName, "Alpha x %d", alpha_slider_max );
   createTrackbar( TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar );

   /// Show some stuff
   on_trackbar( alpha_slider, 0 );
利用highgui庫中的 createTrackbar 函數創建一個滑塊,函數的原型如下

CV_EXPORTS int createTrackbar(const string& trackbarname, const string& winname,
                              int* value, int count,
                              TrackbarCallback onChange = 0,
                              void* userdata = 0);
可以看到,這個函數有6個參數,最後一個爲用戶數據的指針,類似回調函數中的用戶數據指針。

1、第一個參數,爲滑塊的名字

2、第二個參數,爲滑塊所在窗口的名字,用來獲取窗口的handle

3、第三個參數,爲傳人一個數據的指針,通過滑塊來改變該數據的值,該變量需要在回調函數TrackbarCallback 中作用域可見,因此例程中將它定義爲全局變量

4、第四個參數,爲傳人數據的最大值,用來控制數據的變化範圍

5、第五個參數,回調函數的函數指針,當滑塊變化時,便調用回調函數,實現融合畫面隨着滑塊的滑動而變化

下面我們來看下回調函數 on_trackbar, 它被定義了成一個static函數,意味着函數生命週期從被調用開始一直存在知道程序結束,第二個參數對應createTrackbar函數中的第6個參數,用戶數據指針。

/**
 * @function on_trackbar
 * @brief Callback for trackbar
 */
static void on_trackbar( int, void* )
{
   alpha = (double) alpha_slider/alpha_slider_max ;

   beta = ( 1.0 - alpha );

   addWeighted( src1, alpha, src2, beta, 0.0, dst);

   imshow( "Linear Blend", dst );
}


爲了測試addWeighted 函數性能,我們手工打造了一段相同功能的函數MyaddWeighted,代碼如下

void MyaddWeight(InputArray _src1, double alpha, InputArray _src2, double beta, double gamma, OutputArray _dst)
{	
	CV_Assert(_src1.depth() == CV_8U && _src2.depth() == CV_8U);
	CV_Assert(_src1.channels() == _src2.channels() &&_src1.size() == _src2.size());
	_dst.create(_src1.size(),_src1.type());

	Mat src1  = _src1.getMat();
	Mat src2  = _src2.getMat();
	Mat dst   = _dst.getMat();

	int rows = src1.rows;
	int cols = src1.cols * src1.channels();

	if (src1.isContinuous() && src2.isContinuous() && dst.isContinuous())
	{
		cols *= rows;
		rows = 1;
	}

	uchar* pSrc1 = NULL;
	uchar* pSrc2 = NULL;
	uchar* pDst  = NULL;
	for (int i=0; i<rows; i++)
	{
		pSrc1 = src1.ptr<uchar>(i);
		pSrc2 = src2.ptr<uchar>(i);
		pDst  = dst.ptr<uchar>(i);
		for (int j=0; j<cols; j++)
		{
			pDst[j] = saturate_cast<uchar>(pSrc1[j]*alpha + pSrc2[j]*beta + gamma);
		}		
	}
}

在手工打造的程序中,運用C [ ]下標對圖像像素點進行遍歷,在前面的例程有過相關介紹,C[ ] 是三種遍歷方法中性能最好的。

下圖是各自方法 運行1000次的平均時間,可以看出手工打造的代碼,雖然很好理解,但相對於opencv 的addWeightd函數性能差了很多,運行時間是6:1 。


注意到在我們代碼中有用到 saturate_cast<uchar> 進行類型轉換以保證值的安全,那麼這種類型轉換方式對我們性能有多大的影響呢,我們通過去掉這條語句後,重新運行後的結果爲

可以看出,平均運行時間從3.7毫秒縮短到了2.4毫秒,佔了運行時間的1/3。

現在換過一個方法來寫MyAddWeight 函數,還記得opencv中LUT函數中用到的NAryMatIterator,這次我們利用它來重寫MyAddWeight

void MyaddWeight2(InputArray _src1, double alpha, InputArray _src2, double beta, double gamma, OutputArray _dst)
{
	CV_Assert(_src1.depth() == CV_8U && _src2.depth() == CV_8U);
	CV_Assert(_src1.channels() == _src2.channels() &&_src1.size() == _src2.size());
	_dst.create(_src1.size(),_src1.type());

	Mat src1  = _src1.getMat();
	Mat src2  = _src2.getMat();
	Mat dst   = _dst.getMat();

	int cn = src1.channels();

	AddFun fun = AddFunTable[src1.depth()];   //  通過數據類型,從函數指針數組中選擇相應的函數進行調用

	const Mat* arrays[] = {&src1, &src2, &dst,0};   // 注意需要在最後加一個0,作爲指針數組結束的標誌,以確定數組中有效指針的個數
	uchar* ptrs[3];
	NAryMatIterator it(arrays, ptrs);
	int len = (int)it.size;
	for( size_t i = 0; i < it.nplanes; i++, ++it )
	 	fun(ptrs[0], ptrs[1], ptrs[2], len, cn, alpha, beta, gamma);
}

AddFun 是我們定義的一個函數指針類型,利用它加上一個函數指針數組AddFunTable,我們可以很容易的擴展MyAddWeight的使用範圍,使它對CV_16S, CV_32F的數據類型也能適用

typedef void (*AddFun) (const uchar*, const uchar*, const uchar*, int, int, double, double, double);

static void ADD8u_(const uchar* src1, const uchar* src2, uchar* dst, int len, int cn, double aplha, double beta, double gamma)
{
	for (int i=0; i<len*cn; i++)
		dst[i] = src1[i]*aplha + src2[i]*beta + gamma;
}

這個是函數ADD8u_ 的定義,相應的,我們可以快速的寫出適合其他數據類型,如CV_8U,CV_16S,CV_32F的處理函數,

static AddFun AddFunTable[] = 
{
	(AddFun)ADD8u_, 0
};

這個是函數指針數組,目前只實現了對數據類型CV_8U的操作,這樣就可以根據數據的類型,調用相應的函數,而不必針對每一種數據類型寫一個函數,大大提高了代碼的重用率,也大大減少了以後代碼維護的工作量,是一個很值得學習的技巧。

下面看下,這種寫法是否對性能有所提高。



可以看到,運行時間從2.48 毫秒減少到了2.29毫秒,性能提高了大約 7.6%左右,相對於代碼重用方面,性能上面的提高不是很明顯。


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