背景
最近有一个需求需要实现手机视频实时识别屏幕画面目标对象,监测到目标对象之后实时将其宽高信息反馈,在这样的需求背景下,经过调研和实践,最终整理出以下分享。
服务器端 or 客户端方案
因为视频识别后还要进行一系列处理,所以对实时性很高,综合考虑最后选择了客户端方案,在用户端识别视频流数据,比较重要的两步为:视频流在浏览器端转换成图像数据、基于浏览器端图像数据识别形状。
视频流浏览器端转换成图像数据
整体的链路如下图所示:
- 首先通过 Web RTC 相关 MediaDevices 的 getUserMedia 访问后置摄像头,返回一个MediaStream媒体流对象;
- 将 的 srcObject 属性设置为MediaStream媒体流对象,并配置 autoplay属性自动播放;
- 调用 Canvas 元素对象 的 drawImage 方法把 HTML Video 元素传入,绘制当前时刻的视频画面,这是把某一时刻或某一帧摄像头捕捉的影像渲染到Canvas元素;
- 将步骤3传入 window.requestAnimationFrame 请求动画帧方法,递归调用渲染画面,requestAnimationFrame会根据当前系统显示刷新频率执行动画回调函数,让视频画面动起来
基于浏览器端图像识别
将视频流在浏览器端转化为图像数据之后,我们就可以基于浏览器端进行图像识别了,但我们在具体开始之前需要想清楚几个问题:
- 识别的目标对象是什么?
- 怎么找到这个目标对象?
1. 确定识别目标对象
为了识别显眼,我们预设识别的目标为正红色,那具体是什么形状呢?
已知色块中某一像素点,沿此点x轴和y轴向两端做边缘检测,
椭圆色块识别:得到的边缘差值不一定等于椭圆的长轴和短轴,需要基于该点的水平和垂直方向所有点进行扫描对比宽高边缘差值,获取最大的宽高边缘,时间复杂度至少为O(n*n)
矩形色块识别:扫描一次x轴和y轴直线上所有点就可以得到结果,时间复杂度为O(n)
可推导出直接基于目标矩形检测就可以完成和椭圆一致的效果,同时算法性能远大于基于椭圆的检测。视频帧之间的间隔是毫秒级别的,所以对在这之间执行的检测程序性能要求比较高。
2. 颜色对比
因为后续两个步骤:查找图像目标颜色像素点和边缘检测都需要进行颜色对比,所以先讲解颜色对比
浏览器 HTML Canvas 提供了对画布图像像素级操作的接口,比如:可以通过 Canvas 2D 渲染上下文的createImageData()方法基于图像像素数据绘制画布,getImageData()获取画布当前图片像素数据,putImageData()更新画布图像。操作和依赖的是ImageData图像数据类型,ImageData提供了3个属性,ImageData.width、ImageData.height、ImageData.data, 其中ImageData.data为8位无符号整型固定数组类型,数组项值只能为0到255区间的无符号整型构成。数组每4位表示一个像素RGBA的值,RGBA是一种色彩空间模型,由RGB色彩空间和Alpha通道组成,其中RGB负责颜色显示,Alpha通道表示不透明度,RGB色彩空间是基于三原色光模式也叫RGB色彩模型或红绿蓝颜色模型,现在彩色显示器都应用了三原色光加色技术,一个物理像素由3个子像素构成,分别负责显示红、绿、蓝不同强度的原色光,人眼会基于此3色叠加产生一个生理感知色。下图举了一个白色像素的示例说明RGB色彩模型。
下图展示了3px*3px红色边框正方形Canvas画布 ImageData数据与图像像素的关系,根据行(x)、列(y)读取某像素点的 R/G/B/A 值的公式为:imageData.data[((point.y * (imageData.width * 4)) + (point.x * 4)) + 0/1/2/3]。
获取某一像素点RGBA值的公式:
rIndex = y * (imageData.width*4) + x * 4
r = imageData.data[rIndex]
g = imageData.data[rIndex+1]
b = imageData.data[rIndex+2]
a = imageData.data[rIndex+3]
最后得到颜色对比的方法:对比预设颜色和像素点的rgb通道值是否相同
颜色对比二值化
以上颜色对比的方法有一个问题,相近颜色的r、g、b通道值有时差距很大,比如:#FF0000(rgba(255, 0, 0, 255))和#FF6666(rgba(255, 102, 102, 255))的g、b通道差值为102,但颜色很接近,容易产生错误判断,且还可能受到环境光,显示设备对色彩的还原度等等因素的影响,导致我们很难去设定一个误差值判断在误差范围内两个颜色对比是否相等
那如何解决这个问题呢?我们是可以预设目标检测色块的颜色甚至背景。所以可以把图片颜色转换成只有2种颜色,也就是二值化,一般采用黑白色搭配。实现逻辑是:扫描图片每个像素,当像素R、G、B通道均值大于127 把像素RGB通道值都转换成255(白色)否则转换成0(黑色),使用127判断是因为它是0-255最中间的值。此方法让上图近似红、拍摄后显示的屏幕红、正红全部变为白色,解决了颜色判断误差问题。
3. 查找图像目标颜色像素点
当确定了识别的目标对象和颜色对比的方法之后,我们就要想办法找到这个目标识别对象了,第一步要做的呢就是找到目标对象中的任意一个像素点。假设色块颜色在大屏页面中是红色色值:#FF0000,并且拍摄画面中无其他区域同色干扰。基于这个假设条件,只需要找到所拍摄的当前帧图像中的同色像素点即可。方法可以有:
- 扫描图像所有像素点,如果图像像素为750px * 1200px,那需要执行750 * 1000次色值对比;
- 设置网格点阵坐标,把预设的网格点阵坐标存入数组,再遍历数组对每一个点做色值对比;
- 只对拍摄画面中心点像素坐标做色值对比,点位置固定为: (x=image.width/2, y=image.height/2);
第一种方法虽然能一个像素都不漏地检测,但此场景目标色块区域比较大,完全没有必要做全量扫描,时间复杂度O(n * n);
第二种方法大大减少了循环次数,兼顾性能的同时支持检测多个形状,时间复杂度O(n )
第三种方法只需要基于一个像素点做颜色对比,时间复杂度O(1 )
最终选择方法3,只检测画面中心点像素。当然这个方法需要用户保证红色矩形色块区域能覆盖画面中心像素点,可以为拍摄画面增加辅助标记及提示,类似于相机的对焦瞄准框,引导用户便捷地完成此操作。
4. 边缘检测
当找到红色矩形色块中的某个像素点后,就可以基于此像素点的x轴水平方向两端,y轴垂直方向两端做边缘检测,颜色比对二值化解决颜色误差后,具体逻辑就变成判断同轴方向相邻两个像素点颜色R、G、B通道差值是否大于0。水平方向两端边缘点的x轴坐标差等于矩形的宽度,垂直方向两端边缘点y轴坐标差等于矩形的高度,左边缘点x,上边缘点y为红色矩形色块的左顶位置坐标。
最后就可以获得并返回红色矩形的宽高了