[OpenCV实战]3 透明斗篷

时间:2022-12-17 19:57:46


目录

​​1寻找和存储背景帧​​

​​2红色区域检测​​

​​3提取红色区域​​

​​4背景帧红布区域替换当前帧红布区域。​​

​​5工程代码​​

​​参考​​


弄出哈利波特电影里一样效果的透明斗篷。也就是一个视频里,将红布弄成透明。类似下面的效果。

[OpenCV实战]3 透明斗篷

基本思想如下:

1寻找和存储背景帧。

2用颜色检测算法检测红色布。

3提取红色区域。

4背景帧红布区域替换当前帧红布区域。

1寻找和存储背景帧

算法关键思想是用背景像素替换与布相对应的当前帧像我们需提取和存储背景帧。背景帧检测算法很简单,实际上算不上背景帧建模算法。仅仅设定视频第31帧为背景图像。如果想了解背景帧提取算法可以看看背景建模算法,比如混合高斯背景建模算法。

C++代码如下:

Mat background;
//跳过前30帧
for (int i = 0; i < 30; i++)
{
cap >> background;
}
//沿Y轴翻转图像
flip(background, background, 1);

2红色区域检测

事实上基于RGB空间检测红色很困难,因为红色是RGB综合获得的。正确的方法是将图像从RGB颜色空间转到HSV空间。HSV对颜色的定义更接近人的视觉系统。对于颜色检测来说,HSV空间中,

HSV空间各个参数如下:

色调Hue:用角度度量,取值范围为0-度360度。可以认为0度对应于红色,120度对应于绿色,240度对应于蓝色。

饱和度Saturation:饱和度表示颜色的强度和纯度。例如,粉红色不如大红色饱和。

明度Value:表示颜色的明暗程度,取值范围为0.0(黑色)~1.0(白色)。

[OpenCV实战]3 透明斗篷

颜色仅由色调Hue决定。在OpenCV中色调不是0到360度,而被量化为0到180。

其中红色以0-30和150-180表示。

 

红色区域检测主要原理如下:

基于OpenCV中的inRange函数筛选颜色。其中红色Hue值的范围为0-10和170-180,以避免发现皮肤为红色。因为红布应该是高度饱和的红色。所以S值设定为120到255。明度设置为70到255。根据以上能够获得红色Hue值的范围为0-10和170-180d的两个红色区域。然后对其做并操作提取图像红色区域(红布区域)范围二值图像。所获得二值图像中白色部分(像素值为255)表示红布,黑色部分(像素值为0)表示背景。结果如下图所示:

[OpenCV实战]3 透明斗篷

[OpenCV实战]3 透明斗篷

c++代码如下:

//检测帧
Mat frame;

// Capture frame-by-frame
cap >> frame;

// If the frame is empty, break immediately
if (frame.empty())
{
break;
}
//hsv图像
Mat hsv;
flip(frame, frame, 1);
cvtColor(frame, hsv, COLOR_BGR2HSV);

//红色区域1,红色区域2
Mat mask1, mask2;
//红色区域
Mat mask_red;
//背景区域
Mat mask_background;
//过滤颜色
//二值图,其中黑色0表示无红色,白色1表示红色区域。
inRange(hsv, Scalar(0, 120, 70), Scalar(10, 255, 255), mask1);
inRange(hsv, Scalar(170, 120, 70), Scalar(180, 255, 255), mask2);
mask_red = mask1 + mask2;

3提取红色区域

主要是通过红布区域范围二值图像提取红色区域图像(背景为黑色),并将背景图像中红布区域置为黑色。结果分别如下图所示

[OpenCV实战]3 透明斗篷

[OpenCV实战]3 透明斗篷

c++代码如下:

//去除噪声
Mat kernel = Mat::ones(3, 3, CV_32F);
morphologyEx(mask_red, mask_red, cv::MORPH_OPEN, kernel);
morphologyEx(mask_red, mask_red, cv::MORPH_DILATE, kernel);

//将mask_red中0,1互换,得到背景区域范围。
bitwise_not(mask_red, mask_background);
Mat res1, res2, final_output;
//从当前帧抠出背景区域res1,红布区域被涂成黑色。
bitwise_and(frame, frame, res1, mask_background);
//从背景帧提取红布区域覆盖的背景res2
bitwise_and(background, background, res2, mask_red);

4背景帧红布区域替换当前帧红布区域。

最后通过addWeighted函数将上面两张图融合。这样就能弄出透明的效果。只能针对特定视频,所用视频及代码见:


​https://github.com/luohenyueji/OpenCV-Practical-Exercise​

如果没有积分(系统自动设定资源分数)看看参考链接。我搬运过来的,大修改没有。pch是预编译文件。视频有红布出现在第250帧后。

5工程代码

实际上这种方法只有在特定场合实用,来练手还是挺适合的。全部代码如下:

C++版本

#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
//打开视频
VideoCapture cap("video/detect.mp4");

// 检查视频是否打开
if (!cap.isOpened())
{
cout << "Error opening video stream or file" << endl;
return -1;
}

Mat background;
//跳过前30帧
for (int i = 0; i < 30; i++)
{
cap >> background;
}
//沿Y轴翻转图像
flip(background, background, 1);
//红布第251帧才出现,跳过前250帧
for (int i = 0; i < 220; i++)
{
Mat frame_slip;
cap >> frame_slip;
continue;
}


//图像读取
while (1)
{
//检测帧
Mat frame;

// Capture frame-by-frame
cap >> frame;

// If the frame is empty, break immediately
if (frame.empty())
{
break;
}
//hsv图像
Mat hsv;
flip(frame, frame, 1);
cvtColor(frame, hsv, COLOR_BGR2HSV);

//红色区域1,红色区域2
Mat mask1, mask2;
//红色区域
Mat mask_red;
//背景区域
Mat mask_background;
//过滤颜色
//二值图,其中黑色0表示无红色,白色1表示红色区域。
inRange(hsv, Scalar(0, 120, 70), Scalar(10, 255, 255), mask1);
inRange(hsv, Scalar(170, 120, 70), Scalar(180, 255, 255), mask2);
mask_red = mask1 + mask2;

//去除噪声
Mat kernel = Mat::ones(3, 3, CV_32F);
morphologyEx(mask_red, mask_red, cv::MORPH_OPEN, kernel);
morphologyEx(mask_red, mask_red, cv::MORPH_DILATE, kernel);

//将mask_red中0,1互换,得到背景区域范围。
bitwise_not(mask_red, mask_background);
Mat res1, res2, final_output;
//从当前帧抠出背景区域res1,红布区域被涂成黑色。
bitwise_and(frame, frame, res1, mask_background);
//从背景帧提取红布区域覆盖的背景res2
bitwise_and(background, background, res2, mask_red);

addWeighted(res1, 1, res2, 1, 0, final_output);
//展示图像
imshow("Magic !!!", final_output);
// Press ESC on keyboard to exit
char c = (char)waitKey(1);
if (c == 27)
{
break;
}
}

return 0;
}

python代码:

import cv2
import numpy as np
import time


# Creating an VideoCapture object
# This will be used for image acquisition later in the code.
cap = cv2.VideoCapture('video/detect.mp4')

if cap.isOpened:
print("your video is opened")
# We give some time for the camera to setup
time.sleep(3)
count = 0
background=0

# Capturing and storing the static background frame
for i in range(60):
ret,background = cap.read()

background = np.flip(background,axis=1)


#跳帧
for i in range(200):
slip_frame = cap.read()
while(cap.isOpened()):

ret, img = cap.read()
if not ret:
break
count+=1
img = np.flip(img,axis=1)

# Converting the color space from BGR to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# Generating mask to detect red color
lower_red = np.array([0,120,70])
upper_red = np.array([10,255,255])
mask1 = cv2.inRange(hsv,lower_red,upper_red)

lower_red = np.array([170,120,70])
upper_red = np.array([180,255,255])
mask2 = cv2.inRange(hsv,lower_red,upper_red)

mask1 = mask1+mask2

# Refining the mask corresponding to the detected red color
mask1 = cv2.morphologyEx(mask1, cv2.MORPH_OPEN, np.ones((3,3),np.uint8),iterations=2)
mask1 = cv2.dilate(mask1,np.ones((3,3),np.uint8),iterations = 1)
mask2 = cv2.bitwise_not(mask1)

# Generating the final output
res1 = cv2.bitwise_and(background,background,mask=mask1)
res2 = cv2.bitwise_and(img,img,mask=mask2)
final_output = cv2.addWeighted(res1,1,res2,1,0)

cv2.imshow('Magic !!!',final_output)
k = cv2.waitKey(10)
if k == 27:
break

参考

​https://www.learnopencv.com/invisibility-cloak-using-color-detection-and-segmentation-with-opencv/​