引子

在现场下军棋时需要三个人,其中一个人当裁判。如果只有两个人,又想玩军棋,就需要有一个自动裁判机制。想通过图像识别技术自动识别棋子上的文字,从而实现棋子的自动裁判功能。

在前一篇文章《opencv-python实际演练(一)图像识别(1)目标区域提取》对模拟的双方棋子图像进行了目标区域的提取。但是真实的图像与模拟的图像终归有一些差距。接下来要对真实的场景做处理。

棋子图像采集设备的成品图


以上图片是为了看清楚军棋棋子,故意将棋子正面向上放置,实际使用时,棋子应该正面向下放置,游戏的双方都不能看见对方的棋子。两颗棋子正面向下,朝向USB摄像头,棋子图像被摄像头捕获后通过USB连线发送到电脑,再做进一步的处理。

设备实际使用效果如下

设备拆解后的示意图如下:

实际捕获的图像效果

将usb插头插入电脑,由于是免驱的摄像头,直接用opencv-python显示摄像头即可,效果如下:

此时还未放置棋子,现在将棋子放上去,效果如下:

捕获摄像头的python代码如下:

import cv2
import timecamera = cv2.VideoCapture(0)while True:success, frame = camera.read()cv2.imshow('MyCamera',frame)if cv2.waitKey(1) &  0xff == ord('q'):break    time.sleep(0.05)camera.release()

运行后在cv2图像窗口获得焦点时,按下键盘上的"Q"值,程序退出。

棋子图像采集设备的DIY过程

原材料的准备

1 家用usb摄像头,分辨率640*480, 在网上购买成本20元左右
2 中号矿泉水瓶一个
3 泡沫塑料少许
4 纸板或塑料板,最好是黑色的,不要太厚,否则不好裁剪,也不要太薄,否则强度不够

制作过程

1 将中号矿泉水瓶的上部剪掉,只留下面的部分,至于到底要留多长,取决于你选的摄像头最短可以拍摄的距离,我的做法是先尽量多留一些,将摄像头固定在瓶底后,连上电脑,用手拿着棋子,在电脑上观察棋子上的文字,可以最清楚显示时,记下摄像头与棋子间的距离,再将多余的部分剪掉。另外矿泉水瓶不值几个钱,做坏了重做一个就是。

大部分摄像头的镜头都可以旋转调焦,在测试时也可以转一转,调至最清晰的状态后,瓶子要保留的高度也就确定下来了。

说明一下,为什么要选择圆形的透明的水瓶呢?并非是我先知先觉,其实是用其它的材料做废了好几个,最后才选的中号矿泉水瓶。

比如纸板盒子太难看,而且不透光;小号矿泉水瓶放不稳;用木板或亚克力板加工动静太大;

外壳透明才能使用外部照明,如果放在黑盒子里,用摄像头自带的LED照明,会产生反光,高光,得到的图像明暗不均,非常非常影响边缘检查的效果。

我今天差不多花了一个下午,想在用摄像头LED照明的情况下,消除图像明暗不均的问题,加上灯罩,遮光板等等,但最后还是放弃了。用透明的外壳,引入外部光源,效果是最好的。有想尝试用摄像头LED照明的亲们,如果鼓捣出了好的结果,一定要告诉我一声。

圆柱状的瓶身加上圆形的盖板,盖板可以旋转任意的角度,这样一来就可以调整棋子与摄像头的夹角,这一点对最终的成像有好处,方型盒子调节起来就没有这么方便。

2 在矿泉水瓶底部开一个小孔,刚好可供USB插头穿过,将厚泡沫塑料片切成圆形,与水瓶半径一样或稍大一点。中间掏空,掏空的形状比照摄像头的外部轮廓。我用的摄像头在折除掉原装的夹子后是一个长方体,所以中间掏空部分是矩形,如果摄像头是圆柱状的,相应变化就是了。

我用的泡沫塑料厚度是2cm,一层还不够,用了两层,将摄像头牢牢固定在底部。

3 矿泉水瓶上边一圈,每隔两厘米剪一道0.5cm深的小口,间隔着将一半的塑料片向内掰弯,方便安装顶部盖板,将塑料片的尖角剪成圆形,以免扎手,最终效果如下:

4 顶部双孔盖板的制作是最麻烦的部分,一下午用纸板,薄塑料板,透明塑料板等反复试制,做坏了好几个,最后做出的成品也不是特别满意,勉强能用,主要是加工精度不高,边缘不够平直,有毛刺,不过好在有软件可以进行后续处理,不用要求精度特别高。但如果加工精度高一些,识别效果会更好。

圆形盖板的外边缘精度要求不高,对图像采集没有太大的影响,主要是双孔的的精度要求较高,加工时要仔细一些,我是用剪刀+美工刀,加工效果很一般,如果有更专业的加工设备来做的话,效果肯定会好很多,不过成本也会提高很多,只要不影响后期的图像处理就可以了。

这次反复试制,有几点经验
(1) 盖板朝向摄像头的表面要用黑色,遮光,与棋子部分的明暗对比越强烈越好

(2) 双孔要有一点距离,不要挨在一起并合成一个孔,如果合成了一个孔,后面就要通过软件的办法将两个棋子的图像分开。
用双孔而不是一个大孔,也方便玩家放置棋子

盖板上的开孔要求刚好可以放进一个棋子,而又不会掉下去,我一开始的做法是在盖板下面贴一层透明的塑料膜,由于会产生反光,放弃了,现在的加工方法是
(1) 剪出一个圆板,大小刚好可以放在水瓶上
(2) 圆板中部并列开两个孔,大小刚好让棋子穿过
(3) 同样的圆板再做一个,与上一个的区别在于方形孔的四个角上都留了一个小角,孔的形状变成了这样:

(4) 把两个双孔圆板粘在一起,棋子就可以放进去,但是不会掉下去,也不影响透光

5 双孔盖板做好后,扣在水瓶的上面,旋转盖板,调节到最合适的角度

软件参数的调整

原来对手机拍摄的模拟图片进行处理时,由于分辨率较高,图像做了缩小,现在不必要了。其它的一些参数也要做一些小的调整
其中最主要的是对图像做二值化处理的阈值,根据实际情况调整到180。

调整后的python代码如下

#coding:utf-8
#将这两个棋子的内容从图片中提取出来,供下一步的文本识别使用import cv2
import numpy as np
import math#配置数据
class Config:def __init__(self):pass#src = "photo1.jpg"src = "camera/piece16.png"resizeRate = 0.5min_area = 5000min_contours = 8threshold_thresh = 180epsilon_start = 20epsilon_step = 40'''
对坐标点进行排序
@return     [top-left, top-right, bottom-right, bottom-left]
'''
def order_points(pts):# initialzie a list of coordinates that will be ordered# such that the first entry in the list is the top-left,# the second entry is the top-right, the third is the# bottom-right, and the fourth is the bottom-leftrect = np.zeros((4, 2), dtype="float32")# the top-left point will have the smallest sum, whereas# the bottom-right point will have the largest sums = pts.sum(axis=1)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# now, compute the difference between the points, the# top-right point will have the smallest difference,# whereas the bottom-left will have the largest differencediff = np.diff(pts, axis=1)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]# return the ordered coordinatesreturn rect# 求两点间的距离
def point_distance(a,b):return int(np.sqrt(np.sum(np.square(a - b))))# 找出外接四边形, c是轮廓的坐标数组
def boundingBox(idx,c):if len(c) < Config.min_contours: return Noneepsilon = Config.epsilon_startwhile True:approxBox = cv2.approxPolyDP(c,epsilon,True)#求出拟合得到的多边形的面积theArea = math.fabs(cv2.contourArea(approxBox))#输出拟合信息print("contour idx: %d ,contour_len: %d ,epsilon: %d ,approx_len: %d ,approx_area: %s"%(idx,len(c),epsilon,len(approxBox),theArea))if (len(approxBox) < 4):return Noneif theArea > Config.min_area:if (len(approxBox) > 4):# epsilon 增长一个步长值epsilon += Config.epsilon_step               continueelse: #approx的长度为4,表明已经拟合成矩形了                #转换成4*2的数组approxBox = approxBox.reshape((4, 2))                return approxBox                else:print("failed to find boundingBox,idx = %d area=%f"%(idx, theArea))return None#提取目标区域
def pickOut():# 开始图像处理,读取图片文件image = cv2.imread(Config.src)#print(image.shape)#获取原始图像的大小srcHeight,srcWidth ,channels = image.shape#对原始图像进行缩放#image= cv2.resize(image,(int(srcWidth*Config.resizeRate),int(srcHeight*Config.resizeRate))) #cv2.imshow("image", image)#转成灰度图gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) cv2.imshow("gray", gray)# 中值滤波平滑,消除噪声# 当图片缩小后,中值滤波的孔径也要相应的缩小,否则会将有效的轮廓擦除binary = cv2.medianBlur(gray,7)#binary = cv2.medianBlur(gray,3)  #转换为二值图像ret, binary = cv2.threshold(binary, Config.threshold_thresh, 255, cv2.THRESH_BINARY)#显示转换后的二值图像cv2.imshow("binary", binary)# 进行2次腐蚀操作(erosion)# 腐蚀操作将会腐蚀图像中白色像素,可以将断开的线段连接起来binary = cv2.erode (binary, None, iterations = 2)#显示腐蚀后的图像cv2.imshow("erode", binary)# canny 边缘检测binary = cv2.Canny(binary, 0, 60, apertureSize = 3)#显示边缘检测的结果cv2.imshow("Canny", binary)# 提取轮廓contours,_ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 输出轮廓数目print("the count of contours is  %d \n"%(len(contours)))#针对每个轮廓,拟合外接四边形,如果成功,则将该区域切割出来,作透视变换,并保存为图片文件for idx,c in enumerate(contours):approxBox = boundingBox(idx,c)if approxBox is None: print("\n")continue# 获取最小矩形包络rect = cv2.minAreaRect(approxBox)box = cv2.boxPoints(rect)box = np.int0(box)box = box.reshape(4,2)box = order_points(box)print("boundingBox:\n",box)   # 待切割区域的原始位置,# approxPolygon 点重排序, [top-left, top-right, bottom-right, bottom-left]src_rect = order_points(approxBox)  print("src_rect:\n",src_rect)w,h = point_distance(box[0],box[1]), point_distance(box[1],box[2])print("w = %d ,h= %d "%(w,h))# 生成透视变换矩阵dst_rect = np.array([[0, 0],[w - 1, 0],[w - 1, h - 1],[0, h - 1]],dtype="float32")# 透视变换M = cv2.getPerspectiveTransform(src_rect, dst_rect)#得到透视变换后的图像warped = cv2.warpPerspective(image, M, (w, h))#将变换后的结果图像写入png文件cv2.imwrite("output/piece%d.png"%idx, warped, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])print("\n")print('over')
#-----------------------------------------------------------------------------------------------pickOut()
cv2.waitKey(0)

将几个重要部骤的图像显示出来,双屏显示的效果如下,看着还挺有意思的

每个步骤的具体图片如下:
灰度图如下:

二值化后的图像如下

腐蚀后的图像如下:

边缘检测的结果如下:

最终得到的两个提取后的图片:

opencv-python实际演练(二)军棋自动裁判(1)棋子图像采集设备DIY相关推荐

  1. opencv-python实际演练(二)军棋自动裁判(2)棋子图像采集效果分析

    引子 在前一篇文章<opencv-python实际演练(二)军棋自动裁判(1)棋子图像采集设备DIY>介绍了棋子图像采集设备的制作过程,在取得棋子图像后发现提取目标区域的效果还不够好,有时 ...

  2. opencv-python实际演练(二)军棋自动裁判(3)棋子图像采集设备的改进

    引子 在文章<opencv-python实际演练(二)军棋自动裁判(1)棋子图像采集设备DIY>介绍了棋子图像采集设备的制作过程. 在文章<opencv-python实际演练(二)军 ...

  3. 关于军棋自动裁判机制的设想

    一般来说,下军棋需要三个人,其中一个人当裁判. 如果只有两个人,又想玩军棋,就需要有一个自动裁判机制. 想到了几种自动裁判的方案 1 图像识别,用手机自动识别棋子上的文字,缺点是拍照,识别耗时较长 补 ...

  4. OpenCV+python:图像二值化

    1,图像二值化概念及方法 一个像素点的颜色是由RGB三个值来表现的,所以一个像素点矩阵对应三个颜色向量矩阵,分别是R矩阵,G矩阵,B矩阵,它们也都是同样大小的矩阵. 在图像处理中,用RGB三个分量(R ...

  5. Python开发的四国军棋AI智能裁判

    目录 1,背景和软件的用途 2,环境介绍 2.1 硬件环境 2.2 软件环境 2.3 素材准备 3,软件功能介绍 4,代码 5,源程序打包下载 1,背景和软件的用途 在玩四国军棋时,除了要四个玩家之外 ...

  6. python做直方图-python OpenCV学习笔记实现二维直方图

    本文介绍了python OpenCV学习笔记实现二维直方图,分享给大家,具体如下: 官方文档 – https://docs.opencv.org/3.4.0/dd/d0d/tutorial_py_2d ...

  7. 用python怎么样实现图像二值化_使用Python+OpenCV如何实现图像二值化

    使用Python+OpenCV如何实现图像二值化 发布时间:2020-10-26 14:15:52 来源:亿速云 阅读:77 作者:蛋片鸡 这篇文章运用简单易懂的例子给大家介绍使用Python+Ope ...

  8. 【基础】python操作Word时,自动更新目录(二)

    系列文 python-docx-template包之----为文字自定义格式(一) python-docx-template包之----设置表格(二) python-docx-template包之-- ...

  9. 《OpenCv视觉之眼》Python图像处理十二 :Opencv图像轮廓提取之基于一阶导数的Roberts算法、Prewitt算法及Sobel算法

    本专栏主要介绍如果通过OpenCv-Python进行图像处理,通过原理理解OpenCv-Python的函数处理原型,在具体情况中,针对不同的图像进行不同等级的.不同方法的处理,以达到对图像进行去噪.锐 ...

  10. OpenCV python(二)图像预处理:改变图像大小 提取感兴趣区域

    OpenCV python(二)图像预处理:改变图像大小 && 提取感兴趣区域 一.改变图像大小 1.获取图像宽.高.通道数 2.resize函数 3.案例 二.ROI感兴趣区域 1. ...

最新文章

  1. 利用链式存储结构实现线性表
  2. 转]网络上收集的Visual Studio 2008的一些小技巧
  3. YY一下,扎克伯格做了一个什么样的AI家居助手?
  4. ShopEx customSchema 定制可以根据客户的需求对网站进行相应功能的添加修改或者删除
  5. 阿里P8大佬亲自教你!Android内存泄漏总结,看看这篇文章吧!
  6. 基于智能卡的嵌入式网络加密安全系统设计
  7. python解析sql语句表名_python正则表达式匹配sql语句中的表名
  8. 加载脚本依赖发生错误--暴力猴
  9. 江诗丹顿 VACHERON CONSTANTIN
  10. png 微软ppt 透明度_完美PNG半透明窗体解决方案
  11. Microbiome | 东北农大石宝明/孟庆维等揭示宿主-微生物互作介导猪肠炎免疫
  12. 网易云音乐api,登录出现 网络太拥挤
  13. BeatSaber节奏光剑插件开发官方教程2-简单的插件示例
  14. Android仿斗鱼滑动登录验证
  15. 电机控制方案用哪家芯片比较好?
  16. 信息学奥赛一本通1356:计算(calc) (栈)
  17. qq邮箱服务器接收和发送文件夹,将QQ邮箱打造成为你的邮箱总管-qq邮箱怎么发送文件夹...
  18. 企业为何需要建立统一的复用型软件平台?
  19. 王德学:关停小煤矿难度超乎想象
  20. [CTF]SCTF2021 WEB复现(详细版)

热门文章

  1. 【Unity3D】枪战游戏—弹孔设置
  2. 数学建模学习(41):单因素方差分析
  3. 多目标进化算法详细讲解及代码实现(样例:MOEA/D、NSGA-Ⅱ求解多目标(柔性)作业车间调度问题)
  4. 极客算法训练笔记(七),十大经典排序之归并排序,全网最详
  5. Vue PC端和移动端的切换
  6. DWT(离散小波变换)与其简单应用
  7. 恒虚警率(Constant False Alarm Rate)检测
  8. bash, sh, dash 傻傻分不清楚
  9. libvirt live migration 流程
  10. Linux操作系统中常见的英文报错