天天看點

opencv 綠幕背景視訊摳圖

文章目錄

        • 一、說明
        • 二、程式執行個體
        • 三、結果展示

一、說明

在視訊中采用KMeans或者GMM的算法效率太低了,用來做視訊摳圖效果不好,這裡使用了一個更加便捷的方式。就是HSV格式。

由于在視訊中隻有的綠色的背景和前景的人物,且将HSV歸一化為:

H 0 - 180
S 0 - 255
V 0 - 255
           

是以在HSV格式中綠色的範圍是:

H 35 - 155
S 43 - 255
V 46 - 255
           

具體步驟如下:

  1. 幀圖像;
  2. 轉化為HSV;
  3. 模型mask;
  4. 背景融合替換;
  5. 顯示幀圖像;
  6. 下一幀;
  7. 完成。
過程中需要注意的是,背景與視訊中的人物和背景在讀取元素的時候需要保持一緻,不然達不到效果。另外就是需要融合的圖像的背景大小要與視訊的大小一緻。

二、程式執行個體

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

using namespace cv;
using namespace std;
using namespace ml;

const char* title = "input video";
const char* result_win = "result video";
Mat background_01, background_02;

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++)
	{
		uchar* current = frame.ptr<uchar>(row); // 目前幀圖像
		uchar* bgrow = background_01.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;
			}
		}
	}

	return result;
}

int main()
{
	background_01 = imread("D:/source/images/bg2.png"); //注意圖檔的大小與視訊一緻
	background_02 = imread("D:/source/images/sky.png");
	VideoCapture capture;
	capture.open("D:/source/images/01.mp4");

	if (!capture.isOpened())
	{
		puts("could not find the video file");
		system("pause");
		return -1;
	}

	namedWindow(title, WINDOW_AUTOSIZE);
	namedWindow(result_win, WINDOW_AUTOSIZE);

	Mat frame, hsv, mask;
	int count = 0;
	while (capture.read(frame))
	{
		cvtColor(frame, hsv, COLOR_BGR2HSV);
		inRange(hsv, Scalar(35, 43, 46), Scalar(155, 255, 255), mask); 

		// 形态學操作
		Mat k = getStructuringElement(MORPH_RECT, Size(3, 3));
		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(1);
		if (c == 27) break;

		imshow(result_win, result);
		imshow(title, frame);
	}

	waitKey(0);
	return 0;
}
           

三、結果展示

需要融合的背景圖檔:

opencv 綠幕背景視訊摳圖

視訊(這裡隻能是靜态圖檔了):

opencv 綠幕背景視訊摳圖

融合後的效果:

opencv 綠幕背景視訊摳圖