AI学习者 · 2022年02月09日

OpenCV黑魔法之隐身衣 | 附源码

计算机视觉概论

我计划分享一些有趣的实战项目,或许达不到商用的级别,但是希望能在大家做项目的时候能够提供一些思路!

基于OpenCV的颜色检测和分割的隐形斗篷

如果你是个哈利波特迷,你就会知道什么是隐形衣。是的!这是哈利波特用来隐身的隐形衣。当然,我们都知道隐形衣不是真的——它都是图形上的诡计。

在这篇文章中,我们将学习如何使用OpenCV中的简单计算机视觉技术创建我们自己的“隐形衣”。文末会分享 C++和python的代码。
image.png
哈利波特的隐身衣效果

那是哈利·波特在试他的隐形衣!

事实上,你可以用一种叫做颜色检测和分割的图像处理技术来创造这种神奇的体验。好消息是,你不需要成为霍格沃茨的一员!你所需要的是一块红色的布,并遵循这篇文章。

看看下面的视频,我在那里尝试我自己的隐形衣!
image.png
颜色坚持+分割的demo效果

它的原理是什么?

该算法在原理上与绿幕非常相似。但与我们删除背景的绿幕不同,在这个应用中,我们删除了前景!

我们用一块红色的布做我们的斗篷。为什么是红色呢?为什么不绿色的吗?当然,我们可以用绿色,红色不是魔术师的颜色吗?除了此之外,像绿色或蓝色这样的颜色也可以稍微调整一下。

其基本思想如下:

  1. 捕获并存储背景帧。
  2. 使用颜色检测算法检测红色布料。
  3. 将红色的布料分割成一个mask。
  4. 生成最后的增广输出,创造神奇的效果。
    640.gif
    上面的GIF简单地解释了算法的所有阶段。现在我们将详细讨论每一步。

步骤1:捕捉并存储背景帧

如上所述,关键思想是将当前与布料相对应的帧像素替换为背景像素,从而产生一件隐身衣的效果。为此,我们需要存储一个背景帧。

C++

// Create a VideoCapture object and open the input file
// If the input is the web camera, pass 0 instead of the video file name
VideoCapture cap("video4.mp4");
// Check if camera opened successfully
if(!cap.isOpened()){
  cout << "Error opening video stream or file" << endl;
  return -1;
}


Mat background;
for(int i=0;i<30;i++)
{
  cap >> background;
}
//Laterally invert the image / flip the image.
flip(background,background,1);

Python

# Creating a VideoCapture object
# This will be used for image acquisition later in the code.
cap = cv2.VideoCapture("video.mp4")

# We give some time for the camera to warm-up!
time.sleep(3)

background=0

for i in range(30):
  ret,background = cap.read()

# Laterally invert the image / flip the image.
background = np.flip(background,axis=1)

在上面的代码中,cap.read()方法使我们能够通过相机捕获最新的帧(存储在变量‘background’中),它还返回一个布尔值(True/False存储在‘ret’中)。如果一个帧被正确读取,它将为真。所以你可以通过检查这个返回值来检查视频的结束。

为什么捕获背景图像使用'循环'?

因为背景是静态的,我们不能简单地使用一个帧吗?当然,但是与多帧图像相比,捕获的图像有点暗。这是因为相机刚刚开始捕捉帧,因此它的参数还不稳定。因此,使用for循环捕获静态背景的多个图像就可以完成这个任务。

多帧平均也可以降低噪声。

第二步:检测红色

因为我们使用了一块红色的布来将它转换成一件隐形斗篷,所以我们将着重于在框架中检测红色。

听起来简单吗?我们有一个RGB(红-绿-蓝)图像,使用简单阈值的R通道来得到我们的mask。结果证明,这将并不会特别有效,因为RGB值是高度敏感的照明。因此,即使斗篷是红色的,也可能有一些区域,由于阴影,相应像素的红色通道值相当低。

正确的方法是将图像的颜色空间从 RGB转换为HSV(色相-饱和度-亮度)。

HSV颜色空间是什么?

HSV颜色空间表示使用三个值的颜色

1.色相(Hue):这个通道对颜色信息进行编码。色相可以被认为是一个角度,0度对应红色,120度对应绿色,240度对应蓝色。

2.饱和度(Saturation):这个通道编码颜色的强度/纯度。例如,粉色比红色的饱和度低。

3.值(Value):该通道对颜色的亮度进行编码。图像的阴影和光泽成分出现在这个通道中。

不像RGB是根据三原色来定义的,HSV的定义方式类似于人类感知颜色的方式。

对于我们的应用而言,使用 HSV 颜色空间的主要优点是颜色/色调/波长仅由色相组件表示。

要了解不同的色彩空间,请参考我们关于色彩空间的详细博客

https://www.learnopencv.com/color-spaces-in-opencv-cpp-python/

所以当我说,我需要一个特定的颜色,选择色相组件,然后根据饱和度组件,我得到了那个颜色的不同的阴影,进一步根据值组件,我得到了一个颜色的特定阴影的不同的强度。

在下面的代码中,我们首先捕获一个活动帧,将图像从RGB转换为HSV颜色空间,然后定义一个特定范围的H-S-V值来检测红色。

C++

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

// Laterally invert the image / flip the image
flip(frame,frame,1);

//Converting image from BGR to HSV color space.
Mat hsv;
cvtColor(frame, hsv, COLOR_BGR2HSV);

Mat mask1,mask2;
// Creating masks to detect the upper and lower red color.
inRange(hsv, Scalar(0, 120, 70), Scalar(10, 255, 255), mask1);
inRange(hsv, Scalar(170, 120, 70), Scalar(180, 255, 255), mask2);

// Generating the final mask
mask1 = mask1 + mask2;

Python

# Capturing the live frame
ret, img = cap.read()

# Laterally invert the image / flip the image
img  = np.flip(imgaxis=1)

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

# Range for lower red
lower_red = np.array([0,120,70])
upper_red = np.array([10,255,255])
mask1 = cv2.inRange(hsv, lower_red, upper_red)

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

# Generating the final mask to detect red color
mask1 = mask1+mask2

inRange 函数简单地返回一个二值化掩码,其中白色像素(255)表示属于上限和下限范围的像素,黑色像素(0)不属于上限和下限范围的像素。

色相值 实际上分布在一个圆上(范围在0-360度之间),但在OpenCV中为了适应8bit 值,其范围是0-180度。红色由0-30和150-180值表示。

我们使用范围0-10和170-180,以避免检测皮肤为红色。饱和度使用较高范围120-255的值,因为我们的布料应该是高度饱和的红色。亮度值在的较低范围是70,这样我们也可以在布料的褶皱中检测到红色。

mask1 = mask1 + mask2

使用上面的线,我们合并两个红色范围生成的mask。它基本上是在像素上进行OR操作。这是一个操作符重载+的简单例子。

现在,您已经了解了如何进行颜色检测,您可以更改H-S-V范围,并使用一些其他的单色布来代替红色。事实上,绿色的布比红色的效果更好,因为绿色离人的肤色差异最大。

第三步:将检测到的红色布料分割开来

在上一步中,我们生成了一个mask来确定帧中与检测到的颜色相对应的区域。我们精炼这个mask,然后用它从frame上分割布料。下面的代码说明了它是如何实现的。

C++

Mat kernel = Mat::ones(3,3, CV_32F);
morphologyEx(mask1,mask1,cv::MORPH_OPEN,kernel);
morphologyEx(mask1,mask1,cv::MORPH_DILATE,kernel);

// creating an inverted mask to segment out the cloth from the frame
bitwise_not(mask1,mask2);
Mat res1, res2, final_output;

// Segmenting the cloth out of the frame using bitwise and with the inverted mask
bitwise_and(frame,frame,res1,mask2);

Python

mask1 = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((3,3),np.uint8))
mask1 = cv2.morphologyEx(mask, cv2.MORPH_DILATE, np.ones((3,3),np.uint8))

#creating an inverted mask to segment out the cloth from the frame
mask2 = cv2.bitwise_not(mask1)

#Segmenting the cloth out of the frame using bitwise and with the inverted mask
res1 = cv2.bitwise_and(img,img,mask=mask2)

image.png
步骤4:生成最终的增强输出,以创建一个神奇的效果。

最后,我们将检测到的红色区域的像素值替换为静态背景对应的像素值,最后生成一个增强输出,产生神奇的效果,将我们的布料变成了一件隐身斗篷。为此,我们首先使用bitwise_and操作创建一个像素值对应于检测区域的图像,像素值等于静态背景的像素值,然后将输出添加到我们从中分割出红布的图像(res1)中。

C++

// creating image showing static background frame pixels only for the masked region
bitwise_and(background,background,res2,mask1);

// Generating the final augmented output.
addWeighted(res1,1,res2,1,0,final_output);
imshow("magic", final_output);
waitKey(1);

Python

# creating image showing static background frame pixels only for the masked region
res2 = cv2.bitwise_and(background, background, mask = mask1)


#Generating the final output
final_output = cv2.addWeighted(res1,1,res2,1,0)
imshow("magic",final_output)
cv2.waitKey(1)

image.png

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

原文:小白学视觉
作者:AI_study

推荐阅读

更多嵌入式AI技术相关内容请关注嵌入式AI专栏。
推荐阅读
关注数
18811
内容数
1353
嵌入式端AI,包括AI算法在推理框架Tengine,MNN,NCNN,PaddlePaddle及相关芯片上的实现。欢迎加入微信交流群,微信号:aijishu20(备注:嵌入式)
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息