最近公司让做了一个视频实时植入演示系统,
简单来说就是将场景中需要替换的区域,比如电视屏幕等,用纯色绿幕盖住,然后进行拍摄:
图1
图2
然后利用色块检测确定视频中绿幕的区域大小及边缘位置,将摄像头实时画面,使其置换原有绿幕位置,从而实现一个带有摄像头实时画面的画中画效果。
图3
图4
图5
这个东西大概花了一周时间做完,一开始搜了下网上关于绿幕抠像的资料,基本都是吊威亚的戏扣背景什么的,没找到这种需求的类似算法,然后自己想到了一个简易的算法,在这里分享一下。
算法概述:
在视频播放过程中,会对视频的每一帧画面进行抽帧,变成Bitmap, 然后我们可以获取到它的BitmapData:
图6
获取包围矩形:
接下来就是要获取当前帧位图中能够包围某色块的最小矩形,我们利用AS3的getColorBoundsRect方法。
getColorBoundsRect方法是检测某一矩形内包含(或不包含)某个指定颜色的区域,并返回该区域的坐标和宽高,这里我是检测包含0xFF00BC00(绿幕颜色)色值的区域,如下图中玫红色矩形框所示。
图7
获取绿色块的四个顶点:
有了包围矩形,接下来就是在这个矩形框内获取绿色块的四个顶点了。包围矩形的作用就是给我们提供了一个最小的搜索范围,减少遍历次数,也就是减小计算量。下面说一下搜索算法:
首先将矩形框划分成四个区域:
图8
我们画出图7中矩形框的两条对角线:
图9
对角线一半的长度记为r。以r为半径画一个圆,
图10
以(-r, 0)为起点,作一条垂线,从x轴负半轴开始沿x轴正方向运动,
图11
我们取垂线与当前以r为半径的圆的交点pt (locX, locY), 根据r及当前的locX的值,可以计算出locY的值。然后判断一下该点是否在枚红色矩形rect区域范围内,如果不在,则将locX加一,重复计算,直至locX == 0; 如果未找到目标颜色值的像素点,则将r减一,再画一个圆,再重复以上步骤。如果某一点pt1(locX1,locY1)在矩形rect区域范围内,然后判断此点的像素颜色值知否是目标颜色值(绿块颜色,这里是0xFF00BC00),如果不是,则将该点像素颜色值改为白色,以绘制出搜索路径。如果是,则停止循环,该点即是绿块区域的其中一个顶点,然后跳到下一个区域(bottomRight)重复此步骤进行计算。
最后就可以得到绿块区域的四个顶点(红色)及边线(白色),以及搜索过程中矩形区域内的非目标颜色值排除点的集合(白色弧线簇)。
搜索之后的结果如图12所示
图12
可以看到四个顶点的位置及连接起来的边线并未与绿块的实际顶点和边线重合,这是因为绿幕的边缘颜色值与整体颜色值会有一些误差的原因,我们只需要给四个顶点加一个手动的偏移值去修正这个误差即可,
var ptOffSet1:Point =new Point(-8, -8);
var ptOffSet2:Point =new Point(8, -8);
var ptOffSet3:Point =new Point(8, 8);
varptOffSet4:Point = new Point(-8, 8);
point1 = pt1.add(ptOffSet1);
point2 = pt2.add(ptOffSet2);
point3 = pt3.add(ptOffSet3);
point4 =pt4.add(ptOffSet4);
修正后的结果:
图13
总结一下就是采用了分块及分区域方法,减少遍历次数,降低算法的时间复杂度,用以满足视频播放过程中每帧的实时计算。
后面的东西因为涉及公司的业务,就不发了。完成效果如下:
图13
图14
检测部分的算法单独写了一个工程放到github上了,地址:
https://github.com/disini/GreenBlocksDetect