> 技术文档 > 【2024电赛E题】机械臂+cv2视觉方案_电赛机械臂

【2024电赛E题】机械臂+cv2视觉方案_电赛机械臂


2024电赛E题_机械臂+cv2视觉方案

三子棋_人机对弈

【2024电赛E题】机械臂+cv2视觉方案_电赛机械臂

【2024电赛E题】机械臂+cv2视觉方案_电赛机械臂

1.整体设计方案

【2024电赛E题】机械臂+cv2视觉方案_电赛机械臂

2.机械臂系统方案

使用常见的开源六轴自由度stm32机械手臂
【2024电赛E题】机械臂+cv2视觉方案_电赛机械臂

直接使用商家官方给的代码,

我们只需要通过串口给它发送六个舵机的PWM占空比即可控制机械臂的运动

通过商家提供的源码,了解它的串口通信协议,仿照通信协议的格式发送数据即可

3.视觉系统方案

Jetson Nano开发板,树莓派也可,都一样用

【2024电赛E题】机械臂+cv2视觉方案_电赛机械臂

识别内容:

  • 黑白棋子共十个的圆心坐标
  • 九宫格棋盘的旋转角度(棋盘中心固定,已知旋转角度和棋盘尺寸即可计算九个格的中心坐标)

数据发送:

  • 棋盘外最下方的黑棋子坐标和白棋子的坐标
  • 棋盘九个格的状态(‘X’-黑子,‘O’-白子,’ \'-空)
  • 九宫格九个中心点的位置坐标

4.逆运动学解算

机器人运动学逆解指的是已知机器人末端执行器在工作空间中的期望位置和姿态,求解出机器人各个关节变量的取值。

为了使机械臂能够准确地在棋盘上进行落子操作,需要知道每个关节应如何运动才能达到期望的末端位置和姿态。通过运动学逆解,计算出各个关节的角度或位移,从而实现精确的落子动作。合理的逆解计算可以优化机械臂的运动路径,减少不必要的动作和时间消耗。

输入坐标,输出每个轴的角度
【2024电赛E题】机械臂+cv2视觉方案_电赛机械臂

用到了高中数学,余弦定理,公式为cosC = (a² + b² - c²) / 2ab

计算步骤

已知机械臂末端坐标(x,y,z)和α角,求角
首先,测量出机械臂的长度(mm):

  • L 1 = 105.0 L_1 = 105.0 L1=105.0
  • L 2 = 89.0 L_2 = 89.0 L2=89.0
  • L 3 = 180.0 L_3 = 180.0 L3=180.0
  • 计算在xy平面内的底盘舵机角度 θ 1\\theta_1 θ1
    • θ 1 = arctan ⁡ ( y x ) \\theta_1 = \\arctan(\\frac{y}{x}) θ1=arctan(xy)

将三维立体坐标转换到二维平面内

  • 机械臂末端的高度和水平距离 :

    • D = ∣ x 2 + y 2 ∣ D = \\sqrt{|x^2 + y^2|} D=x2+y2 H = z H = z H=z
  • L 3L_3 L3 x x x 方向和 y y y 方向的分量:

    • x α = L 3 × cos ⁡ ( α r a d ) x_{\\alpha} = L_3 \\times \\cos(\\alpha_{rad}) xα=L3×cos(αrad) y α = L 3 × sin ⁡ ( α r a d ) y_{\\alpha} = L_3 \\times \\sin(\\alpha_{rad}) yα=L3×sin(αrad)
  • 连杆 L 1L_1 L1 L 2L_2 L2 x x x y y y 分量:

    • x θ = D − x α x_{\\theta} = D - x_{\\alpha} xθ=Dxα y θ = H − y α y_{\\theta} = H - y_{\\alpha} yθ=Hyα
  • 连杆 L 2L_2 L2末端到原点的距离:

    • d θ = x θ 2 + y θ 2 d_{\\theta} = \\sqrt{x_{\\theta}^2 + y_{\\theta}^2} dθ=xθ2+yθ2
  • 连杆 L 1L_1 L1 L 2L_2 L2之间的夹角 Θ \\Theta Θ

    • Θ = arccos ⁡ ( L 1 2 + L 2 2 − d θ 2 2 × L 1 × L 2 ) \\Theta = \\arccos(\\frac{L_1^2 + L_2^2 - d_{\\theta}^2}{2 \\times L_1 \\times L_2}) Θ=arccos(2×L1×L2L12+L22dθ2)
  • 舵机 4 的角度 θ 3\\theta_3 θ3

    • θ 3 = 180 − π × 1.5 − Θ 180 \\theta_3 = 180 - \\frac{\\pi \\times 1.5 - \\Theta}{180} θ3=180180π×1.5Θ​​ (转换为角度)
  • 角度 θ 2 1 \\theta_{2_1} θ21 θ 2 2 \\theta_{2_2} θ22

    • θ 2 1 = arccos ⁡ ( L 1 2 + d θ 2 − L 2 2 2 × L 1 × d θ ) \\theta_{2_1} = \\arccos(\\frac{L_1^2 + d_{\\theta}^2 - L_2^2}{2 \\times L_1 \\times d_{\\theta}}) θ21=arccos(2×L1×dθL12+dθ2L22) θ 2 2 = arccos ⁡ ( x θ d θ ) \\theta_{2_2} = \\arccos(\\frac{x_{\\theta}}{d_{\\theta}}) θ22=arccos(dθxθ)
  • 舵机 5 的角度 θ 2\\theta_2 θ2

    • θ 2 = 180 − ( θ 2 1 180 + θ 2 2 180 ) \\theta_2 = 180 - (\\frac{\\theta_{2_1}}{180} + \\frac{\\theta_{2_2}}{180}) θ2=180(180θ21+180θ22)
  • 舵机 3 的角度 θ 4\\theta_4 θ4

    • θ 4 = ∣ Θ − α r a d + θ 2 1 + θ 2 2 180 − 90 ∣ \\theta_4 = |\\frac{\\Theta - \\alpha_{rad} + \\theta_{2_1} + \\theta_{2_2}}{180} - 90| θ4=180Θαrad+θ21+θ2290

5.串口通信协议

stm32中控 >>> stm32机械臂

  • 机械臂源码中接收的数据格式

【2024电赛E题】机械臂+cv2视觉方案_电赛机械臂

  • stm32中控发送的数据处理

  • 根据源码中定义的数据格式设定串口发送函数

    • //---串口发送--------------------------------------------------------------------------------------//{G0000#000P1500T1000!#001P1500T1000!#002P1500T1000!#003P1500T1000!#004P1500T1000!#005P1500T1000!}// 1号 p1500t1000 2号 p1500t1000... uint8_t hex_buffer[98];void send_data_deal(void){hex_buffer[0] = \'{\';hex_buffer[1] = \'G\';hex_buffer[2] = \'0\';hex_buffer[3] = \'0\';hex_buffer[4] = \'0\';hex_buffer[5] = \'1\';hex_buffer[6] = \'#\';hex_buffer[7] = \'0\';hex_buffer[8] = \'0\';hex_buffer[9] = \'0\';hex_buffer[10] = \'P\';hex_buffer[11] = ((theta1*11.1+500)/1000)+\'0\';hex_buffer[12] = ((int)(theta1*11.1+500) % 1000)/100+\'0\';hex_buffer[13] = ((int)(theta1*11.1+500) % 100)/10+\'0\';hex_buffer[14] = ((int)(theta1*11.1+500) % 10)+\'0\';;hex_buffer[15] = \'T\';hex_buffer[16] = (time_move/1000)+\'0\';hex_buffer[17] = (time_move % 1000)/100+\'0\';hex_buffer[18] = (time_move % 100)/10+\'0\';hex_buffer[19] = (time_move % 10)+\'0\';;hex_buffer[20] = \'!\';hex_buffer[21] = \'#\';hex_buffer[22] = \'0\';hex_buffer[23] = \'0\';hex_buffer[24] = \'1\';hex_buffer[25] = \'P\';hex_buffer[26] = ((theta2*11.1+500)/1000)+\'0\';hex_buffer[27] = ((int)(theta2*11.1+500) % 1000)/100+\'0\';hex_buffer[28] = ((int)(theta2*11.1+500) % 100)/10+\'0\';hex_buffer[29] = ((int)(theta2*11.1+500) % 10)+\'0\';;hex_buffer[30] = \'T\';hex_buffer[31] = (time_move/1000)+\'0\';hex_buffer[32] = (time_move % 1000)/100+\'0\';hex_buffer[33] = (time_move % 100)/10+\'0\';hex_buffer[34] = (time_move % 10)+\'0\';;hex_buffer[35] = \'!\';hex_buffer[36] = \'#\';hex_buffer[37] = \'0\';hex_buffer[38] = \'0\';hex_buffer[39] = \'2\';hex_buffer[40] = \'P\';hex_buffer[41] = ((theta3*11.1+500)/1000)+\'0\';hex_buffer[42] = ((int)(theta3*11.1+500) % 1000)/100+\'0\';hex_buffer[43] = ((int)(theta3*11.1+500) % 100)/10+\'0\';hex_buffer[44] = ((int)(theta3*11.1+500) % 10)+\'0\';;hex_buffer[45] = \'T\';hex_buffer[46] = (time_move/1000)+\'0\';hex_buffer[47] = (time_move % 1000)/100+\'0\';hex_buffer[48] = (time_move % 100)/10+\'0\';hex_buffer[49] = (time_move % 10)+\'0\';;hex_buffer[50] = \'!\';hex_buffer[51] = \'#\';hex_buffer[52] = \'0\';hex_buffer[53] = \'0\';hex_buffer[54] = \'3\';hex_buffer[55] = \'P\';hex_buffer[56] = ((theta4*11.1+500)/1000)+\'0\';hex_buffer[57] = ((int)(theta4*11.1+500) % 1000)/100+\'0\';hex_buffer[58] = ((int)(theta4*11.1+500) % 100)/10+\'0\';hex_buffer[59] = ((int)(theta4*11.1+500) % 10)+\'0\';;hex_buffer[60] = \'T\';hex_buffer[61] = (time_move/1000)+\'0\';hex_buffer[62] = (time_move % 1000)/100+\'0\';hex_buffer[63] = (time_move % 100)/10+\'0\';hex_buffer[64] = (time_move % 10)+\'0\';;hex_buffer[65] = \'!\';hex_buffer[66] = \'#\';hex_buffer[67] = \'0\';hex_buffer[68] = \'0\';hex_buffer[69] = \'4\';hex_buffer[70] = \'P\';hex_buffer[71] = ((theta5*11.1+500)/1000)+\'0\';hex_buffer[72] = ((int)(theta5*11.1+500) % 1000)/100+\'0\';hex_buffer[73] = ((int)(theta5*11.1+500) % 100)/10+\'0\';hex_buffer[74] = ((int)(theta5*11.1+500) % 10)+\'0\';;hex_buffer[75] = \'T\';hex_buffer[76] = (time_move/1000)+\'0\';hex_buffer[77] = (time_move % 1000)/100+\'0\';hex_buffer[78] = (time_move % 100)/10+\'0\';hex_buffer[79] = (time_move % 10)+\'0\';;hex_buffer[80] = \'!\';hex_buffer[81] = \'#\';hex_buffer[82] = \'0\';hex_buffer[83] = \'0\';hex_buffer[84] = \'5\';hex_buffer[85] = \'P\';hex_buffer[86] = ((theta6*11.1+500)/1000)+\'0\';hex_buffer[87] = ((int)(theta6*11.1+500) % 1000)/100+\'0\';hex_buffer[88] = ((int)(theta6*11.1+500) % 100)/10+\'0\';hex_buffer[89] = ((int)(theta6*11.1+500) % 10)+\'0\';;hex_buffer[90] = \'T\';hex_buffer[91] = (time_move/1000)+\'0\';hex_buffer[92] = (time_move % 1000)/100+\'0\';hex_buffer[93] = (time_move % 100)/10+\'0\';hex_buffer[94] = (time_move % 10)+\'0\';;hex_buffer[95] = \'!\';hex_buffer[96] = \'}\';hex_buffer[97] = \'\\0\';}

Jetson Nano >>> stm32中控

  • Jetson Nano数据发送

  • 将所有要发送的数据整合到一起发送

    • # 局外白棋 send_data_str = \"aaab%03d%03d\" % (global_white_outside[0],global_white_outside[1]) # 局外黑棋 send_data_str += \"%03d%03d\" % (global_black_outside[0],global_black_outside[1]) # 棋局状态 send_data_str += \"%c%c%c%c%c%c%c%c%c\" % (global_game_ststus[0],global_game_ststus[1],global_game_ststus[2],global_game_ststus[3],global_game_ststus[4] ,global_game_ststus[5],global_game_ststus[6],global_game_ststus[7],global_game_ststus[8])  # 棋盘九个中心点坐标 send_data_str += \"%03d%03d\" % (global_board_center[0],global_board_center[1]) # 1 send_data_str += \"%03d%03d\" % (global_board_center[2],global_board_center[3]) # 2 send_data_str += \"%03d%03d\" % (global_board_center[4],global_board_center[5]) # 3 send_data_str += \"%03d%03d\" % (global_board_center[6],global_board_center[7]) # 4 send_data_str += \"%03d%03d\" % (global_board_center[8],global_board_center[9]) # 5 send_data_str += \"%03d%03d\" % (global_board_center[10],global_board_center[11]) # 6 send_data_str += \"%03d%03d\" % (global_board_center[12],global_board_center[13]) # 7 send_data_str += \"%03d%03d\" % (global_board_center[14],global_board_center[15]) # 8 send_data_str += \"%03d%03dzzz\" % (global_board_center[16],global_board_center[17]) # 9 
  • stm32中控根据发送的格式设定接收函数

    • // 局外黑白棋子坐标int global_white_outside[2] = {0};int global_black_outside[2] = {0};// 棋局状态char global_game_ststus[9] = {\" \"};// 棋盘九个中心点坐标int global_board_center[9][2] = {0};// 棋盘索引char class_board_index[3][3] = {0,1,2,3,4,5,6,7,8};/*-USART1===================================================================*/char RX_buffer[90] = {0}; //数据储存char RX_buffer_IT; // 接收缓冲int Rx_CNT = 0; //数组下标/***********串口中断回调**************/void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){if(huart->Instance == USART1){if(Rx_CNT < 100){RX_buffer[Rx_CNT++] = RX_buffer_IT;if(RX_buffer[Rx_CNT-1] == \'z\'){Rx_CNT = 0;int i = 0;while(RX_buffer[i] != \'b\')i++;global_white_outside[0] = (RX_buffer[i+1]-\'0\')*100 + (RX_buffer[i+2]-\'0\')*10 + (RX_buffer[i+3]-\'0\');global_white_outside[1] = (RX_buffer[i+4]-\'0\')*100 + (RX_buffer[i+5]-\'0\')*10 + (RX_buffer[i+6]-\'0\');global_black_outside[0] = (RX_buffer[i+7]-\'0\')*100 + (RX_buffer[i+8]-\'0\')*10 + (RX_buffer[i+9]-\'0\');global_black_outside[1] = (RX_buffer[i+10]-\'0\')*100 + (RX_buffer[i+11]-\'0\')*10 + (RX_buffer[i+12]-\'0\');global_game_ststus[0] = RX_buffer[i+13];global_game_ststus[1] = RX_buffer[i+14];global_game_ststus[2] = RX_buffer[i+15];global_game_ststus[3] = RX_buffer[i+16];global_game_ststus[4] = RX_buffer[i+17];global_game_ststus[5] = RX_buffer[i+18];global_game_ststus[6] = RX_buffer[i+19];global_game_ststus[7] = RX_buffer[i+20];global_game_ststus[8] = RX_buffer[i+21];global_board_center[0][0] = (RX_buffer[i+22]-\'0\')*100 + (RX_buffer[i+23]-\'0\')*10 + (RX_buffer[i+24]-\'0\');global_board_center[0][1] = (RX_buffer[i+25]-\'0\')*100 + (RX_buffer[i+26]-\'0\')*10 + (RX_buffer[i+27]-\'0\');global_board_center[1][0] = (RX_buffer[i+28]-\'0\')*100 + (RX_buffer[i+29]-\'0\')*10 + (RX_buffer[i+30]-\'0\');global_board_center[1][1] = (RX_buffer[i+31]-\'0\')*100 + (RX_buffer[i+32]-\'0\')*10 + (RX_buffer[i+33]-\'0\');global_board_center[2][0] = (RX_buffer[i+34]-\'0\')*100 + (RX_buffer[i+35]-\'0\')*10 + (RX_buffer[i+36]-\'0\');global_board_center[2][1] = (RX_buffer[i+37]-\'0\')*100 + (RX_buffer[i+38]-\'0\')*10 + (RX_buffer[i+39]-\'0\');global_board_center[3][0] = (RX_buffer[i+40]-\'0\')*100 + (RX_buffer[i+41]-\'0\')*10 + (RX_buffer[i+42]-\'0\');global_board_center[3][1] = (RX_buffer[i+43]-\'0\')*100 + (RX_buffer[i+44]-\'0\')*10 + (RX_buffer[i+45]-\'0\');global_board_center[4][0] = (RX_buffer[i+46]-\'0\')*100 + (RX_buffer[i+47]-\'0\')*10 + (RX_buffer[i+48]-\'0\');global_board_center[4][1] = (RX_buffer[i+49]-\'0\')*100 + (RX_buffer[i+50]-\'0\')*10 + (RX_buffer[i+51]-\'0\');global_board_center[5][0] = (RX_buffer[i+52]-\'0\')*100 + (RX_buffer[i+53]-\'0\')*10 + (RX_buffer[i+54]-\'0\');global_board_center[5][1] = (RX_buffer[i+55]-\'0\')*100 + (RX_buffer[i+56]-\'0\')*10 + (RX_buffer[i+57]-\'0\');global_board_center[6][0] = (RX_buffer[i+58]-\'0\')*100 + (RX_buffer[i+59]-\'0\')*10 + (RX_buffer[i+60]-\'0\');global_board_center[6][1] = (RX_buffer[i+61]-\'0\')*100 + (RX_buffer[i+62]-\'0\')*10 + (RX_buffer[i+63]-\'0\');global_board_center[7][0] = (RX_buffer[i+64]-\'0\')*100 + (RX_buffer[i+65]-\'0\')*10 + (RX_buffer[i+66]-\'0\');global_board_center[7][1] = (RX_buffer[i+67]-\'0\')*100 + (RX_buffer[i+68]-\'0\')*10 + (RX_buffer[i+69]-\'0\');global_board_center[8][0] = (RX_buffer[i+70]-\'0\')*100 + (RX_buffer[i+71]-\'0\')*10 + (RX_buffer[i+72]-\'0\');global_board_center[8][1] = (RX_buffer[i+73]-\'0\')*100 + (RX_buffer[i+74]-\'0\')*10 + (RX_buffer[i+75]-\'0\');}}elseRx_CNT = 0;HAL_UART_Receive_IT(&huart1, (uint8_t *)&RX_buffer_IT, 1);}}

6.视觉程序设计

识别内容

  • 黑白棋子共十个的圆心坐标

  • 九宫格棋盘的旋转角度(棋盘中心固定,已知旋转角度和棋盘尺寸即可计算九个格的中心坐标)

  • # ------------圆形检测----------------------------------------------------------------------------------------------------------------------------------- blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 找到圆形 -param2:圆形的相似度阈值 circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, dp=1.1, minDist=30, param1=50, param2=40, minRadius=MIN_RADIUS, maxRadius=MAX_RADIUS) global_circles_count = 0 if circles is not None: goable_circle_centers_black.clear() # 清空数组 goable_circle_centers_white.clear() # 清空数组 global_circles_count=len(circles[0,:]) # 获得圆形个数 circles = np.round(circles[0, :]).astype(\"int\") for (x, y, r) in circles: color = get_color(frame[y, x][0], frame[y, x][1], frame[y, x][2]) cv2.circle(frame, (x, y), r, (0, 255, 0), 4) cv2.putText(frame, color, (x - r, y - r), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) # 将中心坐标添加到列表中 if(color == \'B\'): goable_circle_centers_black.append((x, y)) global_black_outside = (0,0) if(color == \'W\'): goable_circle_centers_white.append((x, y)) global_white_outside = (0,0) global_game_ststus = [\' \',\' \',\' \',\' \',\' \',\' \',\' \',\' \',\' \'] # 找到最下面的边界外的棋子 for (x,y) in goable_circle_centers_black: # 黑 if(x<Left_boundary or x > Right_boundary): # 如果在边界外  if(y>global_black_outside[1]): # 如果是下面的 global_black_outside = (x,y) else:  i=0  while(i<9): if(calculate_distance((global_board_center[(i*2)],global_board_center[(i*2)+1]),(x,y)) < 50): global_game_ststus[i] = \'B\' i+=1 for (x,y) in goable_circle_centers_white: # 白 if(x<Left_boundary or x > Right_boundary): # 如果在边界外  if(y>global_white_outside[1]): # 如果是下面的 global_white_outside = (x,y) else:  i=0  while(i<9): if(calculate_distance((global_board_center[(i*2)],global_board_center[(i*2)+1]),(x,y)) < 20): global_game_ststus[i] = \'W\' i+=1 # print(\'black\',len(circles)) # print(\'white\',goable_circle_centers_white)# ------线段检测---------------------------------------------------------------------------------------------------------------------------------------- # 找线段 edges = cv2.Canny(gray, 80, 120, apertureSize=3) lines = cv2.HoughLines(edges, 1, np.pi / 180, 130) # pr_4 ++ if lines is not None: for line in lines: rho, theta = line[0] degree = np.degrees(theta) if min_degree <= degree <= max_degree: a = np.cos(theta) b = np.sin(theta) x0 = a * rho y0 = b * rho x1 = int(x0 + 1000 * (-b)) y1 = int(y0 + 1000 * (a)) x2 = int(x0 - 1000 * (-b)) y2 = int(y0 - 1000 * (a)) cv2.line(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) global_angle = degree % 180 if global_angle < 90:  global_angle = 90 + global_angle # print(global_angle) # 绘制旋转的九宫格并标注十字,同时不显示内部四条线,并且储存并打印九个中心点坐标,给每个格子标上序号 draw_rotating_grid(frame, (global_width_mid, global_height_mid), 91, global_angle)

数据计算

  1. 九个格中心点的坐标:根据棋盘中心点坐标、棋盘旋转角度和棋盘尺寸,计算九个格中心点的坐标

  2. 棋盘状态:根据棋子的圆心坐标和棋盘格中心点的坐标,计算两点间的距离判断棋子是否在棋盘格内

    def calculate_distance(point1, point2): \"\"\" 计算两点之间的距离。 参数: point1 (tuple): 第一个点的坐标,格式为 (x1, y1)。 point2 (tuple): 第二个点的坐标,格式为 (x2, y2)。 返回: float: 两点之间的距离。 \"\"\" x1, y1 = point1 x2, y2 = point2 distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) return distancedef draw_rotating_grid(frame, center, size, angle): \"\"\" 在图像上绘制旋转的九宫格并在格子中心标注十字,同时不显示内部四条线,并且储存并打印九个中心点坐标,给每个格子标上序号 :param frame: 图像帧 :param center: 九宫格中心点坐标 (center_x, center_y) :param size: 九宫格中每个小方格的边长 :param angle: 旋转角度,单位为度 \"\"\" def calculate_rotated_point(x, y, cx, cy, cos_angle, sin_angle): temp_x = x - cx temp_y = y - cy new_x = temp_x * cos_angle - temp_y * sin_angle + cx new_y = temp_x * sin_angle + temp_y * cos_angle + cy return int(new_x), int(new_y) def calculate_grid_corners(center, size, angle): half_size = size * 1.5 # 因为 3x3 格子有 3 个边长 angle_rad = math.radians(angle) cos_angle = math.cos(angle_rad) sin_angle = math.sin(angle_rad) points = [] for i in range(4): for j in range(4): x = center[0] + (i - 1.5) * size y = center[1] + (j - 1.5) * size rotated_point = calculate_rotated_point(x, y, center[0], center[1], cos_angle, sin_angle) points.append(rotated_point) return points # 计算旋转后的九宫格顶点 points = calculate_grid_corners(center, size, angle) # 绘制九宫格的外部线条 # 画上下两条外部线 cv2.line(frame, points[0], points[3], (0, 255, 0), 2) cv2.line(frame, points[12], points[15], (0, 255, 0), 2) # 画左右两条外部线 cv2.line(frame, points[0], points[12], (0, 255, 0), 2) cv2.line(frame, points[15], points[3], (0, 255, 0), 2) # 计算并储存每个格子的中心坐标 grid_centers = [] for i in range(3): for j in range(3): # 计算格子中心坐标(这里的计算基于九宫格的布局规律) grid_center_x = center[0] + (i - 1) * size grid_center_y = center[1] + (j - 1) * size angle_rad = math.radians(angle) cos_angle = math.cos(angle_rad) sin_angle = math.sin(angle_rad) # 计算旋转后的格子中心坐标 rotated_grid_center_x, rotated_grid_center_y = calculate_rotated_point(grid_center_x, grid_center_y, center[0], center[1], cos_angle, sin_angle) grid_centers.append((rotated_grid_center_x, rotated_grid_center_y)) # 打印九个中心点坐标 global global_board_center for i, center_coord in enumerate(grid_centers): if(i == 0): global_board_center[0] = center_coord[0] global_board_center[1] = center_coord[1] elif(i == 1): global_board_center[2] = center_coord[0] global_board_center[3] = center_coord[1] elif(i == 2): global_board_center[4] = center_coord[0] global_board_center[5] = center_coord[1] elif(i == 3): global_board_center[6] = center_coord[0] global_board_center[7] = center_coord[1] elif(i == 4): global_board_center[8] = center_coord[0] global_board_center[9] = center_coord[1] elif(i == 5): global_board_center[10] = center_coord[0] global_board_center[11] = center_coord[1] elif(i == 6): global_board_center[12] = center_coord[0] global_board_center[13] = center_coord[1] elif(i == 7): global_board_center[14] = center_coord[0] global_board_center[15] = center_coord[1] elif(i == 8): global_board_center[16] = center_coord[0] global_board_center[17] = center_coord[1] # print(f\"格子 {i + 1} 的中心点坐标: {center_coord}\") # 绘制十字并标注序号 for i, center_coord in enumerate(grid_centers): cross_size = int(size / 8) cv2.line(frame, (center_coord[0] - cross_size, center_coord[1]),  (center_coord[0] + cross_size, center_coord[1]), (255, 0, 0), 1) cv2.line(frame, (center_coord[0], center_coord[1] - cross_size),  (center_coord[0], center_coord[1] + cross_size), (255, 0, 0), 1) # 在格子中心标注序号 cv2.putText(frame, str(i + 1), (center_coord[0] - 5, center_coord[1] + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)

数据发送

  • 棋盘外最下方的黑棋子坐标和白棋子的坐标

  • 棋盘九个格的状态(‘X’-黑子,‘O’-白子,’ \'-空)

  • 九宫格九个中心点的位置坐标

    # 局外白棋 send_data_str = \"aaab%03d%03d\" % (global_white_outside[0],global_white_outside[1]) # 局外黑棋 send_data_str += \"%03d%03d\" % (global_black_outside[0],global_black_outside[1]) # 棋局状态 send_data_str += \"%c%c%c%c%c%c%c%c%c\" % (global_game_ststus[0],global_game_ststus[1],global_game_ststus[2],global_game_ststus[3],global_game_ststus[4] ,global_game_ststus[5],global_game_ststus[6],global_game_ststus[7],global_game_ststus[8])  # 棋盘九个中心点坐标 send_data_str += \"%03d%03d\" % (global_board_center[0],global_board_center[1]) # 1 send_data_str += \"%03d%03d\" % (global_board_center[2],global_board_center[3]) # 2 send_data_str += \"%03d%03d\" % (global_board_center[4],global_board_center[5]) # 3 send_data_str += \"%03d%03d\" % (global_board_center[6],global_board_center[7]) # 4 send_data_str += \"%03d%03d\" % (global_board_center[8],global_board_center[9]) # 5 send_data_str += \"%03d%03d\" % (global_board_center[10],global_board_center[11]) # 6 send_data_str += \"%03d%03d\" % (global_board_center[12],global_board_center[13]) # 7 send_data_str += \"%03d%03d\" % (global_board_center[14],global_board_center[15]) # 8 send_data_str += \"%03d%03dzzz\" % (global_board_center[16],global_board_center[17]) # 9  # print(send_data_str,\'\\n\') send_LvBo_1.append((send_data_str,global_circles_count)) num_lvbo = 0 if(len(send_LvBo_1)>20): for data1,num in send_LvBo_1: if(num>num_lvbo): num_lvbo = num send_data_str = data1 send_LvBo_1.clear() print(send_data_str,\'\\n\') ser.write(send_data_str.encode()) # 串口发送

数据过滤

在识别过程中经常会出现某一帧识别不到或不准,大部分时间识别是准确的,但是如果将识别不准的这一帧数据发送出去,将会造成许多未知的错误

为此,我将数据做以下处理

对识别到的圆形的坐标进行储存,每储存15帧便进行检查,将识别到圆形个数最多的一组作为最终数据

对识别到的每条线的角度进行储存,将储存的数进行排序,取中间的数值作为最终数据

blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 找到圆形 -param2:圆形的相似度阈值 circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, dp=1.1, minDist=30, param1=50, param2=40, minRadius=MIN_RADIUS, maxRadius=MAX_RADIUS) global_circles_count = 0 if circles is not None: goable_circle_centers_black.clear() # 清空数组 goable_circle_centers_white.clear() # 清空数组 global_circles_count=len(circles[0,:]) # 获得圆形个数 circles = np.round(circles[0, :]).astype(\"int\") for (x, y, r) in circles: color = get_color(frame[y, x][0], frame[y, x][1], frame[y, x][2]) cv2.circle(frame, (x, y), r, (0, 255, 0), 4) cv2.putText(frame, color, (x - r, y - r), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) # 将中心坐标添加到列表中 if(color == \'B\'): goable_circle_centers_black.append((x, y)) global_black_outside = (0,0) if(color == \'W\'): goable_circle_centers_white.append((x, y)) global_white_outside = (0,0) global_game_ststus = [\' \',\' \',\' \',\' \',\' \',\' \',\' \',\' \',\' \'] # 找到最下面的边界外的棋子 for (x,y) in goable_circle_centers_black: # 黑 if(x<Left_boundary or x > Right_boundary): # 如果在边界外  if(y>global_black_outside[1]): # 如果是下面的 global_black_outside = (x,y) else:  i=0  while(i<9): if(calculate_distance((global_board_center[(i*2)],global_board_center[(i*2)+1]),(x,y)) < 50): global_game_ststus[i] = \'B\' i+=1 for (x,y) in goable_circle_centers_white: # 白 if(x<Left_boundary or x > Right_boundary): # 如果在边界外  if(y>global_white_outside[1]): # 如果是下面的 global_white_outside = (x,y) else:  i=0  while(i<9): if(calculate_distance((global_board_center[(i*2)],global_board_center[(i*2)+1]),(x,y)) < 20): global_game_ststus[i] = \'W\' i+=1 # print(\'black\',len(circles)) # print(\'white\',goable_circle_centers_white)
 send_LvBo_1.append((send_data_str,global_circles_count)) num_lvbo = 0 if(len(send_LvBo_1)>20): for data1,num in send_LvBo_1: if(num>num_lvbo): num_lvbo = num send_data_str = data1 send_LvBo_1.clear() print(send_data_str,\'\\n\') ser.write(send_data_str.encode()) # 串口发送

全部代码

import cv2import numpy as npimport mathimport serialser = serial.Serial(\"/dev/ttyTHS1\",115200,timeout=0.01)print(\"serial Open\")# 全局变量定义=========================================================global_angle = 0global_angle_lvbo=[]width = 0global_width_mid = (width//2)height = 0global_height_mid = (height//2)send_LvBo_1 = []# 局外黑白棋子global_white_outside = (0,0)global_black_outside = (0,0)# 棋局状态global_game_ststus = [\' \',\' \',\' \',\' \',\' \',\' \',\' \',\' \',\' \']# 棋局九个中心坐标 global_board_center = [0,0,0,0,0,0, 0,0,0,0,0,0, 0,0,0,0,0,0]# 圆形坐标goable_circle_centers_black = []goable_circle_centers_white = []global_circles_count = 0# ---------------------------------------------------------------------def calculate_distance(point1, point2): \"\"\" 计算两点之间的距离。 参数: point1 (tuple): 第一个点的坐标,格式为 (x1, y1)。 point2 (tuple): 第二个点的坐标,格式为 (x2, y2)。 返回: float: 两点之间的距离。 \"\"\" x1, y1 = point1 x2, y2 = point2 distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) return distancedef draw_rotating_grid(frame, center, size, angle): \"\"\" 在图像上绘制旋转的九宫格并在格子中心标注十字,同时不显示内部四条线,并且储存并打印九个中心点坐标,给每个格子标上序号 :param frame: 图像帧 :param center: 九宫格中心点坐标 (center_x, center_y) :param size: 九宫格中每个小方格的边长 :param angle: 旋转角度,单位为度 \"\"\" def calculate_rotated_point(x, y, cx, cy, cos_angle, sin_angle): temp_x = x - cx temp_y = y - cy new_x = temp_x * cos_angle - temp_y * sin_angle + cx new_y = temp_x * sin_angle + temp_y * cos_angle + cy return int(new_x), int(new_y) def calculate_grid_corners(center, size, angle): half_size = size * 1.5 # 因为 3x3 格子有 3 个边长 angle_rad = math.radians(angle) cos_angle = math.cos(angle_rad) sin_angle = math.sin(angle_rad) points = [] for i in range(4): for j in range(4): x = center[0] + (i - 1.5) * size y = center[1] + (j - 1.5) * size rotated_point = calculate_rotated_point(x, y, center[0], center[1], cos_angle, sin_angle) points.append(rotated_point) return points # 计算旋转后的九宫格顶点 points = calculate_grid_corners(center, size, angle) # 绘制九宫格的外部线条 # 画上下两条外部线 cv2.line(frame, points[0], points[3], (0, 255, 0), 2) cv2.line(frame, points[12], points[15], (0, 255, 0), 2) # 画左右两条外部线 cv2.line(frame, points[0], points[12], (0, 255, 0), 2) cv2.line(frame, points[15], points[3], (0, 255, 0), 2) # 计算并储存每个格子的中心坐标 grid_centers = [] for i in range(3): for j in range(3): # 计算格子中心坐标(这里的计算基于九宫格的布局规律) grid_center_x = center[0] + (i - 1) * size grid_center_y = center[1] + (j - 1) * size angle_rad = math.radians(angle) cos_angle = math.cos(angle_rad) sin_angle = math.sin(angle_rad) # 计算旋转后的格子中心坐标 rotated_grid_center_x, rotated_grid_center_y = calculate_rotated_point(grid_center_x, grid_center_y, center[0], center[1], cos_angle, sin_angle) grid_centers.append((rotated_grid_center_x, rotated_grid_center_y)) # 打印九个中心点坐标 global global_board_center for i, center_coord in enumerate(grid_centers): if(i == 0): global_board_center[0] = center_coord[0] global_board_center[1] = center_coord[1] elif(i == 1): global_board_center[2] = center_coord[0] global_board_center[3] = center_coord[1] elif(i == 2): global_board_center[4] = center_coord[0] global_board_center[5] = center_coord[1] elif(i == 3): global_board_center[6] = center_coord[0] global_board_center[7] = center_coord[1] elif(i == 4): global_board_center[8] = center_coord[0] global_board_center[9] = center_coord[1] elif(i == 5): global_board_center[10] = center_coord[0] global_board_center[11] = center_coord[1] elif(i == 6): global_board_center[12] = center_coord[0] global_board_center[13] = center_coord[1] elif(i == 7): global_board_center[14] = center_coord[0] global_board_center[15] = center_coord[1] elif(i == 8): global_board_center[16] = center_coord[0] global_board_center[17] = center_coord[1] # print(f\"格子 {i + 1} 的中心点坐标: {center_coord}\") # 绘制十字并标注序号 for i, center_coord in enumerate(grid_centers): cross_size = int(size / 8) cv2.line(frame, (center_coord[0] - cross_size, center_coord[1]),  (center_coord[0] + cross_size, center_coord[1]), (255, 0, 0), 1) cv2.line(frame, (center_coord[0], center_coord[1] - cross_size),  (center_coord[0], center_coord[1] + cross_size), (255, 0, 0), 1) # 在格子中心标注序号 cv2.putText(frame, str(i + 1), (center_coord[0] - 5, center_coord[1] + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------data_send = \"123\"# ser.write(data_send.encode())# 打开摄像头cap = cv2.VideoCapture(0)# 定义一些常量enable_lens_corr = False # 打开以获得更直的线条min_degree = 2max_degree = 179MIN_RADIUS = 25 # 圆形最小半径MAX_RADIUS = 50 # 圆形最大半径# 镜头畸变校正def correct_lens_distortion(image): h, w = image.shape[:2] k = np.array([[w, 0, w / 2],  [0, h, h / 2],  [0, 0, 1]], dtype=np.float32) dist = np.zeros((5,), dtype=np.float32) corrected_image = cv2.undistort(image, k, dist) return corrected_imagedef get_color(b, g, r): # 定义颜色阈值 white_threshold = (150, 150, 150) black_threshold =(104, 86, 121) # 判断颜色 if r > white_threshold[2] and g > white_threshold[1] and b > white_threshold[0]: return \"W\" elif r < black_threshold[2] and g < black_threshold[1] and b < black_threshold[0]: return \"B\" else: return \"?\"# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------# ----------------------------------------------------------------------------------------------------------------------------------------------while True: # ser.write(data_send.encode()) ret, img1 = cap.read() if not ret: break frame = cv2.flip(img1,-1) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) if enable_lens_corr: gray = correct_lens_distortion(gray) # 对图像进行镜头畸变校正# ---------边界计算------------------------------------------------------------------------------------------------------------------------------------- # 获取图像的高度和宽度 height, width = frame.shape[:2] global_width_mid = (width//2)+11 global_height_mid = (height//2) cless_width = 200 # 棋盘左右边界 用于区分棋盘内外棋子 Left_boundary = global_width_mid-cless_width Right_boundary = global_width_mid+cless_width cv2.line(frame, (Left_boundary,0), (Left_boundary,height), (0, 255, 255), thickness=2) # 黄色 cv2.line(frame, (Right_boundary,0), (Right_boundary,height), (255,0, 255), thickness=2) # 紫色# ------------圆形检测----------------------------------------------------------------------------------------------------------------------------------- blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 找到圆形 -param2:圆形的相似度阈值 circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, dp=1.1, minDist=30, param1=50, param2=40, minRadius=MIN_RADIUS, maxRadius=MAX_RADIUS) global_circles_count = 0 if circles is not None: goable_circle_centers_black.clear() # 清空数组 goable_circle_centers_white.clear() # 清空数组 global_circles_count=len(circles[0,:]) # 获得圆形个数 circles = np.round(circles[0, :]).astype(\"int\") for (x, y, r) in circles: color = get_color(frame[y, x][0], frame[y, x][1], frame[y, x][2]) cv2.circle(frame, (x, y), r, (0, 255, 0), 4) cv2.putText(frame, color, (x - r, y - r), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) # 将中心坐标添加到列表中 if(color == \'B\'): goable_circle_centers_black.append((x, y)) global_black_outside = (0,0) if(color == \'W\'): goable_circle_centers_white.append((x, y)) global_white_outside = (0,0) global_game_ststus = [\' \',\' \',\' \',\' \',\' \',\' \',\' \',\' \',\' \'] # 找到最下面的边界外的棋子 for (x,y) in goable_circle_centers_black: # 黑 if(x<Left_boundary or x > Right_boundary): # 如果在边界外  if(y>global_black_outside[1]): # 如果是下面的 global_black_outside = (x,y) else:  i=0  while(i<9): if(calculate_distance((global_board_center[(i*2)],global_board_center[(i*2)+1]),(x,y)) < 50): global_game_ststus[i] = \'B\' i+=1 for (x,y) in goable_circle_centers_white: # 白 if(x<Left_boundary or x > Right_boundary): # 如果在边界外  if(y>global_white_outside[1]): # 如果是下面的 global_white_outside = (x,y) else:  i=0  while(i<9): if(calculate_distance((global_board_center[(i*2)],global_board_center[(i*2)+1]),(x,y)) < 20): global_game_ststus[i] = \'W\' i+=1 # print(\'black\',len(circles)) # print(\'white\',goable_circle_centers_white)# ------线段检测---------------------------------------------------------------------------------------------------------------------------------------- # 找线段 edges = cv2.Canny(gray, 80, 120, apertureSize=3) lines = cv2.HoughLines(edges, 1, np.pi / 180, 130) # pr_4 ++ if lines is not None: for line in lines: rho, theta = line[0] degree = np.degrees(theta) if min_degree <= degree <= max_degree: a = np.cos(theta) b = np.sin(theta) x0 = a * rho y0 = b * rho x1 = int(x0 + 1000 * (-b)) y1 = int(y0 + 1000 * (a)) x2 = int(x0 - 1000 * (-b)) y2 = int(y0 - 1000 * (a)) cv2.line(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) global_angle = degree % 180 if global_angle < 90:  global_angle = 90 + global_angle # print(global_angle) # 绘制旋转的九宫格并标注十字,同时不显示内部四条线,并且储存并打印九个中心点坐标,给每个格子标上序号 draw_rotating_grid(frame, (global_width_mid, global_height_mid), 91, global_angle) ## cv2.circle(frame,(50,50),60,(255,0,0),2) # 局外白棋 send_data_str = \"aaab%03d%03d\" % (global_white_outside[0],global_white_outside[1]) # 局外黑棋 send_data_str += \"%03d%03d\" % (global_black_outside[0],global_black_outside[1]) # 棋局状态 send_data_str += \"%c%c%c%c%c%c%c%c%c\" % (global_game_ststus[0],global_game_ststus[1],global_game_ststus[2],global_game_ststus[3],global_game_ststus[4] ,global_game_ststus[5],global_game_ststus[6],global_game_ststus[7],global_game_ststus[8])  # 棋盘九个中心点坐标 send_data_str += \"%03d%03d\" % (global_board_center[0],global_board_center[1]) # 1 send_data_str += \"%03d%03d\" % (global_board_center[2],global_board_center[3]) # 2 send_data_str += \"%03d%03d\" % (global_board_center[4],global_board_center[5]) # 3 send_data_str += \"%03d%03d\" % (global_board_center[6],global_board_center[7]) # 4 send_data_str += \"%03d%03d\" % (global_board_center[8],global_board_center[9]) # 5 send_data_str += \"%03d%03d\" % (global_board_center[10],global_board_center[11]) # 6 send_data_str += \"%03d%03d\" % (global_board_center[12],global_board_center[13]) # 7 send_data_str += \"%03d%03d\" % (global_board_center[14],global_board_center[15]) # 8 send_data_str += \"%03d%03dzzz\" % (global_board_center[16],global_board_center[17]) # 9  # print(send_data_str,\'\\n\') send_LvBo_1.append((send_data_str,global_circles_count)) num_lvbo = 0 if(len(send_LvBo_1)>20): for data1,num in send_LvBo_1: if(num>num_lvbo): num_lvbo = num send_data_str = data1 send_LvBo_1.clear() print(send_data_str,\'\\n\') ser.write(send_data_str.encode()) # 串口发送 cv2.imshow(\'Frame\', frame) if cv2.waitKey(1) & 0xFF == ord(\'q\'): breakcap.release()cv2.destroyAllWindows()

7.主控程序设计

OLED屏幕显示

  1. 主菜单

    void Oled_menu(void){char lode_text[40] = {0};sprintf(lode_text,\"---MENU--- \");OLED_ShowString(0,8,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"K1:Topic_1 \");OLED_ShowString(0,16,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"K2:Topic_2/3 \");OLED_ShowString(0,24,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"K3:Topic_4 \");OLED_ShowString(0,32,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"K4:Topic_5/6 \");OLED_ShowString(0,40,(uint8_t *)lode_text,8,1);sprintf(lode_text,\" \");OLED_ShowString(0,48,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"key: %02d %02d %02d %02d %02d\",Key_Down_Num[0],Key_Down_Num[1],Key_Down_Num[2],Key_Down_Num[3],Key_Down_Num[4]);OLED_ShowString(0,56,(uint8_t *)lode_text,8,1);OLED_Refresh();}
  2. 第一题界面

    void Oled_Topic_1(void){char lode_text[40] = {0};sprintf(lode_text,\"black_out:(%d,%d)\",coordinate_transformat_x(global_black_outside[0]),coordinate_transformat_y(global_black_outside[1]));OLED_ShowString(0,8,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"k1: ----- \");OLED_ShowString(0,16,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"k2: OK \");OLED_ShowString(0,24,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"k3: ----- \");OLED_ShowString(0,32,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"k4:break \");OLED_ShowString(0,40,(uint8_t *)lode_text,8,1);sprintf(lode_text,\"key: %02d %02d %02d %02d %02d\",Key_Down_Num[0],Key_Down_Num[1],Key_Down_Num[2],Key_Down_Num[3],Key_Down_Num[4]);OLED_ShowString(0,56,(uint8_t *)lode_text,8,1);OLED_Refresh();}
  3. 第二、三题界面

    void Oled_Topic_2(void){ char lode_text[40] = {0};sprintf(lode_text,\"choose_xy:(%d,%d) \",coordinate_transformat_x(global_board_center[class_board_index[x_chose][y_chose]][0]+5),coordinate_transformat_y(global_board_center[class_board_index[x_chose][y_chose]][1])-10);OLED_ShowString(0,8,(uint8_t *)lode_text,8,1); sprintf(lode_text, \"%c1%c1%c %c1%c1%c k1:%s \", chessBoard_chose[0][0], chessBoard_chose[0][1], chessBoard_chose[0][2], global_game_ststus[0], global_game_ststus[1], global_game_ststus[2], point_key0_text); OLED_ShowString(0, 16, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"----- ----- k2:%s \", point_key1_text); OLED_ShowString(0, 24, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"%c1%c1%c %c1%c1%c k3:%s \", chessBoard_chose[1][0], chessBoard_chose[1][1], chessBoard_chose[1][2], global_game_ststus[3], global_game_ststus[4], global_game_ststus[5], point_key2_text); OLED_ShowString(0, 32, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"----- ----- k4:%s \", point_key3_text); OLED_ShowString(0, 40, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"%c1%c1%c %c1%c1%c \", chessBoard_chose[2][0], chessBoard_chose[2][1], chessBoard_chose[2][2], global_game_ststus[6], global_game_ststus[7], global_game_ststus[8]); OLED_ShowString(0, 48, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"%s \", point_text); OLED_ShowString(0, 56, (uint8_t *)lode_text, 8, 1); OLED_Refresh();}
  4. 第四、五、六题界面

    void print_board(void){ char lode_text[40] = {0};sprintf(lode_text, \" Player VS Robot \"); OLED_ShowString(0, 8, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"%c1%c1%c k1:%s \", board[0][0], board[0][1], board[0][2], point_key0_text); OLED_ShowString(0, 16, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"----- k2:%s \", point_key1_text); OLED_ShowString(0, 24, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"%c1%c1%c k3:%s \", board[1][0], board[1][1], board[1][2], point_key2_text); OLED_ShowString(0, 32, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"----- k4:%s \", point_key3_text); OLED_ShowString(0, 40, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"%c1%c1%c \", board[2][0], board[2][1], board[2][2]); OLED_ShowString(0, 48, (uint8_t *)lode_text, 8, 1); sprintf(lode_text, \"%s \", point_text); OLED_ShowString(0, 56, (uint8_t *)lode_text, 8, 1); OLED_Refresh();}

棋盘逻辑

没有用minmax算法,采用优先级判断

  1. 判断是否结束
  2. 判断是否有赢的机会
  3. 判断是否有堵的机会
  4. 判断中心方格是否为空
  5. 判断四个角的方格是否为空
  6. 判断四个边的方格是否为空
//将接收到的棋盘信息储存void check_board(void){board[0][0] = global_game_ststus[0];board[0][1] = global_game_ststus[1];board[0][2] = global_game_ststus[2];board[1][0] = global_game_ststus[3];board[1][1] = global_game_ststus[4];board[1][2] = global_game_ststus[5];board[2][0] = global_game_ststus[6];board[2][1] = global_game_ststus[7];board[2][2] = global_game_ststus[8];}// 检查是否平局bool check_draw() {check_board(); for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { if (board[i][j] == \' \') { return false; } } } return true;}// 检查是否有人赢了int check_win(char player) { for (int i = 0; i < BOARD_SIZE; i++) { if (board[i][0] == player && board[i][1] == player && board[i][2] == player) return 1; if (board[0][i] == player && board[1][i] == player && board[2][i] == player) return 1; } if (board[0][0] == player && board[1][1] == player && board[2][2] == player) return 1; if (board[0][2] == player && board[1][1] == player && board[2][0] == player) return 1; return 0;}// 检查是否有堵棋的机会bool block_move(char player) { for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { if (board[i][j] != \'X\' && board[i][j] != \'O\') { char original = board[i][j]; board[i][j] = player; if (check_win(player)) {if(player == \'X\'){//board[i][j] = \'O\';board[i][j] = original; // 恢复棋盘状态robot_move_real(WHITE,i,j);}else{//board[i][j] = \'X\';board[i][j] = original; // 恢复棋盘状态robot_move_real(BLACK,i,j);}  return true; // 有堵棋机会,直接返回 } board[i][j] = original; // 恢复棋盘状态 } } } return false;}// 检查是否有赢棋的机会bool block_win_move(char player) { for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { if (board[i][j] == \' \') { char original = board[i][j]; board[i][j] = player; if (check_win(player)) {//board[i][j] = player;robot_move_real(player,i,j);  return true; // 有堵棋机会,直接返回 } board[i][j] = original; // 恢复棋盘状态 } } } return false;}// 机器人下棋void robot_move(char player) { char opponent = (player == WHITE) ? BLACK : WHITE;// 尝试赢棋if (block_win_move(player)) { return; } // 1. 尝试堵棋 if (block_move(opponent)) { return; } // 2. 优先选择中心位置 if (board[1][1] == \' \') { //board[1][1] = player;robot_move_real(player,1,1); return; } // 3. 优先选择角落位置 int corners[4][2] = {{0, 0}, {0, 2}, {2, 0}, {2, 2}}; for (int i = 0; i < 4; i++) { int x = corners[i][0]; int y = corners[i][1]; if (board[x][y] == \' \') { //board[x][y] = player;robot_move_real(player,x,y); return; } } // 4. 选择第一个空位置int corners2[4][2] = {{0, 1}, {2, 1}, {1, 0}, {1, 2}}; for (int i = 0; i < 4; i++) { int x = corners2[i][0]; int y = corners2[i][1]; if (board[x][y] == \' \') { //board[x][y] = player;robot_move_real(player,x,y); return; } }}

机械臂角度计算

// ----逆运动学计算---------------------------------------------------#include #include #define PI 3.141#define L1 105.0#define L2 98.0#define L3 150.0//底座 ... 夹爪 double theta1 = 90, theta2 = 90, theta3 = 90, theta4 = 90, theta5 = 0, theta6 = 50;// xyz坐标+末端位姿 alphaint x1 = 0, y1 = 0, z1 = 374, alpha = 90;int time_move = 1000;// 弧度转角度double Rad2Deg(double rad) { return rad * 180 / PI;}// 角度转弧度double Deg2Rad(double deg) { return deg * PI / 180;}// 计算底盘舵机角度,并将坐标转换到二维bool JiSuan(double x, double y, double z, double alpha) { // 计算底盘舵机角度 theta1 = (atan2(y, x) * 180 / PI) - 45; double H = z; // 高度 double D = sqrt(fabs(x * x) + y * y); // 水平距离 return My_Model(D, H, alpha);}// 在二维平面计算角度bool My_Model(double D, double H, double alpha) { alpha = Deg2Rad(alpha); // 转为弧度 double x_alpha = L3 * cos(alpha); double y_alpha = L3 * sin(alpha); double x_theta = D - x_alpha; double y_theta = H - y_alpha; double d_theta = sqrt(x_theta * x_theta + y_theta * y_theta); if (d_theta > (L1 + L2) || d_theta < fabs(L1 - L2)) { // 超过机械臂的最大或最小长度,无法达到该点 //printf(\"无法到达目标位置。\\n\"); return false; } double Theta = acos((L1 * L1 + L2 * L2 - d_theta * d_theta) / (2 * L1 * L2)); theta3 = Rad2Deg(PI * 1.5 - Theta); // 舵机 4 double theta2_1 = acos((L1 * L1 + d_theta * d_theta - L2 * L2) / (2 * L1 * d_theta)); double theta2_2 = acos(x_theta / d_theta); theta2 = Rad2Deg(theta2_1 + theta2_2); // 舵机 5 theta4 = fabs(Rad2Deg(Theta - alpha + theta2_1 + theta2_2) - 90); // 舵机 3 if (!IsSolutionValid(theta1, theta2, theta3, theta4)) { // 解不在有效范围内 //printf(\"计算出的角度无效。\\n\"); return false; } return true;}// 检查解是否在有效范围内bool IsSolutionValid(double theta1, double theta2, double theta3, double theta4) { return (theta1 >= 0 && theta1 <= 180 && theta2 >= 0 && theta2 <= 180 && theta3 >= 0 && theta3 <= 180 && theta4 >= 0 && theta4 <= 180);}

机械臂运动动作

//-夹取-放下动作--------------------------------------------------------------------void arm_JiaQv(int x_input ,int y_input){time_move = 1000; //------误差调整-----------------------------if(x_input<0 && y_input<230) //左下角{x_input-=abs(230-y_input*y_input)*0.00005;y_input+=abs(230-y_input)*0.1;}if(y_input>230 && x_input<0)//左上角{y_input-=5;//(y_input-230)*0.01x_input+=5;}if(y_input>230 && x_input>0)//右上角{y_input-=(y_input*0.020);}if(x_input>0)//右侧{x_input+=(x_input)*0.25-y_input*0.009;}if(y_input<230 && x_input>0)//右下角{y_input+=(230-y_input)*0.15;x_input+=abs(230-y_input)*0.2;}//xy上方theta6 = 50;x1 = x_input;y1 = y_input;z1 = Z_START;alpha = A_START;if(y_input>230 && x_input>0)z1-=5;JiSuan(x1,y1,z1,alpha);//计算并传值给thetasend_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1000);//xyzx1 = x_input;//-50y1 = y_input;//-55z1 = Z_DOWN-9+abs(x1)*y1*0.0005; //------误差调整-----------------------------if(x_input<-40)//左边{y1-=5;}if(y_input<250 && x_input<-40)//左边{z1-=0;}if(y_input<200 && x_input<-40)//左下角{x1-=15;z1-=7;}if(y_input>220 && x_input<-40)//左上角{x1+=5;}if(y_input<180 && y_input>220 && x_input<-40)//左中部{y1+=4;z1-=5;}if(y_input>220 && x_input<-40)//左上角上部{z1-=2;}if(y_input>230 && x_input>40)//右上角{z1-=2;}if(y_input>260 && x_input>40)//右上角上部{z1+=6;x1-=9;y1-=4;}if(y_input<210 && x_input>40)//右下角{z1+=2;x1-=1;y1-=1;}if(y_input<180 && x_input>40)//右下角下部y1-=5;alpha = A_START;JiSuan(x1,y1,z1,alpha);//计算并传值给thetasend_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1500);//抓上theta6 = 110; send_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1500);x1 = 0;y1 = 170;z1 = Z_START+15;alpha = A_START;JiSuan(x1,y1,z1,alpha);send_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(500);}void arm_FangXia(int x_input ,int y_input){time_move = 1000;//xy上方x1 = x_input;y1 = y_input-10;z1 = Z_START;alpha = A_START;JiSuan(x1,y1,z1,alpha);//计算并传值给thetasend_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1000);//xyzx1 = x_input;y1 = y_input;z1 = Z_DOWN+10;alpha = A_START;JiSuan(x1,y1,z1,alpha);//计算并传值给thetasend_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1000);//放下theta6 = 90; send_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1000);//xy上方x1 = x_input;y1 = y_input;z1 = Z_START;alpha = A_START;JiSuan(x1,y1,z1,alpha);//计算并传值给thetasend_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1000);//初始位置x1 = X_START;y1 = Y_START;z1 = Z_START;alpha = A_START;JiSuan(x1,y1,z1,alpha);//计算并传值给thetasend_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1000);}

第六题思路

int BuGaiZai[2] = {-1,-1}; // 不该在这int YingGaiZai[2] = {-1,-1}; //应该在这void Check_Board_Legal_index(char player,int x_input,int y_input,int golobal_num){char vs_player;if(player == BLACK)vs_player = WHITE;elsevs_player = BLACK;if(Board_Legal[x_input][y_input] != global_game_ststus[golobal_num]){if( (Board_Legal[x_input][y_input] == \' \') && (global_game_ststus[golobal_num] != vs_player) ) // 应该是空,不该在这{BuGaiZai[0] = x_input;BuGaiZai[1] = y_input;}if(Board_Legal[x_input][y_input] == player) // 应该是player,应该在这{YingGaiZai[0] = x_input;YingGaiZai[1] = y_input;}}}int Check_Board_Legal_1(char player){//初始化BuGaiZai[0] = -1;BuGaiZai[1] = -1;YingGaiZai[0] = -1;YingGaiZai[1] = -1;//全部检查Check_Board_Legal_index(player,0,0,0);Check_Board_Legal_index(player,0,1,1);Check_Board_Legal_index(player,0,2,2);Check_Board_Legal_index(player,1,0,3);Check_Board_Legal_index(player,1,1,4);Check_Board_Legal_index(player,1,2,5);Check_Board_Legal_index(player,2,0,6);Check_Board_Legal_index(player,2,1,7);Check_Board_Legal_index(player,2,2,8);if(BuGaiZai[0] != -1 && YingGaiZai[0] != -1){//------误差调整-----------------------------int data_x = 0,data_y = 0;if(BuGaiZai[0] == 0 && BuGaiZai[1] == 1)//中上{data_x = 4;}if(BuGaiZai[0] == 1 && BuGaiZai[1] == 1)//中间{data_x = 5;}if(BuGaiZai[0] == 0 && BuGaiZai[1] == 0)//右上{data_x = 9;}if(BuGaiZai[0] == 1 && BuGaiZai[1] == 0)//右中{data_x = 8;}if(BuGaiZai[0] == 2 && BuGaiZai[1] == 0)//下{data_y = -9;}if(BuGaiZai[0] == 2 && BuGaiZai[1] == 1)//下{data_y = -9;}if(BuGaiZai[0] == 2 && BuGaiZai[1] == 2)//下{data_y = -12;}//抓不该,放应该HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_RESET);arm_JiaQv_Legal(coordinate_transformat_x(global_board_center[class_board_index[BuGaiZai[0]][BuGaiZai[1]]][0]+5)+data_x,coordinate_transformat_y(global_board_center[class_board_index[BuGaiZai[0]][BuGaiZai[1]]][1]+5)+data_y);HAL_Delay(1000);arm_FangXia(coordinate_transformat_x(global_board_center[class_board_index[YingGaiZai[0]][YingGaiZai[1]]][0]+5),coordinate_transformat_y(global_board_center[class_board_index[YingGaiZai[0]][YingGaiZai[1]]][1])-10);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_SET);HAL_Delay(1000);}if(BuGaiZai[0] != -1 && YingGaiZai[0] == -1){//抓不该}if(BuGaiZai[0] == -1 && YingGaiZai[0] != -1){//放应该}return 0;}void arm_JiaQv_Legal(int x_input ,int y_input){time_move = 1000;if(x_input<0 && y_input<230) //左下角{x_input-=abs(230-y_input*y_input)*0.00005;y_input+=abs(230-y_input)*0.1;}if(y_input>230 && x_input<0)//左上角{y_input-=5;//(y_input-230)*0.01x_input+=5;}if(y_input>230 && x_input>0)//右上角{y_input-=(y_input*0.020);}if(x_input>0)//右侧{x_input+=(x_input)*0.25-y_input*0.009;}if(y_input<230 && x_input>0)//右下角{y_input+=(230-y_input)*0.15;x_input+=abs(230-y_input)*0.2;}//xy上方theta6 = 80;x1 = x_input;y1 = y_input;z1 = Z_START;alpha = A_START;if(y_input>230 && x_input>0)z1-=5;JiSuan(x1,y1,z1,alpha);//计算并传值给thetasend_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1000);//xyzx1 = x_input;//-50y1 = y_input;//-55z1 = Z_DOWN-9+abs(x1)*y1*0.0005;if(x_input<-40)//左边{y1-=5;}if(y_input<250 && x_input<-40)//左边{z1-=0;}if(y_input<200 && x_input<-40)//左下角{x1-=15;}if(y_input>220 && x_input<-40)//左上角{x1+=5;}if(y_input>220 && x_input<-40)//左上角上部{z1-=2;}if(y_input>230 && x_input>40)//右上角{z1-=2;}if(y_input>265 && x_input>40)//右上角上部{z1+=6;}if(y_input<210 && x_input>40)//右下角{z1+=2;x1-=1;y1-=1;}z1+=8;alpha = A_START;JiSuan(x1,y1,z1,alpha);//计算并传值给thetasend_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1500);//抓上theta6 = 110; send_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(1500);x1 = 0;y1 = 170;z1 = Z_START+15;alpha = A_START;JiSuan(x1,y1,z1,alpha);send_data_deal(); //转换发送的数据HAL_UART_Transmit(&huart1, (uint8_t*)hex_buffer, 97, 10);//发送数据HAL_Delay(500);}

整体资料:

通过百度网盘分享的文件:2024_E.z…链接:https://pan.baidu.com/s/1FLbzI6jk_ilQh_FO5i9YPw 提取码:6k1z复制这段内容打开「百度网盘APP 即可获取」