视频中GMM或KMeans算法,实时性不行。
算法设计步骤:
HSV 颜色空间:
代码
#include "../common/common.hpp"
static Mat background_01, background_02;
static Mat replace_and_blend(Mat &frame, Mat &mask);
void main(int argc, char** argv)
{
int arr[2][2] = { {1, 4}, {8, 12} };
int * parr = arr[0];
int num = *parr++; // 等同于 *(parr++), ++ 优先于 * 运算符,但是右++的特性是先赋值给表达式,然后再++
cout << num << "," << *parr << endl; // 1,4
int (*ppa)[2] = &arr[0]; // 二维数组的指针
int ab = **(ppa + 1); // 行指针,指针步长是一行。指针步长可以理解为将变量的数据类型去掉一个* ,然后留下的数据类型的sizeof()就是步长
cout << ab << endl; // 8
background_01 = imread(getCVImagesPath("images/bg_01.jpg"));
background_02 = imread(getCVImagesPath("images/bg_02.jpg")); // 替换成的背景
VideoCapture capture;
capture.open(getCVImagesPath("videos/01.mp4")); // 720P 的视频
if (!capture.isOpened()) printf("could not find the video file...\n");
Mat frame, hsv, mask;
bool is = true; // 打印信息
while (capture.read(frame))
{
cvtColor(frame, hsv, COLOR_BGR2HSV); // 转HSV
/*
void inRange( // 可实现二值化功能(类似threshold()函数)
InputArray src, // 输入要处理的图像,可以为单通道或多通道。
InputArray lowerb, // 包含下边界的数组或标量。
InputArray upperb, // 包含上边界数组或标量。
OutputArray dst // 输出的二值图像,与输入图像src 尺寸相同且为CV_8U 单通道
// dst每个像素位置的值为:若src对应位置像素的每个通道的颜色数据大于等于lowerb小于等于upperb对应通道的颜色数据时,dst为255,否则为0
);
*/
inRange(hsv, Scalar(35, 43, 46), Scalar(155, 255, 255), mask); // 在HSV中,Scalar的通道顺序为 H S V ?
if (is) {
is = false;
// frame depth=0, type=16, channels=3 CV_8UC3
cout << "frame depth=" << frame.depth() << ", type=" << frame.type() << ", channels=" << frame.channels() << endl;
// hsv depth = 0, type = 16, channels = 3 CV_8UC3
cout << "hsv depth=" << hsv.depth() << ", type=" << hsv.type() << ", channels=" << hsv.channels() << endl;
// mask depth=0, type=0, channels=1
cout << "mask depth=" << mask.depth() << ", type=" << mask.type() << ", channels=" << mask.channels() << endl;
}
imshow("inRange", mask); // mask 中元素的值,只有 0 或 255
// 形态学操作,降低边缘的干扰
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); // 腐蚀用的size是3,模糊的时候也要是3,效果才好
Mat result = replace_and_blend(frame, mask);
if (waitKey(1) == 27) break; // 帧间隔不用设置为帧率,因为上面的算法会耗时的
imshow("matting5-15", result); // 时效性还是有点不好。。。
imshow("sc5-15", frame);
}
}
static bool go = true; // 打印信息,此变量放到函数内部速度会变慢一点。。
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();
// replace and blend
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;
double time = getTickCount();
for (int row = 0; row < h; row++)
{
uchar* current = frame.ptr<uchar>(row);
uchar* bgrow = background_02.ptr<uchar>(row);
uchar* maskrow = mask.ptr<uchar>(row);
uchar* targetrow = result.ptr<uchar>(row);
for (int col = 0; col < w; col++)
{
m = *maskrow++;
if (m == 255) { // 背景
*targetrow++ = *bgrow++;
*targetrow++ = *bgrow++;
*targetrow++ = *bgrow++;
current += 3;
}
else if (m == 0) {// 前景
*targetrow++ = *current++;
*targetrow++ = *current++;
*targetrow++ = *current++;
bgrow += 3;
}
else {
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;
}
}
}
if (go) {
go = false;
cout << (getTickCount() - time) / getTickFrequency() << endl; // 0.00792733 ,指针速度比 mat.at<> 速度要快一个数量级!
}
return result;
}