python opencv读取图像并生成plt文件

1 前言

在数控系统中,plt文件是标准的数控加工文件格式。一般可由signMast、文泰等工控软件生成plt加工文件。现在假设电脑或手机上没有工控软件,只有一些描述加工路径的图片,此时可以用opencv提取轮廓来生成加工路径,并将路径保存成plt文件。使用python版的opencv库可以快速搞定这个功能。

2 轮廓的提取

轮廓的提取

先用网上搜到的提取轮廓最简单的几步:

import cv2
import numpy
import os

def show_img(window,img):
    cv2.namedWindow(window,0)
    cv2.resizeWindow(window,480,640)
    cv2.imshow(window,img)

img = cv2.imread("D:/iphone.png")
img = cv2.bitwise_not(img)
#转为灰度图
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#图像二值化
ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY| cv2.THRESH_OTSU)
#得到轮廓
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_TC89_L1)

draw_img_1 = numpy.ones(img.shape,dtype=numpy.uint8) 
cv2.drawContours(draw_img_1,contours,-1,(0,0,255),1)
show_img('contours',draw_img_1)

cv2.waitKey(0)

原图片 iphone.png:

 

提取到的轮廓图 draw_img_1:

仔细看可以发现这里为每个图形对象找到了两个轮廓。由于图片中的线条有宽度,所以Opencv会为每个图形对象找到了2个轮廓,分别是线条外边构成的外轮廓和线条另外一边构成的内轮廓。现在的任务就是怎么去掉另外的重复轮廓。

去除重复轮廓

研究cv2.findContours函数:

contours,hierarchy = cv2.findContours(image,mode,method)
输入:
image:带有轮廓信息的图像;

mode:提取轮廓后,输出轮廓信息的组织形式,可以取以下值:
    cv2.RETR_EXTERNAL:输出轮廓中只有外侧轮廓信息;
    cv2.RETR_LIST:以列表形式输出轮廓信息,各轮廓之间无等级关系;
    cv2.RETR_CCOMP:输出两层轮廓信息,即内外两个边界(下面将会说到contours的数据结构);
    cv2.RETR_TREE:以树形结构输出轮廓信息。

method:指定轮廓的近似办法,有以下选项
    cv2.CHAIN_APPROX_SIMPLE:指定轮廓的近似办法,有以下选项:
    cv2.CHAIN_APPROX_NONE:存储轮廓所有点的信息,相邻两个轮廓点在图象上也是相邻的;
    cv2.CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标;
    cv2.CHAIN_APPROX_TC89_L1:使用teh-Chinl chain 近似算法保存轮廓信息。

输出:
python3里返回2个值:contours,hierarchy

contours:list结构,列表中每个元素代表一个边沿信息。每个元素是(x,1,2)的三维向量,x表示该条边沿里共有多少个像素点,第三维的那个“2”表示每个点的横、纵坐标;

hierarchy:返回类型是(x,4)的二维ndarray。x和contours里的x是一样的意思。如果输入选择cv2.RETR_TREE,则以树形结构组织输出,hierarchy的四列分别对应同级下一个轮廓编号、同级上一个轮廓编号、子轮廓编号、父轮廓编号,该值为负数表示没有对应项。

重点需要关注返回的hierarchy参数。它描述了轮廓的层次信息。当mode采用cv2.RETR_CCOMP参数后,findContours只会取找到两层轮廓。对于ihone.png图片,hierarchy仿真控制台打印结果是:

这里共有8个轮廓。轮廓1的父轮廓是轮廓0;轮廓3的父轮廓是轮廓2;轮廓5的父轮廓是轮廓4;轮廓7的父轮廓是轮廓0。轮廓0,2,4,6都没有父轮廓,意味着他们是最顶级轮廓。由此可以看出这里轮廓0,2,4,6是要提取的轮廓,轮廓1,3,5,7分别是多出来的重复内层轮廓。因此程序中是否可以采用hierarchy数组元素的第4个子元素hierarchy[i][3]为-1的条件来判断,hierarchy[i][3]为-1就是顶层轮廓,只要hierarchy[i][3]不为-1就是内层轮廓就可以去掉该轮廓了吗?但是还需要考虑另外一种情况,看这张abcd.png图片:

提取出来的hierarchy打印如下:

这里字母ABCD共提取了8条轮廓,字母A由2条轮廓,字母B有3条轮廓,字母C由1条轮廓,字母D由2条轮廓。若是采用刚才的判断条件,就只剩下4条轮廓了,这明显不对。在这里的内层轮廓也需要保留。这是因为iphone图片的内层轮廓是由线条宽度引入进来的,和外层轮廓相似度非常高,这张字母图片的内层轮廓是字母固有的内层轮廓,和外层轮廓的差别比较大。可以用轮廓长度的相似度来区分这俩种情况。下面代码当子轮廓和父轮廓轮廓长度比值小于0.8时就认为是不同轮廓。

points_arr = []
for i in range(len(hierarchy[0])-1,-1,-1):
    item = hierarchy[0][i]
    if item[3] == -1:
        cv2.drawContours(draw_img_1,contours,i,(0,0,255),1)
        points_arr.append(contours[i])
    else:
        father_id = item[3]
        l1 = cv2.arcLength(contours[father_id],True)
        l2 = cv2.arcLength(contours[i],True)
        if l2/l1<0.8:
            cv2.drawContours(draw_img_1,contours,i,(0,0,255),1)
            points_arr.append(contours[i])

绘制加工轨迹

提取出所有轮廓以后,就可以生成加工路径了。每条轮廓都是由加工点构成的闭合路径。为了减小轮廓点数,所以在findContours函数中未采用cv2.CHAIN_APPROX_NONE参数,而是采用cv2.CHAIN_APPROX_TC89_L1近似算法来保留关键拐点。对于同一条轮廓,从第1个点进入以后走完该轮廓所有点,还需要回到最初点才构成闭合路径。当走完第1条轮廓路径以后,接着走第2条轮廓路径。

#绘制线条加工轨迹到draw_img_2中
draw_img_2 = numpy.zeros(img.shape,dtype=numpy.uint8) 
for i in range(len(points_arr)):
    points = points_arr[i]
    old_point = points[0]
    for point in points:
        cv2.line(draw_img_2,(old_point[0][0],old_point[0][1]),(point[0][0],point[0][1]),(0,255,0),1)
        old_point = point
    cv2.line(draw_img_2,(old_point[0][0],old_point[0][1]),(points[0][0][0],points[0][0][1]),(0,255,0),1)
    if i<len(points_arr)-1:
        cv2.line(draw_img_2,(points_arr[i][0][0][0],points_arr[i][0][0][1]),(points_arr[i+1][0][0][0],points_arr[i+1][0][0][1]),(255,0,0),1)
show_img('animation',draw_img_2)
cv2.imwrite('contours2.png',draw_img_2)

生成plt文件

生成plt文件就是跟绘制加工轨迹相似的方法。就是注意下刀和台刀。走刀第每条路径第1个点时必然是抬刀,使用PU;在路径中走动是下刀,使用PD。走完所有路径以后,需要回到(0,max_y)点,这里的max_y是所有加工路径中纵坐标最大值。此外函数中设置了一个比值ratio,代表1像素对应多少毫米。将像素换成毫米。在plt文件中1毫米代表40。这是程序中又乘以了40的由来。

def writePoint(f,point,height,flag,ratio):
    if(flag == 1):
        f.write('PD%d,%d;' % (int((point[0][1])*ratio*40),int(point[0][0]*ratio*40)))
    else:
        f.write('PU%d,%d;' % (int((point[0][1])*ratio*40),int(point[0][0]*ratio*40)))

#ratio:,像素到毫米的换算比例,1像素=ratio毫米
def createPltFile(arr,height,ratio):
    f = open('text.plt','w+')
    f.write('IN;SP1;')
    max_y = 0
    writePoint(f,points_arr[0][0],height,0,ratio)
    max_y = max(points_arr[0][0][0][1],max_y)
    for i in range(len(points_arr)):
        points = points_arr[i]
        first_point = points[0]
        for point in points:
            writePoint(f,point,height,1,ratio)
            max_y = max(point[0][1],max_y)
        writePoint(f,first_point,height,1,ratio)
        max_y = max(first_point[0][1],max_y)
        if i<len(points_arr)-1:
            writePoint(f,points_arr[i+1][0],height,0,ratio)
            max_y = max(points_arr[i+1][0][0][1],max_y)
    writePoint(f,[[0,max_y]],height,0,ratio)
    f.write('PG;@@@@@@@@@@@@@@@@@@@;')

完整程序:

import cv2
import numpy
import os
def show_img(window,img):
    cv2.namedWindow(window,0)
    cv2.resizeWindow(window,480,640)
    cv2.imshow(window,img)

def writePoint(f,point,height,flag,ratio):
    if(flag == 1):
        f.write('PD%d,%d;' % (int((point[0][1])*ratio*40),int(point[0][0]*ratio*40)))
    else:
        f.write('PU%d,%d;' % (int((point[0][1])*ratio*40),int(point[0][0]*ratio*40)))
#ratio:,像素到毫米的换算比例,1像素=ratio毫米
def createPltFile(arr,height,ratio):
    f = open('text.plt','w+')
    f.write('IN;SP1;')
    max_y = 0
    writePoint(f,points_arr[0][0],height,0,ratio)
    max_y = max(points_arr[0][0][0][1],max_y)
    for i in range(len(points_arr)):
        points = points_arr[i]
        first_point = points[0]
        for point in points:
            writePoint(f,point,height,1,ratio)
            max_y = max(point[0][1],max_y)
        writePoint(f,first_point,height,1,ratio)
        max_y = max(first_point[0][1],max_y)
        if i<len(points_arr)-1:
            writePoint(f,points_arr[i+1][0],height,0,ratio)
            max_y = max(points_arr[i+1][0][0][1],max_y)
    writePoint(f,[[0,max_y]],height,0,ratio)
    f.write('PG;@@@@@@@@@@@@@@@@@@@;')
    
img = cv2.imread("D:/iphone.png")
img = cv2.bitwise_not(img)
#转为灰度图
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

kernel = numpy.ones((3, 3), numpy.uint8)
img_dilate = cv2.dilate(gray, kernel,iterations = 3)
img_dilate = cv2.erode(gray, kernel,iterations = 3)

#gray = cv2.Canny(gray, 100, 300)
#图像二值化
ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY| cv2.THRESH_OTSU)
#得到轮廓
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_TC89_L1)
draw_img_1 = numpy.ones(img.shape,dtype=numpy.uint8) 
index = 0
points_arr = []
for i in range(len(hierarchy[0])-1,-1,-1):
    item = hierarchy[0][i]
    if item[3] == -1:
        cv2.drawContours(draw_img_1,contours,i,(0,0,255),3)
        points_arr.append(contours[i])
    else:
        father_id = item[3]
        l1 = cv2.arcLength(contours[father_id],True)
        l2 = cv2.arcLength(contours[i],True)
        if l2/l1<0.8:
            cv2.drawContours(draw_img_1,contours,i,(0,0,255),3)
            points_arr.append(contours[i])

#cv2.drawContours(draw_img_1,contours,-1,(0,0,255),1)

show_img('input',img)
show_img('gray',gray)
show_img('thresh',thresh)
show_img('contours',draw_img_1)
cv2.imwrite('contours.png',draw_img_1)

#绘制线条加工轨迹到draw_img_2中
draw_img_2 = numpy.zeros(img.shape,dtype=numpy.uint8) 
for i in range(len(points_arr)):
    points = points_arr[i]
    old_point = points[0]
    for point in points:
        cv2.line(draw_img_2,(old_point[0][0],old_point[0][1]),(point[0][0],point[0][1]),(0,255,0),3)
        old_point = point
    cv2.line(draw_img_2,(old_point[0][0],old_point[0][1]),(points[0][0][0],points[0][0][1]),(0,255,0),3)
    if i<len(points_arr)-1:
        cv2.line(draw_img_2,(points_arr[i][0][0][0],points_arr[i][0][0][1]),(points_arr[i+1][0][0][0],points_arr[i+1][0][0][1]),(255,0,0),3)
show_img('animation',draw_img_2)
cv2.imwrite('contours2.png',draw_img_2)
createPltFile(points_arr,img.shape[0],1.0/10)

cv2.waitKey(0)

当读入iphone.png时执行结果:

当读入abcd.png时执行结果:

 

 

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值