玩opencv时候,肯定会接触色彩空间。最常用的是RGB色彩空间,用0~255表示每个通道。

摄影投效里有个技术,叫做蓝幕,也有绿幕。后期把蓝色区域扣掉,然后换成别的背景。


在上图中,细心的小伙伴会发现,实际这个蓝色并不一定是纯蓝(0,0,255)

有脑洞大的小朋友想到了一个方法,只提取蓝色通道,然后把值接近255的都扣掉。

但是有个问题,这样会扣到高光区域(RGB三个值都很高的区域)

另外,如果这个蓝色偏亮偏暗或偏绿,或者摄像机有偏色,用(0,0,255)这个参数当特征来匹配是很困难的事。

这儿我给大家推荐一个好用的色彩空间,叫HSV色彩空间。


H是个色相参数,用0到360度表示了颜色。

S是个色彩鲜艳度的参数,越大颜色越鲜艳,越小颜色越灰。S有个通俗的名字,叫饱和度。

V是个亮度参数,越小越黑

RGB到HSV色彩空间的转换代码如下:

float rgb2h(int B, int G, int R)
{
float H;
int max = max(max(R, G), B);
int min = min(min(R, G), B);
if (R == max)
H = (G - B)*60.0 / (max - min);
if (G == max)
H = 120+(B - R)*60.0 / (max - min);
if (B == max)
H = 240+(R - G)*60.0 / (max - min);
if (H < 0)H = H + 360;
return H;
}

我们先找一张色彩分布的图来试验一下抠图


蓝色附近的区域就是H在240度附近。下面我们写个小程序把240度正负50度的区域抠掉。

void mytest()
{
Mat frame_t = imread("a3.jpg", CV_LOAD_IMAGE_UNCHANGED);
int WW, HH;//图像宽度和高度 单位:像素
HH = frame_t.rows;WW = frame_t.cols;int row2, col2;//循环时候计数第几行和第几列的
for (row2 = 0; row2 < HH; row2++)
{
for (col2 = 0; col2 < WW; col2++)
{
if (fabsf(rgb2h(frame_t.at(row2, col2)[0], frame_t.at(row2, col2)[1], frame_t.at(row2, col2)[2]) - cut)<5)
{
frame_t.at(row2, col2)[0] = 0;
frame_t.at(row2, col2)[1] = 0;
frame_t.at(row2, col2)[2] = 0;
}
}
}
imshow("hsvcut", frame_t);
}

运行效果如图


在上图中,我们发现这个算法可以准确地扣出蓝色区域,而且即使蓝色亮一点暗一点或是灰一点鲜艳一点,也没有影响。

接下来,我们找两个图片来抠图和合成图像。

首先是蓝幕原图


然后是背景


我们来写一下抠除蓝色部分以后与背景叠加的代码

Mat frame_orig;//采集的原始图像
frame_orig = imread("my2.jpg", CV_LOAD_IMAGE_UNCHANGED);
Mat frame_back;//采集的原始图像
frame_back = imread("my1.jpg", CV_LOAD_IMAGE_UNCHANGED);
imshow("orig", frame_orig);
imshow("back", frame_back);
Mat frame_c;//测试图像
frame_c = frame_orig;
int W, H;//图像宽度和高度 单位:像素
H = frame_orig.rows;
W = frame_orig.cols;
int row, col;//循环时候计数第几行和第几列的
for (row = 0; row < H; row++)
{
for (col = 0; col < W; col++)
{
if (fabsf(rgb2h(frame_orig.at(row, col)[0], frame_orig.at(row, col)[1], frame_orig.at(row, col)[2])-cut)
{
frame_c.at(row, col)[0] = frame_back.at(row, col)[0];
frame_c.at(row, col)[1] = frame_back.at(row, col)[1];
frame_c.at(row, col)[2] = frame_back.at(row, col)[2];
}
}
}
imshow("MyPic", frame_c);

合成以后的图像见下图右下。


接下来,我们再换个图片做一次。为了让图片增加些戏剧效果,我找来了一个热气球的蓝幕原图和一段树枝的背景。

蓝幕原图


背景


最后合成的图像见下图右上。


这种基于OpenCV的蓝幕抠图算法可以自动地对每一帧图像进行处理和合成,特别适合用于视频的实时抠图,也许可以用在自动换背景等应用上。