1. 程式人生 > >OPENCV 實現png繪製,alpha通道疊加。

OPENCV 實現png繪製,alpha通道疊加。

一夜未眠,一直在找一個好點的方法將帶alpha通道的png圖片疊加到其他三通道圖片上。

下面進入正題:

在這段程式碼中,cvAdd4cMat 其實是一個巨集,由 CA4M_EXCAT 巨集來控制它展開成什麼。

#ifdef CA4M_EXCAT
#define cvAdd4cMat cvAdd4cMat_e
#else
#define cvAdd4cMat cvAdd4cMat_q
#endif

注:e版函式是精確版,q版函式是快速版。我也不知道實際使用e版和q版差別多大,速度e版更快,但是繪製出來的質量我用肉眼完全看不出差別,文章後面有我對兩版函式速度測試的說明。如果你要直接使用我的模組,那麼我建議你不要定義CA4M_EXCAT這個巨集。因為我認為q版函式更加優秀。如果你不知道怎麼使用它,你可以到底部獲得演示程式的下載地址。

原理:

視訊記憶體中每個點是以BGR方式儲存的,B、G、R分別叫藍色通道、綠色通道、紅色通道

而對於一些圖片來所,它們還需要有一個A通道,阿爾法通道,即不透明度通道。來控制這個點的不透明度,其實所謂的不透明度,只在與其他圖片疊時反應出來。

對於某一個畫素點的疊加公式:

   b = b1 * a1/255 + b0 * (255 - a1)/255
   g = g1 * a1/255 + g0 * (255 - a1)/255
   r = r1 * a1/255 + r0 * (255 - a1)/255


那麼把這個單點的計算公式轉換成矩陣的計算公式,通過Opencv對矩陣運算的優勢,可極大的提高疊加的效率。 那麼我通過split函式分解矩陣的通道 把png圖片分解為B1矩陣,G1矩陣,R1矩陣,A1矩陣 把背景圖片分解為了B0矩陣,G0矩陣,R0矩陣。 於是疊加的結果: 令alpha = A1 , 令beta = 255 - alpha。注意:alpha和beta依然是矩陣
 B=B1 * alpha/255 + B0 * beta/255
 G=G1 * alpha/255 + G0 * beta/255
 R=R1 * alpha/255 + R0 * beta/255
如果使用scale增益這個引數,那麼:
newalpha = alpha * scale
newbeta = 255 - newalpha

以上只是該模組的基本思想,為了防止溢位,減少冗餘計算,原始碼在演算法上做了較大的調整,具體請看程式碼了:

cvAdd4cMat.h

#pragma once

#include <vector>
#include <opencv2/opencv.hpp>


static int createtable = false;//init lookup-table;
static cv::Mat table(1, 256, CV_32FC1);
static cv::Mat otable(1, 256, CV_32FC1);
int cvAdd4cMat_e(cv::Mat &dst, cv::Mat &scr, double scale);
int cvAdd4cMat_e(cv::Mat &dst, cv::Mat &scr);
int cvAdd4cMat_q(cv::Mat &dst, cv::Mat &scr, double scale = 1.0);
void InitLookupTable(void);

#ifdef CA4M_EXCAT
#define cvAdd4cMat cvAdd4cMat_e
#else
#define cvAdd4cMat cvAdd4cMat_q
#endif

cvAdd4cMat.cpp

#include "cvAdd4cMat.h"

int cvAdd4cMat_e(cv::Mat &dst, cv::Mat &scr, double scale)
{
	if (createtable == false)
	{
		InitLookupTable();
	}
	if (dst.channels() != 3 || scr.channels() != 4)
	{
		return 0;
	}
	if (scale < 0.01)
		return 1;
	std::vector<cv::Mat> scr_channels;
	std::vector<cv::Mat> dstt_channels;
	split(scr, scr_channels);
	split(dst, dstt_channels);
	CV_Assert(scr_channels.size() == 4 && dstt_channels.size() == 3);
	cv::Mat alpha(dst.rows, dst.cols, CV_32FC1);
	LUT(scr_channels[3], table, alpha);
	for (int i = 0; i < 3; i++)
	{
		scr_channels[i].convertTo(scr_channels[i], CV_32FC1);
		dstt_channels[i].convertTo(dstt_channels[i], CV_32FC1);
		dstt_channels[i] = dstt_channels[i].mul(1 - alpha*scale);
		dstt_channels[i] += scr_channels[i].mul(alpha * scale);
		dstt_channels[i].convertTo(dstt_channels[i], CV_8UC1);
	}
	merge(dstt_channels, dst);
	return 1;
}

int cvAdd4cMat_e(cv::Mat &dst, cv::Mat &scr)
{
	if (createtable == false)
	{
		InitLookupTable();
	}
	if (dst.channels() != 3 || scr.channels() != 4)
	{
		return 0;
	}
	std::vector<cv::Mat>scr_channels;
	std::vector<cv::Mat>dstt_channels;
	split(scr, scr_channels);
	split(dst, dstt_channels);
	CV_Assert(scr_channels.size() == 4 && dstt_channels.size() == 3);
	cv::Mat alpha(dst.rows, dst.cols, CV_32FC1);
	cv::Mat beta(dst.rows, dst.cols, CV_32FC1);
	LUT(scr_channels[3], table, alpha);
	LUT(scr_channels[3], otable, beta);
	for (int i = 0; i < 3; i++)
	{
		scr_channels[i].convertTo(scr_channels[i], CV_32FC1);
		dstt_channels[i].convertTo(dstt_channels[i], CV_32FC1);
		dstt_channels[i] = dstt_channels[i].mul(beta);
		dstt_channels[i] += scr_channels[i].mul(alpha);
		dstt_channels[i].convertTo(dstt_channels[i], CV_8UC1);
	}
	merge(dstt_channels, dst);
	return 1;
}



int cvAdd4cMat_q(cv::Mat &dst, cv::Mat &scr, double scale)
{

	if (dst.channels() != 3 || scr.channels() != 4)
	{
		return true;
	}
	if (scale < 0.01)
		return false;
	std::vector<cv::Mat>scr_channels;
	std::vector<cv::Mat>dstt_channels;
	split(scr, scr_channels);
	split(dst, dstt_channels);
	CV_Assert(scr_channels.size() == 4 && dstt_channels.size() == 3);

	if (scale < 1)
	{
		scr_channels[3] *= scale;
		scale = 1;
	}
	for (int i = 0; i < 3; i++)
	{
		//newalpha equal scale * alpha
		dstt_channels[i] = dstt_channels[i].mul(255.0 / scale - scr_channels[3], scale / 255.0);
		dstt_channels[i] += scr_channels[i].mul(scr_channels[3], scale / 255.0);
	}
	merge(dstt_channels, dst);
	return true;
}

void InitLookupTable(void)
{
	for (int i = 0; i < 256; i++)
	{
		table.at<float>(i) = i / 255.0f;
		otable.at<float>(i) = 1 - table.at<float>(i);
	}
	createtable = true;
}

對兩版函式的分析:

for (int i = 0; i < 5000;i++)
cvAdd4cMat(img1_t2, img2);
e版測兩次:26344ms 26547ms
q版測兩次:20094ms 19996ms


for (int i = 0; i < 5000;i++)
cvAdd4cMat(img1_t2, img2, 1.5);
e版測兩次:29851ms 29725ms
q版測兩次:21051ms 21023ms


for (int i = 0; i < 5000;i++)
cvAdd4cMat(img1_t2, img2, 0.5);
e版測兩次:29281ms 29221ms
q版測兩次:22031ms 22031ms


綜上看出
  e版函式平均每次繪圖4ms,q版函式平均每次繪圖6ms。
  e版函式在繪製速度上不如q版函式,e版函式繪製時如果使用scale引數,速度還會減慢10%左右
  q版函式相對更快,但在scale引數小於1時因為演算法原因,繪製速度也會減慢10%左右

下面是我寫的一個小example:


下載地址:http://download.csdn.net/detail/u013097499/7483975