首先说明,参考的博客
https://blog.csdn.net/linqianbi/article/details/78975998
https://blog.csdn.net/linqianbi/article/details/79151960
一.算法的选择
绿幕背景视频抠图对实时性要求比较高,如果使用kmeans或者GMM的话那么就太耗时了,达不到要求,因此将RGB空间转换到HSV色彩空间进行处理。
关于HSV参考上面的博客
二.本实例的程序设计思路
三.源程序
(和网上的一样,我自己加了些注释,方便自己理解)
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
#define WINDOW_NAME1 "title"
#define WINDOW_NAME2 "resultWin"
Mat replace_and_blend(Mat &frame, Mat &mask);//绿幕抠图的实现函数
Mat background_01;
Mat background_02;
int main(int argc, char** argv)
{
//定义你想替换的背景,背景1和背景2都可选择
background_01 = imread("E:\\图像处理图片\\34.jpg");
background_02 = imread("E:\\图像处理图片\\35.jpg");
VideoCapture capture; //读取视频
capture.open("E:\\图像处理图片\\01.mp4");
if (!capture.isOpened()) //检验是否读取到视频
{
printf("could not find the video file...\n");
return -1;
}
namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);
Mat frame, hsv, mask; //frame表示每一帧的图像
while (capture.read(frame)) //检测读取到的每一帧图像
{
cvtColor(frame, hsv, COLOR_BGR2HSV); //将帧图像转为HSV
inRange(hsv, Scalar(35, 43, 46), Scalar(155, 255, 255), mask);
//inRange()函数可实现二值化功能(这点类似threshold()函数),更关键的是可以同时针对多通道进行操作。
//主要是将在两个阈值内的像素值设置为白色(255),而不在阈值区间内的像素值设置为黑色(0),该功能类似于之间所讲的双阈值化操作。
//函数原型:void inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst);
// 参数1:输入要处理的图像,可以为单通道或多通道。
// 参数2:包含下边界的数组或标量。
// 参数3:包含上边界数组或标量。
// 参数4:输出图像,与输入图像src 尺寸相同且为CV_8U 类型。
// 请注意:该函数输出的dst是一幅二值化之后的图像。
// 形态学操作
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(mask, mask, MORPH_CLOSE, k); //闭运算
erode(mask, mask, k); //腐蚀
GaussianBlur(mask, mask, Size(3, 3), 0, 0); //高斯模糊
Mat result = replace_and_blend(frame, mask);
char c = waitKey(30); //停顿30毫米给计算机做处理
if (c == 27) //按下“ESC”键即退出程序
{
break;
}
imshow(WINDOW_NAME2 result);
imshow(WINDOW_NAME1, frame);
}
waitKey(0);
return 0;
}
//对视频的每一帧的图像进行处理
Mat replace_and_blend(Mat &frame, Mat &mask)
{
Mat result = Mat::zeros(frame.size(), frame.type());
int h = frame.rows;
int w = frame.cols;
int dims = frame.channels();
// 背景的融合替换
int m = 0; //图像的像素值
double wt = 0; //初始化权重值,即融合的比例
//输出像素
int r = 0, g = 0, b = 0;
int r1 = 0, g1 = 0, b1 = 0;
int r2 = 0, g2 = 0, b2 = 0;
//这里的遍历像素选择指针,因为指针的效果速度快,效率高
for (int row = 0; row < h; row++)
{
//访问图像像素有三种可行的方法:
//指针访问访问的速度最快,Mat类可以通过ptr函数得到图像任意一行的首地址,
//同时,Mat类的一些属性也可以用到公有属性 rows和cols 表示行和列
//通道数可以通过channels()函数获得;
uchar* current = frame.ptr<uchar>(row); //current为帧图像的行数,即高度
uchar* bgrow = background_02.ptr<uchar>(row); //bgrow为要替换的背景图像的行数,即高度
uchar* maskrow = mask.ptr<uchar>(row); //maskrow为二值化后的图像mask的行数,即高度
uchar* targetrow = result.ptr<uchar>(row); //targetrow为输出结果图像的行数,即高度
for (int col = 0; col < w; col++)
{
m = *maskrow++; //读取mask的像素值,每运行一次行数加一
if (m == 255)
{// 背景
*targetrow++ = *bgrow++;
*targetrow++ = *bgrow++;
*targetrow++ = *bgrow++;
current += 3; //将frame的图像的像素的通道也移动单个保持一致
}
else if (m == 0)
{// 前景
*targetrow++ = *current++;
*targetrow++ = *current++;
*targetrow++ = *current++;
bgrow += 3; //将background_02的图像的像素的通道也移动单个保持一致
}
else
{
//要替换的背景图background_02每个像素的三个通道
b1 = *bgrow++;
g1 = *bgrow++;
r1 = *bgrow++;
//每一帧每一个像素的三个通道
b2 = *current++;
g2 = *current++;
r2 = *current++;
// 权重
wt = m / 255.0;
// 混合
b = b1 * wt + b2 * (1.0 - wt);
g = g1 * wt + g2 * (1.0 - wt);
r = r1 * wt + r2 * (1.0 - wt);
*targetrow++ = b;
*targetrow++ = g;
*targetrow++ = r;
}
}
}
return result;
}
程序运行后的结果却不尽人意,原视频和背景换掉的视频都播放的很慢,我调了等待的时间也没用,不知道是电脑的问题,还是程序的问题,总之没解决,希望看到的大佬能给我指点下。。。