zoukankan      html  css  js  c++  java
  • MTP 写字机器

    目标

    无意中看到下面视频,我打算也实现一个类似机器
    视频视频2视频3
    来源于油管Creativity Buzz的创意,顺便了解到有家AxiDraw公司在生产这种机器,淘宝上也有售卖。

    想法

    观看视频可以发现,这个机器的原理类似于数控机床,笔尖根据预先设定好的程序、方案运动,绘制出图案。关键是如何根据图案或字符得到三个步进电机的运动控制信号。为了快速实现一个简易系统,先不考虑如何根据字符得到三个步进电机的控制信号,只考虑如何根据图案得到三个步进电机的控制信号。而且此机器不能中途换笔,故画笔只有一种颜色。

    硬件

    因为涉及图像处理,我选择树莓派作为控制板。上位机+stm32也是一种可行的好方案。

    机械结构

    参考视频的机械结构,购买了以下配件,自行淘宝3D打印机配件
    丝杆、光杆套装各两套
    A4988步进电机驱动板2块点我看使用方法
    42步进电机2个

    效果图2019.01.20

    我对木工不太熟,这构造和预期有点出入。

    软件规划

    由以上分析知系统的输入为图片,输出为三个步进电机的运动控制信号。采用模块化分析方法,应构造以下模块

    为便于分析,长度单位都为mm。设机器笔尖最大绘制范围为xy的矩阵,矩阵原点在机器极限位置左边距离n,上边距离m处。称矩阵原点为绘制起点。设笔尖宽度为q,则矩阵中有x/qy/q个像素点。

    图像处理模块

    输入:图片
    输出:尺寸为x/q*y/q的灰度矩阵
    功能:

    1. 灰度化图片
    2. 确定图片右下角与绘制起点的相对位置
    3. 根据设定要求放缩图片,使图片整体位于绘制范围内部
    4. 输出尺寸为x/q*y/q的灰度矩阵

    构造控制信号模块

    方案一
    输入:尺寸为x/q*y/q的灰度矩阵
    输出:描述笔尖运动的矩阵
    功能:绘制图案时,机器将从绘制起点(0,0)开始绘制图案,先向y轴运动绘制x=0处的图案,然后向快速到(1,0)处向y轴运动绘制x=1处的图案。(注意回程误差)这样就把二维图像变成了一维图像。对于每一行,值大于0的数字连成几条线段,记录线段的起点、终点。构造一个如下所示的矩阵,每一行代表一条线段,左右分别是起点终点。

    #作废
    [
    [(l1,l2),(l3,l4)],
    [(l5,l6),(l7,l8)],
    ...
    ]
    

    考虑到以后扩展的需要,决定记录画线类型、画线所需信息。目前只实现画直线

    #n*5
    [
    [1 23 43 23 46] #从(23,43)画直线到(23,46)
    ]
    

    方案二
    输入:尺寸为x/q*y/q的矩阵
    输出:NC程序代码
    暂略

    仿真模块

    为了检查以上步骤软件是否写正确,简单编写了一个仿真模块
    输入:描述笔尖运动的矩阵
    输出:绘制的图案
    效果如下,右边是原图,左边是根据描述笔尖运动的矩阵绘制的图案,所以从原理上来说这个机器是可能实现的。因为步长设置比较大,看起来失真有点严重

    电机控制模块

    输入:描述笔尖运动的矩阵
    输出:三个控制步进电机的脉冲信号
    功能:读取一组数据,运动到起点位置,下笔,运动到终点,提笔。
    读取下一组数据,循环往复直至读完。
    最后笔尖回到绘制起点。

    代码

    最新版本代码托管在Github

    测试代码

    #coding:utf-8
    from IMPlib import IMP
    from SGNlib import SGN
    from SIMlib import SIM
    #from CTRlib import CTR
    import cv2
    import numpy as np
    
    #图像处理后得到的灰度图
    outcome=IMP().getResult()
    
    
    
    
    sgn=SGN()
    sgn.setImgPixels(outcome)
    outcome1=sgn.getResult() #得到的控制代码
    #np.savetxt("c.txt",outcome1, fmt="%d", delimiter=",")
    np.save("code.npy",outcome1)
    
    print (outcome1.shape)
    
    #仿真
    sim=SIM()
    sim.setImgPixels(outcome)
    sim.setCode(outcome1)
    outcome2=sim.analyse()
    outcome=255-outcome
    outcome2=255-outcome2
    
    #ctr=CTR()
    
    
    cv2.namedWindow("Image") 
    cv2.namedWindow("Image2") 
    cv2.imshow("Image", outcome) 
    cv2.imshow("Image2", outcome2) 
    cv2.waitKey (0)
    cv2.destroyAllWindows()
    
    

    图像处理

    #coding:utf-8
    import cv2
    import numpy as np
    # pip install opencv-python
    class IMP:
        '''
        image process
        输入:图片、背景图片大小、缩放比例、相对位置
        输出:尺寸为x/q*y/q的灰度矩阵
        功能:
            灰度化图片
            确定图片右下角与绘制起点的相对位置
            根据设定要求放缩图片,使图片整体位于绘制范围内部
            输出尺寸为x/q*y/q的灰度矩阵
        '''
        backgroundSize=np.asarray([210,150])#[297,210]
        penLineSize=0.5
        imgPath="MTP//girl3.jpg"
        scaling=0.3
        position=np.asarray([1,1])
        
        def __init__(self):
    
            ## 创建空白背景图片
            self.backgroundPixels=self.backgroundSize/self.penLineSize
            self.backgroundPixels = np.zeros(self.backgroundPixels.astype(int).tolist(), np.uint8)
            self.backgroundPixelsX,self.backgroundPixelsY=self.backgroundPixels.shape
            ## 读取目标图片
            targetImg= cv2.GaussianBlur(cv2.imread(self.imgPath,0),(5,5),1.5)#高斯滤波
            targetImgHeight,targetImgWidth=targetImg.shape
            self.targetImg=255-cv2.resize(targetImg,(int(targetImgWidth*self.scaling),int(targetImgHeight*self.scaling))) #缩放
            self.targetImgPixelsX,self.targetImgPixelsY=self.targetImg.shape
    
            ## 将目标图片放置到黑色空白背景图片上,并指定相对位置
            
            '''
            cv2.namedWindow("Image") 
            cv2.imshow("Image", outcome) 
            cv2.waitKey (0)
            cv2.destroyAllWindows()
            '''
        def replace(self):
            #输出
            outcome = np.zeros(self.backgroundPixels.shape, np.uint8)       
            
            #确定背景、图片右下角的相对位置(像素)
            positionPixelX,positionPixelY=(self.position/self.penLineSize).astype(int).tolist()
    
            #循环变量,x和y确定背景中的一个像素点,indexX和indexY确定图片中的一个像素点
            x,y=(self.backgroundPixelsX-positionPixelX+1,self.backgroundPixelsY-positionPixelY+1)
            indexX,indexY=(self.targetImgPixelsX-1,self.targetImgPixelsY-1)
    
            #用图片中的像素点替换掉背景中的像素点,从而将图片放入背景中,生成新图像outcome
            while (indexX>=0 and x>=0) :
                while (indexY>=0 and y>=0):
                    outcome[x,y]=self.targetImg[indexX,indexY]
                    indexY=indexY-1
                    y=y-1    
                indexX=indexX-1
                x=x-1
                #遍历完x方向后,y要回到原来的位置,从x+1开始
                y=self.backgroundPixelsY-positionPixelY+1
                indexY=self.targetImgPixelsY-1      
            self.outcome=outcome
    
        def getResult(self):
            self.replace()
            return self.outcome
    

    信号产生

    #coding:utf-8
    import cv2
    import numpy as np
    class SGN:
        '''
        输入:尺寸为x/q*y/q的灰度矩阵
        输出:描述笔尖运动的矩阵
        功能:绘制图案时,机器将从绘制起点(0,0)开始绘制图案,先向y轴运动绘制x=0处的图案,然后向快速到(1,0)处向y轴运动绘制x=1处的图案。
        '''
        #targetImgHeight,targetImgWidth=targetImg.shape
        #np.savetxt("a.txt", sgn.getResult(), fmt="%d", delimiter=",")
        step=50
        informationContent=5 #描述输出矩阵的列数
        threshold=0
    
        def __init__(self):
            #起始、终止信息
            begin=np.zeros([1,self.informationContent], dtype = int)
            begin[0,0]=100
            self.stop=np.zeros([1,self.informationContent], dtype = int)
            self.begin=begin
    
        def setImgPixels(self,imgPixel):
            self.imgPixel=imgPixel.astype(int)
            self.imgPixelHeight,self.imgPixelWidth=self.imgPixel.shape
            self.begin[0,1]=self.imgPixelWidth
            self.begin[0,2]=self.imgPixelHeight
        
        def run(self):
            indexX=self.imgPixelHeight-1
            indexY=self.imgPixelWidth-1
            times=int(255/self.step)
            imgCopy=self.imgPixel
            while times>0:  
                recordeFlag=1
                while (indexX>=0):
                    while(indexY>=0):  
                        if imgCopy[indexX,indexY]>self.threshold:
                            if (recordeFlag==1)and(indexY>0):
                                temp=np.zeros([1,self.informationContent], dtype = int)
                                temp[0,0]=1
                                temp[0,1]=indexX
                                temp[0,2]=indexY
                                temp[0,3]=indexX
                                temp[0,4]=indexY
                                recordeFlag=0
                            elif indexY==0 and recordeFlag==1:
                                recordeFlag=1
                            elif indexY==0:
                                self.begin=np.r_[self.begin,temp]
                                recordeFlag=1
                            elif recordeFlag==0:
                                temp[0,3]=indexX
                                temp[0,4]=indexY
                        else:
                            if recordeFlag==0:
                                self.begin=np.r_[self.begin,temp]
                                recordeFlag=1
    
                        indexY=indexY-1
                    indexY=self.imgPixelWidth-1    
                    indexX=indexX-1
                indexX=self.imgPixelHeight-1
                times=times-1
                imgCopy=imgCopy-self.step
            pass
        def getResult(self):
            self.run()
            self.outcome=np.r_[self.begin,self.stop]
            return self.outcome
    

    电机控制

    #coding: utf8
    import RPi.GPIO as GPIO
    import time
    import sys
    
    
    class StepMotor:
        parameter=8*1.8/(16*360)  #丝杆导程*全步进角/(细分系数*360度) 单位mm/次 意义每次步进脉冲平台移动距离
        position=-5
        reset=False 
        def __init__(self,stepPin,dirPin,minPosition=0,maxPosition=200):
            self.stepPin=stepPin
            self.dirPin=dirPin
            self.minPosition=minPosition
            self.maxPosition=maxPosition
            GPIO.setwarnings(False)
            GPIO.setmode(GPIO.BOARD)
            GPIO.setup(self.stepPin,GPIO.OUT)
            GPIO.setup(self.dirPin,GPIO.OUT)
            self.setDirF()
            self.goToOriginalPoint()
        def setDirF(self):
            GPIO.output(self.dirPin,1)
        def setDirB(self):
            GPIO.output(self.dirPin,0)
        def move(self):
            GPIO.output(self.stepPin,0)
            time.sleep(self.speed)
            GPIO.output(self.stepPin,1)
            time.sleep(self.speed)
        def run(self,speed=0.0001,distance=0):
            #speed=0.00001快 0.0001慢
            self.speed=speed
            times=int(distance/self.parameter) #这是由螺杆导程、步进电机步进角决定的
            while(times>0):
                self.move()
                times=times-1   
        def getPermission(self,direction,distance):
            '''
            检查电机的位置,避免超程
            '''
            if direction == 'F' :
                nextPosition=self.position+distance
            elif direction == 'B':
                nextPosition=self.position-distance
            if (self.minPosition<=nextPosition) and (self.maxPosition>=nextPosition):
                self.position=nextPosition
                return True
            else:
                print("超程警告,已取消操作")
                return False
        def goF(self,speed=0.0001,distance=0):
            if(self.getPermission('F',distance)):
                self.setDirF()
                self.run(speed,distance)        
        def goB(self,speed=0.0001,distance=0):
            if(self.getPermission('B',distance)):
                self.setDirB()
                self.run(speed,distance)
        def goToPosition(self,position,speed=0.0001):
            distance=position-self.position
            if distance>=0 :
                self.goF(speed,distance)
            else:
                distance=-distance
                self.goB(speed,distance)
        def goToOriginalPoint(self,speed=0.0001):
            if (not self.reset):
                if self.position<=0:
                    self.setDirF()
                    self.run(speed,-self.position)
                    self.reset=True
                else:
                    pass
            else:
                self.goToPosition(0,0.0001)
         
    
    class Steer:
        contrlPeriod=0.020
        pulseWidth=0.000
        def __init__(self,contrlPin):
            self.contrlPin=contrlPin
            GPIO.setwarnings(False)
            GPIO.setmode(GPIO.BOARD)
            GPIO.setup(self.contrlPin,GPIO.OUT)
        def run(self,angle):
            self.pulseWidth=(angle+45.0)/90.0*0.001
            i=0
            while(i<25):
                i=i+1
                GPIO.output(self.contrlPin,1)
                time.sleep(self.pulseWidth)
                GPIO.output(self.contrlPin,0)
                time.sleep(self.contrlPeriod-self.pulseWidth)
    
    class CTR:
        penLineSize=0.5
        def __init__(self):
            self.steer=Steer(29)  
            self.penUp()
            self.XMotor=StepMotor(35,37,0,150)
            self.YMotor=StepMotor(31,33,0,210)
    
        
        def setCode(self,code):
            self.code=code
            print(self.code.shape)
        def goToPosition(self,x,y,speed=0.0001):
            self.YMotor.goToPosition(y,speed)
            self.XMotor.goToPosition(x,speed)
        def penDown(self):
            self.steer.run(180)
        def penUp(self):
            self.steer.run(100)
            
        def drawLine(self,line,start,end):
            startPosition=(self.imgWidth-start)*self.penLineSize
            endPosition=(self.imgWidth-end)*self.penLineSize
            linePosition=(self.imgHeight-line)*self.penLineSize
            self.goToPosition(startPosition,linePosition,0.00001)
            #下笔
            self.penDown()
    
            self.goToPosition(endPosition,linePosition)
            #抬笔
            self.penUp()
        def run(self):
            codeIndex=0
            codeId=100 
            while codeId>0:
                currentCode=self.code[codeIndex] #读取第一条代码                         
                codeId=currentCode[0] #下一条代码类型
                if codeId==100:
                    self.imgWidth=currentCode[1]
                    self.imgHeight=currentCode[2]
                if codeId==1: #如果是画直线
                    line=currentCode[1] # X方向第几行
                    start=currentCode[2] #Y方向开始的地方
                    end=currentCode[4]   #Y方向结束的地方
                    self.drawLine(line,start,end)
                codeIndex=codeIndex+1 #下一条代码索引
                print(codeIndex)
            print("结束")
            self.XMotor.goToOriginalPoint()
            self.YMotor.goToOriginalPoint()
            
    

    仿真

    #coding:utf-8
    import cv2
    import numpy as np
    class SIM:
        
        def __init__(self):
            pass
        def setImgPixels(self,imgPixel):
            self.imgPixelHeight,self.imgPixelWidth=imgPixel.shape
            self.imgPixel= np.zeros([self.imgPixelHeight,self.imgPixelWidth], np.uint8)
        def setCode(self,code):
            self.code=code
        def analyse(self):
            currentCode=self.code[0]
            codeIndex=0
            codeId=100
            imgCopy=self.imgPixel
            while codeId>0:
                codeIndex=codeIndex+1
                currentCode=self.code[codeIndex]
                codeId=currentCode[0]
                if codeId>0:
                    line=currentCode[1]
                    start=currentCode[2]
                    end=currentCode[4]
                    imgtemp=imgCopy[line,...]       
                    imgtemp[end:start+1]=imgtemp[end:start+1]+50
                    imgCopy[line,...]=imgtemp
            
            return imgCopy
    

    实际效果

    有时间放个视频

    发现的问题

    不是很精密,笔会抖
    软件功能太少,有待升级

    命名

    就叫MTP吧

  • 相关阅读:
    MVC的各个部分都有那些技术来实现?如何实现?
    spring入门学习
    动态 SQL 及安全性(Birt vs 润乾)
    报表的 SQL 植入风险及规避方法
    web 如何实现精准打印
    birt 报表与润乾报表对比
    ireport 与润乾报表对比
    银行存折套打续打功能 -- 报表如何记录上次打印的位置
    脚本中如何做填报数据校验
    鼠标悬停出现提示信息怎么做
  • 原文地址:https://www.cnblogs.com/uestcman/p/10244109.html
Copyright © 2011-2022 走看看