技术目标:
通过摄像机提供的实时视频,分析视频画面中的扑克牌,实现程序自动识别扑克牌功能。最终淘汰通过扫描仪和条形码去扫描扑克的旧技术。
技术要点:
l 识别扑克的位置
l 识别扑克的数字
l 识别扑克的花色
l 提高识别率
实现方法:
技术基础:开源的OpenCV库、Python的numpy数据分析库。其中OpenCV是图像识别的核心库;Numpy库用于分析图片差异,计算轮廓,灰度处理等数据处理。
l 位置识别:有算法可以识别扑克的轮廓,然后取中心点坐标。
l 数字识别:对数字的识别有源码可直接用,研发难度不高。
l 扑克花色:可以按识别数字的方式拍照、训练来实现。
l 提高识别率:这个还需要继续研究,确保100%精确。
主要步骤:
l 每隔200ms从摄像机截取视频的图片
l 将截取到的图片数据二值化,
l 调节阈值,使扑克轮廓和数字刚好清晰可见
l 查找图片最外层轮廓,并截取出轮廓内部的图片
l 使用漫水填充算法把扑克牌四周的多余的背景变成和扑克牌牌面背景一样的白色像素
l 此时图片只剩白色背景以及黑色的扑克牌数字、花色、头像等,再查找最左上角轮廓并截取出,这就是扑克牌的数字
l 从余下的图片中再查找最左边的轮廓并截取出,这就是扑克牌的花色
l 可以将数字和花色都预先保存下来,进行一些处理得到预测模型
l 得到模型后就可以完成一套自动识别扑克牌的系统了
具体步骤和主要函数
摄像机视频截图
获取截图函数:
cv2.cv2.VideoCapture.VideoCapture def __init__(self,
*args: Any,
**kwargs: Any) -> None
画图函数:
cv2.cv2 def imshow(winname: Any,
mat: Any) -> None
图片二值化
对截取的图片二值化
函数:
cv2.cv2 def threshold(src: Any,
thresh: Any,
maxval: Any,
type: Any,
dst: Any = None) -> None
下图是二值化后的图片。二值化后看不清点数,还需要调节阈值:
调节阈值
就是调节threshold函数的thresh参数,范围1-255。
如果调节阈值过度,会导致下面情况,:
阈值调节正常后,刚好可以看清点数和花色,如下:
优化后的自适应阈值:
根据图像上的每一个小区域计算与其对应的阀值。因此在同一幅图像上的不同区域采用的是不同的阀值,从而使我们能在亮度不同的情况下得到更好的结果
方法:
cv2.cv2 def adaptiveThreshold(src: Any,
maxValue: Any,
adaptiveMethod: Any,
thresholdType: Any,
blockSize: Any,
C: Any,
dst: Any = None) -> None
自适应阈值图像处理如下:
分析轮廓
寻找轮廓函数:
cv2.cv2 def findContours(image: Any,
mode: Any,
method: Any,
contours: Any = None,
hierarchy: Any = None,
offset: Any = None) -> None
画出轮廓函数:
cv2.cv2 def drawContours(image: Any,
contours: Any,
contourIdx: Any,
color: Any,
thickness: Any = None,
lineType: Any = None,
hierarchy: Any = None,
maxLevel: Any = None,
offset: Any = None) -> None
计算中心点的坐标
获取轮廓的宽、高、和坐标
cv2.cv2 def boundingRect(points: Any) -> None
然后再通过宽、高、和坐标计算出中心点的坐标
中心点在下图绿色小点标出
通过OpenCV的画图接口,画出坐标值
cv2.cv2 def putText(img: Any,
text: Any,
org: Any,
fontFace: Any,
fontScale: Any,
color: Any,
thickness: Any = None,
lineType: Any = None,
bottomLeftOrigin: Any = None) -> None
训练库
匹配的方法就是先对视频截图,二值化,保存特征图片到训练库。特征图片就是数字、花色
然后用训练库的特征图片和视频流里面的数据截取对比,对比二者的差距,差距小于阈值则认为匹配成功。
优化后的训练方法是:
每一个数字,在不同倾斜角度、不同光线强度、不同位置拍摄一组图片作为训练库,提高识别率。
匹配数字
对比图片差异的函数:
cv2.cv2 def absdiff(src1: Any,
src2: Any,
dst: Any = None) -> None
匹配过程简单地说就是逐一对比训练库里面的所有数字图片,差异最小的即为匹配成功。
具体流程是:截取二值化的图片的数字部分的位图,再跟训练库保存的位图比较,统计出不一致的像素的个数。逐个对比训练库里所有图片,计算差异。找出像素差异最少的图,而且同时差异像素小于1000个像素的图片,就算匹配成功。如下:
优化后的新流程是:
原图灰度处理 --> 裁剪出扑克 -->拉伸 -->二值化 --> 切割 -->重新寻找轮廓 --> 伸到训练库尺寸 -->对比差异像素 -->计算相似度 --> 相似度最高的为匹配成功
同时判断下方的数字花色,这样在上方数字不可辨认情况下,可以检查扑克下方的数字,从而提高了识别率,如图:
识别数字如下:
匹配花色
“匹配数字” 和 “匹配花色”,二者采用的方法相同,但后者难度较高,因为花色比数字更难精确识别。
识别花色如下: 其中”桃9”和”方Q”的花色没有识别出来,显示“Unknown”, “心3”花色正常识别出来了,显示为 ‘Xin’。
经过再次拍照训练,花色也可以识别出来了。但在每秒50次的识别频率里,还是会偶尔出现几次识别失败。这个问题可以用程序逻辑去规避。(因为实际情况1秒内不会更换几次牌)
到此,扑克牌图像识别技术的主要技术点基本完成。数字、花色、坐标如下。
此Demo核心功能基本完成,还有下面技术细节已经完成了,可提高识别率:
l 程序自动训练自动学习。
l 限定轮廓的宽高比例,可以排除墙壁等物的干扰
l 限定轮廓像素大小,可排除纸片等异物的干扰。
l 计算出相似度来比较,比像素差异的比较更容易理解,从而更准确。
存在的问题:
l 不能让扑克有反光,反光会严重影响识别率。要注意摄像机的位置和灯光位置。
l 手臂会暂时挡住牌面,导致暂时识别失败。
l 视频识别会受到光线强弱的影响,导致偶尔识别失败。(程序已修正)。
剩下来就是业务逻辑的实现,例如百家乐,定义好固定的6张牌的位置坐标,只扫描在这6张牌坐标范围内的扑克,然后将扫描到的扑克信息发送给服务器。
OpenCV参考资料
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_tutorials.html
https://docs.opencv.org/3.1.0/d7/dbd/group__imgproc.html
https://www.kancloud.cn/aollo/aolloopencv/262768
https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10
https://github.com/EdjeElectronics/OpenCV-Playing-Card-Detector