opencv-python实际演练(二)军棋自动裁判(4)棋子图像提取算法的改进

本文介绍了一个军棋自动裁判系统的图像处理方法,重点解决了棋子边缘文字导致的图像提取难题。通过改进轮廓提取算法,利用外接矩形和轮廓面积判断,成功提升了棋子图像的准确率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引子

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

在文章《opencv-python实际演练(二)军棋自动裁判(3)棋子图像采集设备的改进》对图像采集设备进行了改进。

在实验中发现了一种情形,就是有些棋子上的文字比较靠近边缘,按照以前的算法不能有效地提取目标区域

问题描述

当棋子上的文字比较靠近边缘,采集到的原始图像如下:
在这里插入图片描述
边缘检测的结果如下:
在这里插入图片描述
从上图可以看出棋子“连长”的轮廓的下边已经向内凹陷,这会导致轮廓的面积变小,在以前的算法中就不能正常的提取这个棋子的图像。改进思路

1 从硬件入手,保证棋子上的文字不要靠近边缘,但如果某些棋子在生产过程中有了偏差,要在采集设备上着手,比较难办

2 从软件着手,整个轮廓还是比较清楚的,只是边上向内凹陷或断裂。这时可以采用求出这个轮廓的外接矩形,并结合面积大小进行判断。

改进部分如下:

 else:
        	#尝试计算外接矩形,当棋子上的笔画确到了外边缘,会造成外轮廓不再是矩形,面积缩小,这时尝试用外接矩形来包住这种外轮廓
            print("try boundingRect")             
            x, y, w, h = cv2.boundingRect(c)
            if w*h > Config.min_area:                
                approxBox = [[x,y],[x+w,y],[x+w,y+h],[x,y+h]]                
                approxBox = np.int0(approxBox)                
                return approxBox 
            else:
                print("It is too small ,need not to find boundingBox,idx = %d area=%f"%(idx, theArea))
                return None

改进后的提取效果如下:

在这里插入图片描述

完整的python代码

#coding:utf-8
#将两个棋子的内容从棋子图像采集器中提取出来,供下一步的文本识别使用

import cv2
import numpy as np
import math

#配置数据
class Config:
    def __init__(self):
        pass
    #src = "photo1.jpg"
    src = "camera/piece1.png"
    resizeRate = 0.5
    min_area = 30000
    min_contours = 8
    threshold_thresh = 180
    epsilon_start = 10
    epsilon_step = 5

'''
对坐标点进行排序
@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-left
    rect = np.zeros((4, 2), dtype="float32")

    # the top-left point will have the smallest sum, whereas
    # the bottom-right point will have the largest sum
    s = 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 difference
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]

    # return the ordered coordinates
    return rect

# 求两点间的距离
def point_distance(a,b):
    return int(np.sqrt(np.sum(np.square(a - b))))

# 找出外接四边形, c是轮廓的坐标数组
def boundingBox(idx,c,image):
    if len(c) < Config.min_contours:
        print("the contours length is  less than %d ,need not to find boundingBox,idx = %d "%(Config.min_contours,idx)) 
        return None
    epsilon = Config.epsilon_start
    while True:
        approxBox = cv2.approxPolyDP(c,epsilon,True)

        #显示拟合的多边形
        #cv2.polylines(image, [approxBox], True, (0, 255, 0), 2)
        #cv2.imshow("image", image)        
        
        if (len(approxBox) < 4):
            print("the approxBox edge count %d is  less than 4 ,need not to find boundingBox,idx = %d "%(len(approxBox),idx)) 
            return None
        #求出拟合得到的多边形的面积
        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 theArea > Config.min_area:
            if (len(approxBox) > 4):
                # epsilon 增长一个步长值
                epsilon += Config.epsilon_step               
                continue
            else: #approx的长度为4,表明已经拟合成矩形了                
                #转换成4*2的数组
                approxBox = approxBox.reshape((4, 2))                            
                return approxBox                
        else:
        	#尝试计算外接矩形,当棋子上的笔画确到了外边缘,会造成外轮廓不再是矩形,面积缩小,这时尝试用外接矩形来包住这种外轮廓
            print("try boundingRect")             
            x, y, w, h = cv2.boundingRect(c)
            if w*h > Config.min_area:                
                approxBox = [[x,y],[x+w,y],[x+w,y+h],[x,y+h]]                
                approxBox = np.int0(approxBox)                
                return approxBox 
            else:
                print("It is too small ,need not 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)))
    
    #显示轮廓
    #cv2.drawContours(image,contours,-1,(0,0,255),1)
    #cv2.imshow("image", image)


    #针对每个轮廓,拟合外接四边形,如果成功,则将该区域切割出来,作透视变换,并保存为图片文件
    for idx,c in enumerate(contours):
        approxBox = boundingBox(idx,c,image)
        if approxBox is None: 
            print("\n")
            continue

        #显示拟合结果
        #cv2.polylines(image, [approxBox], True, (0, 0, 255), 2)
        #cv2.imshow("image", image)

        # 待切割区域的原始位置,
        # approxPolygon 点重排序, [top-left, top-right, bottom-right, bottom-left]
        src_rect = order_points(approxBox)  
        print("src_rect:\n",src_rect)        

         # 获取最小矩形包络
        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)         
       
        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))
        warped = cv2.warpPerspective(gray, M, (w, h))

        #将变换后的结果图像写入png文件
        cv2.imwrite("output/piece%d.png"%idx, warped, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])

        print("\n")

                    
    print('over')
#-----------------------------------------------------------------------------------------------

#准备捕捉摄像头内容
camera = cv2.VideoCapture(0)

print('press s to pickout,q to quit')

picIdx = 0
while True:
    success, frame = camera.read()
    cv2.imshow('MyCamera',frame)
    userInput = cv2.waitKey(10) & 0xff
    if userInput == ord('q'):
            break
    if userInput == ord('s'):
            print('save pic')
            Config.src = "camera/pic%d.png"%picIdx
            cv2.imwrite(Config.src, frame, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
            pickOut()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值