【瞎折騰/opencv】前後景分離-幀差法提取遊戲素材

說在前面

效果展示

在這裏插入圖片描述

原圖

在這裏插入圖片描述

前景圖

原理

  • 準備一張背景圖
    在這裏插入圖片描述
  • 使用absdiff函數
    可以看到提取的挺不錯了。
    在這裏插入圖片描述
  • 使用threshold進行閾值化處理
    若不使用則會在後續處理中產生一些噪聲
    在這裏插入圖片描述
  • 使用findcontours函數找出最大輪廓,並用白色(255)填充
    在這裏插入圖片描述
  • 使用copyto函數(見這裏最後一點關於copyTo函數)將原圖中在上面白色區域的地方提取出來,得到最終結果

缺陷

  • 必須保證背景不動(這就很難受,咱這算是最基本的方法了,真要搞遊戲裏的素材還是拆包吧,咱也不會啊

Code

#include <opencv2\imgcodecs.hpp>
#include <opencv2\highgui.hpp>
#include <opencv2\imgproc.hpp>
#include <opencv2\videoio.hpp>
#include <iostream>
#include <cmath>
using namespace cv;
using namespace std;

// 這裏將結果輸出爲視頻了
const char input_video[] = "test8.mp4";
const char output_video[] = "out8.mp4";

// threshold函數的閾值,可以針對不同的視頻進行調整
const int thresh_value = 25;

// 錄製的視頻是1080的,取咱們感興趣的地方
const int x = 790;
const int y = 500;
const int width = 320;
const int height = 450;

int main(int argc, char** argv)
{
	// 分別作爲背景圖、前景圖、灰度圖(用於findcontours)
	Mat back, front, gray;
	VideoCapture cap(input_video);//打開輸入視頻

	if (!cap.isOpened())
	{
		cout << "Could not open video! " << endl;
		return -1;
	}

	// 這裏我在錄製視頻時就將背景圖錄制進去了,在視頻最後面
	// 所以直接提取視頻最後幾幀中的一幀作爲背景圖即可
	// 建議不要用其他方法(比如qq截圖啥的)截取背景圖,可能會有噪聲
	int frames = cap.get(CAP_PROP_FRAME_COUNT);// 獲取幀數
	cap.set(CAP_PROP_POS_FRAMES, frames - 2); // 跳到最後面
	cap >> back; // 獲取,這裏是1080的
	cap.set(CAP_PROP_POS_FRAMES, 1); // 再跳到開頭

	Rect roi(x, y, width, height);
	Mat back_roi = back(roi).clone(); //感興趣區域

	VideoWriter outputVideo; // 準備輸出視頻
	outputVideo.open(output_video, static_cast<int>(cap.get(CAP_PROP_FOURCC)), cap.get(CAP_PROP_FPS), back_roi.size(), true);

	int count = 0;
	for (;;)
	{
		cap >> front;	// 獲取一幀
		if (front.empty()) // 判斷是否到視頻結尾
			break;
		Mat front_roi = front(roi).clone(); // 提取感興趣區域

		Mat diff;
		absdiff(back_roi, front_roi, diff); // 幀差法
		
		cvtColor(diff, gray, COLOR_RGB2GRAY); // 轉換爲灰度圖
		threshold(gray, gray, thresh_value, 255, THRESH_BINARY); // 閾值處理
		imshow("My diff", diff);  // 測試用
	
		vector<vector<Point> > contours; // 存放contours
		cv::findContours(gray, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); // find
		Mat hole(gray.size(), CV_8U, Scalar(0)); 
		drawContours(hole, contours, -1, Scalar(255), FILLED); // 繪製輪廓,並用255填充
		
		Mat res(front_roi.rows, front_roi.cols, CV_8UC3, Scalar(0, 0, 0));// 結果圖
		front_roi.copyTo(res, hole); // copyTo
		
		++count; // 原視頻中有一部分不需要
		if(count<frames/3-20)
			outputVideo << res;

		char c = (char)waitKey(10);
		if (c == 27) break;
	}
	
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章