pythonwx+opencv+kociemba实现魔方还原

源码:https://gitee.com/zouchengxin/cube

窗口程序(cube_desktop.py)

import wx
import cv2
import colordetect as cd
import colordraw
class CubeFrame(wx.Frame):

    def __init__(self,*args,**kwargs):
        super(CubeFrame,self).__init__(*args,**kwargs)

        self.SetClientSize(600,680)
        self.SetMaxSize((600,680))
        self.SetMinSize((600,680))
        self.Bind(wx.EVT_IDLE,self.onIDLE)
        self.panel = wx.Panel(self, wx.ID_ANY)

        labelOne = wx.StaticText(self.panel, wx.ID_ANY, '机器人串口:')
        inputTxtOne = wx.TextCtrl(self.panel, wx.ID_ANY, 'COM3')
        labelTwo = wx.StaticText(self.panel, wx.ID_ANY, '摄像头串口:')
        inputTxtTwo = wx.TextCtrl(self.panel, wx.ID_ANY, '0')
        self.Bind(wx.EVT_TEXT,self.onCOM1,inputTxtOne)
        self.Bind(wx.EVT_TEXT, self.onCOM2, inputTxtTwo)

        self.cap=None
        self.index = 0
        self.COM2=0
        self.COM1="COM3"
        self.names=["up.jpg","down.jpg","left.jpg","right.jpg","front.jpg","back.jpg"]
        labelThree = wx.StaticText(self.panel, wx.ID_ANY, '魔方:')
        choice=wx.Choice(self.panel,choices=["上面","下面","左面","右面","前面","后面"])
        choice.SetSelection(self.index)
        self.Bind(wx.EVT_CHOICE,self.onChoice,choice)

        self.cameraBtn = wx.Button(self.panel, wx.ID_ANY, '拍照保存')
        self.cubeBtn = wx.Button(self.panel, wx.ID_ANY, '魔方还原')
        openBtn = wx.Button(self.panel, wx.ID_ANY, '打开摄像头')
        self.cameraBtn.Disable()
        #self.cubeBtn.Disable()
        self.Bind(wx.EVT_BUTTON, self.onCamera, self.cameraBtn)
        self.Bind(wx.EVT_BUTTON, self.onCube, self.cubeBtn)
        self.Bind(wx.EVT_BUTTON,self.onOpen,openBtn)

        self.pic=wx.StaticBitmap(self.panel)
        self.pic.SetSize((600,500))
        img=wx.Bitmap()
        img.Create(600,500)
        self.pic.SetBitmap(img)


        topSizer = wx.BoxSizer(wx.VERTICAL)
        inputOneSizer = wx.BoxSizer(wx.HORIZONTAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)

        inputOneSizer.Add(labelOne, 0, wx.ALL, 5)
        inputOneSizer.Add(inputTxtOne, 1, wx.ALL | wx.EXPAND, 5)
        inputOneSizer.Add(labelTwo, 0, wx.ALL, 5)
        inputOneSizer.Add(inputTxtTwo, 1, wx.ALL | wx.EXPAND, 5)
        inputOneSizer.Add(labelThree, 0, wx.ALL | wx.EXPAND, 5)
        inputOneSizer.Add(choice, 1, wx.ALL | wx.EXPAND, 5)
        btnSizer.Add(openBtn, 0, wx.ALL, 5)
        btnSizer.Add(self.cameraBtn, 0, wx.ALL, 5)
        btnSizer.Add(self.cubeBtn, 0, wx.ALL, 5)
        topSizer.Add(inputOneSizer, 0, wx.ALL | wx.EXPAND, 5)
        topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL | wx.EXPAND, 5)
        topSizer.Add(self.pic, 0, wx.ALL | wx.EXPAND, 5)
        topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL | wx.EXPAND, 5)
        topSizer.Add(btnSizer, 0, wx.ALL | wx.CENTER, 5)
        self.panel.SetSizer(topSizer)
        #topSizer.Fit(self)
        self.Show(True)

    #打开摄像头按钮点击事件
    def onOpen(self,event):
        try:
            self.cap = cv2.VideoCapture(self.COM2)
            if self.cap.isOpened()==False:
                self.cap.release()
                self.cap.retrieve()
                self.cap==None
                wx.MessageDialog(self, "打开摄像头失败,检查串口是否正确").ShowModal()
            else:
                self.cameraBtn.Enable()
        except Exception as e:
            wx.MessageDialog(self,"打开摄像头失败,检查串口是否正确").ShowModal()
            self.cap = None
            print("打开摄像头失败")
            print(str(e))


    def onCOM1(self,event):
        self.COM1=event.GetEventObject().GetLineText(0)
        #print("COM1:"+self.COM1)

    def onCOM2(self,event):
        self.COM2=event.GetEventObject().GetLineText(0)
        #print("COM2:" + self.COM2)

    #选择魔方六面
    def onChoice(self,event):
        choice=event.GetEventObject()
        self.index=choice.GetSelection()
        if self.cap:
            if self.cap.isOpened()==False:
                self.cap.open(self.COM2)

    #拍照保存按钮点击事件
    def onCamera(self, event):
        ret, frame = self.cap.read()
        if ret:
            cv2.imwrite(self.names[self.index],frame)
            self.cap.release()
            print(self.names[self.index]+"保存成功")

    #魔方还原按钮点击事件
    def onCube(self, event):
        if self.COM1!="":
            try:
                cd.analyzeCube(self.COM1)
                if self.cap!=None:
                    self.cap.release()
            except Exception as e:
                wx.MessageDialog(self, "解析魔方出错,请检查文件是否存在或串口是否正确").ShowModal()
                print("解析魔方出错(COM1=%s,COM2=%s)"% (self.COM1,self.COM2))
                print(str(e))

    #监听摄像头事件
    def onIDLE(self,event):
        if self.cap:
            ret,frame=self.cap.read()
            if ret:
                #cv2.imshow("img",frame)
                self.showImage(frame)

    #绘制图像
    def showImage(self,img):
        height,width=img.shape[:2]
        (B,G,R)=cv2.split(img)
        bitmap=wx.Bitmap.FromBuffer(width,height,cv2.merge((R,G,B)))
        self.pic.SetBitmap(bitmap)

图像处理(colordetect.py)

import cv2
import numpy as np
import sys
import serial
from colordraw import *
import math
import json
import kociemba
import time
from multiprocessing import Pool
from concurrent.futures import ThreadPoolExecutor

kernel_15 = np.ones((15,15),np.uint8)#15x15的卷积核
kernel_50 = np.ones((50,50),np.uint8)#50x50的卷积核
draw = np.zeros((4800,6400, 3), dtype="uint8")#创建一个高4800*宽6400画布
#处理图片
def colorMatch(side):
    
    cube_rgb = cv2.imread( side + '.jpg')
    cube_gray = cv2.cvtColor(cube_rgb, cv2.COLOR_BGR2GRAY)#颜色转换gray
    cube_hsv = cv2.cvtColor(cube_rgb,cv2.COLOR_BGR2HSV)#颜色转换hsv
    cube_gray = cv2.adaptiveThreshold(cube_gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,11,2)#自适应滤波
    #cv2.namedWindow(side,cv2.WINDOW_NORMAL)
    #cv2.imshow(side,cube_rgb)
    # 白色
    lower_white = np.array([0, 0, 201])
    upper_white = np.array([180, 50, 255])
    white_mask = cv2.inRange(cube_hsv, lower_white, upper_white)
    white_erosion = cv2.erode(white_mask, kernel_15, iterations = 1)
    white_res = cv2.bitwise_and(cube_rgb, cube_rgb, mask = white_erosion)
    #红色
    '''lower_red = np.array([0,50,50])
    upper_red = np.array([10,255,255])
    red_mask0 = cv2.inRange(cube_hsv, lower_red, upper_red)
    lower_red = np.array([172, 135, 150])
    upper_red = np.array([179, 240, 255])
    red_mask1 = cv2.inRange(cube_hsv, lower_red, upper_red)
    red_mask = red_mask0 + red_mask1
    '''
    lower_red = np.array([170, 110, 145])
    upper_red = np.array([182, 240, 255])
    red_mask = cv2.inRange(cube_hsv, lower_red, upper_red)
    red_erosion = cv2.erode(red_mask, kernel_15, iterations = 1)
    red_res = cv2.bitwise_and(cube_rgb, cube_rgb, mask = red_erosion)
    #橙色
    lower_orange = np.array([3, 115, 195])
    upper_orange = np.array([9, 190, 255])
    orange_mask = cv2.inRange(cube_hsv, lower_orange, upper_orange)
    orange_erosion = cv2.erode(orange_mask, kernel_15, iterations = 1)
    orange_res = cv2.bitwise_and(cube_rgb, cube_rgb, mask = orange_erosion)

    #黄色
    lower_yellow = np.array([20, 125, 142])
    upper_yellow = np.array([34, 243, 255])
    yellow_mask = cv2.inRange(cube_hsv, lower_yellow, upper_yellow)
    yellow_erosion = cv2.erode(yellow_mask, kernel_15, iterations = 1)
    yellow_res = cv2.bitwise_and(cube_rgb, cube_rgb, mask = yellow_erosion)
    #绿色
    lower_green = np.array([68,140,120])
    upper_green = np.array([82,255,245])
    green_mask = cv2.inRange(cube_hsv, lower_green, upper_green)
    green_erosion = cv2.erode(green_mask, kernel_15, iterations = 1)
    green_res = cv2.bitwise_and(cube_rgb, cube_rgb, mask = green_erosion)
    #蓝色
    lower_blue = np.array([95, 123, 109])
    upper_blue = np.array([124, 253, 240])
    blue_mask = cv2.inRange(cube_hsv, lower_blue, upper_blue)
    blue_erosion = cv2.erode(blue_mask, kernel_15, iterations = 1)
    blue_res = cv2.bitwise_and(cube_rgb, cube_rgb, mask = blue_erosion)
    #总掩膜
    mask = red_erosion + green_erosion + yellow_erosion + blue_erosion + orange_erosion + white_erosion
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_50)#开运算分割色块
    mask = cv2.erode(mask, kernel_50, iterations = 1)
    res = cv2.bitwise_and(cube_hsv, cube_hsv, mask = mask)  
       
    edges = cv2.Canny(mask,50,80,apertureSize=5)
    points = cv2.findNonZero(edges)
    #求得图像最大最小值点
    min = np.amin(points, axis=0)#求每列的最小值,axis 0为列,1为行
    max = np.amax(points, axis=0)#求每列的最大值,axis 0为列,1为行
    #求像素x、y最大最小值
    w, h = cube_gray.shape[::-1]#获取cube_gray大小(宽列数w*高行数h)
    x_max = max[0][0]
    y_max = max[0][1]
    x_min = min[0][0]
    y_min = min[0][1] 
    width = int(x_max - x_min)
    height = int(y_max - y_min)
    #定位中点色块颜色及坐标
    def midpoint(x1,y1,x2,y2):
        x_mid = int((x1 + x2)/2)
        y_mid = h - int(((y1 + y2)/2))
        color = res[y_mid, x_mid]
        return ([int(color[0]), int(color[1]), int(color[2])])
    # midpoint获得每个面九个色块的颜色
    mid_1 = midpoint(x_min, y_max, (x_min + int(width/3)), (y_max - int(height/3)))
    mid_2 = midpoint((x_min + int(width/3)), y_max, (x_min + int(width*2/3)), (y_max - int(height/3)))
    mid_3 = midpoint((x_min + int(width*2/3)), y_max, x_max, (y_max - int(height/3)))
    mid_4 = midpoint(x_min, (y_max - int(height/3)), (x_min + int(width/3)), (y_max - int(height*2/3)))
    mid_5 = midpoint((x_min + int(width/3)), (y_max - int(height/3)), (x_min + int(width*2/3)), (y_max - int(height*2/3)))
    mid_6 = midpoint((x_min + int(width*2/3)), (y_max - int(height/3)), x_max, (y_max - int(height*2/3)))
    mid_7 = midpoint(x_min, (y_max - int(height*2/3)), (x_min + int(width/3)), y_min)
    mid_8 = midpoint(x_min + int(width/3), (y_max - int(height*2/3)), (x_min + int(width*2/3)), y_min)
    mid_9 = midpoint(x_min + int(width*2/3), (y_max - int(height*2/3)), x_max, y_min)
    mids = [mid_1, mid_2, mid_3, mid_4, mid_5, mid_6, mid_7, mid_8, mid_9]


    s=''
    for rgb in mids:#hsv
        if ((0<=rgb[0]<=180 ) and (0<=rgb[1]<=50 ) and( 201<=rgb[2]<=255)):#白
            s+='D'
        elif ((3<=rgb[0]<=9 )and (115<=rgb[1]<=190 )and (195<=rgb[2] <=255)):#澄
            s+='B'
        elif ((95<=rgb[0] <=124) and (123<=rgb[1]<=253) and (109<=rgb[2]<=240)):#蓝
            s+='L'
        elif( (68<=rgb[0]<=82) and (140<=rgb[1]<=255) and (120<=rgb[2]<=245)):#绿
            s+='R'
        elif ((20<=rgb[0]<=34) and (125<=rgb[1]<=243) and(142 <=rgb[2]<=255)):#黄
            s+='U'
        elif ((170<=rgb[0]<=182)and (110<=rgb[1] <=240 )and (145<=rgb[2]<=255 )):#红
            s+='F'
    return(s)

#任务: 1.IO密集型(会有cpu空闲的时间) 
#      2.计算密集型
#多线程对于IO密集型任务有作用,而计算密集型任务不推荐使用多线程。而其中我们还可以得到一个结论:由于GIL锁,多线程不可能真正实现并行,所谓的并行也只是宏观上并行微观上并发,本质上是由于遇到io操作不断的cpu切换所造成并行的现象。由于cpu切换速度极快,所以看起来就像是在同时执行。
#--问题:没有利用多核的优势
#--这就造成了多线程不能同时执行,并且增加了切换的开销,串行的效率可能更高。

def analyzeCube(port):
    time_start=time.time()
    side=['up','right','front','down','left','back']
    #ps=Pool(6)

    #cpu=ps.apply(colorMatch,args=(i,))      # 同步执行
    #up_cpu=ps.apply_async(colorMatch,args=('up',))  # 异步执行
    #right_cpu=ps.apply_async(colorMatch,args=('right',))
    #front_cpu=ps.apply_async(colorMatch,args=('front',))
    #down_cpu=ps.apply_async(colorMatch,args=('down',))
    #left_cpu=ps.apply_async(colorMatch,args=('left',))
    #back_cpu=ps.apply_async(colorMatch,args=('back',))

    # 关闭进程池,停止接受其它进程
    #ps.close()
    # 阻塞进程
    #ps.join()

    #get_str = up_cpu.get() + right_cpu.get() + front_cpu.get() + down_cpu.get() + left_cpu.get() + back_cpu.get()

    #线程池(解决进程池问题)
    ts = ThreadPoolExecutor(6)
    up_cpu=ts.submit(colorMatch,"up")
    down_cpu=ts.submit(colorMatch,"down")
    right_cpu =ts.submit(colorMatch,"right")
    left_cpu =ts.submit(colorMatch,"left")
    front_cpu =ts.submit(colorMatch,"front")
    back_cpu =ts.submit(colorMatch,"back")

    get_str = up_cpu.result() + right_cpu.result() + front_cpu.result() + down_cpu.result() + left_cpu.result() + back_cpu.result()

    print('当前颜色:'+get_str)

    solve_str=kociemba.solve(get_str)
    print(solve_str)
    drawcolor(get_str)

    time_end=time.time()
    print('所需时间:',time_end-time_start,'s')

    #向串口输入指令
    ser = serial.Serial(port, 9600, timeout=None)
    res=ser.write(solve_str.encode("gbk"))
    print("已向串口("+port+")写入"+str(res)+"字节")

程序执行效果(https://gitee.com/zouchengxin/cube/blob/master/cube_desktop.PNG)

CSDN图标

说明

1.机器人串口:与机器人通信的串口
2.摄像头串口:默认0代表计算机自带的摄像头,如使用外部设备请尝试1,2,…
3.填好串口后点击打开摄像头即可看见画面
4.选择一个魔方面(up,down,left,right,front,back六个面)进行拍照保存
5.最后点击魔方还原(就会向机器人串口发送通过算法得到的字符串)

当前颜色:BRDBURUFFLBRDRLFLLBLURFBDFRLDUUDDLRBDLRULDUFFFFRUBBDUB
U2 R D' L2 B' R D L2 U' F U' B' U B2 R2 D' L2 F2 D2 B2 U2

6."U2 R D’ L2 B’ R D L2 U’ F U’ B’ U B2 R2 D’ L2 F2 D2 B2 U2"这一串字符将会写入机器人串口中
其中U(up):上面,D(down):下面,L(left):左面,R(right):右面,F(前面),B(back):后面
R:右面顺时针转90度
R’:右面逆时针转90度
R2:右面转180度

发布了46 篇原创文章 · 获赞 90 · 访问量 33万+

猜你喜欢

转载自blog.csdn.net/qq_40077167/article/details/88901439
今日推荐