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

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

三子棋_人机对弈

在这里插入图片描述

在这里插入图片描述

1.整体设计方案

在这里插入图片描述

2.机械臂系统方案

使用常见的开源六轴自由度stm32机械手臂
在这里插入图片描述

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

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

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

3.视觉系统方案

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

在这里插入图片描述

识别内容:

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

数据发送:

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

4.逆运动学解算

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

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

输入坐标,输出每个轴的角度
在这里插入图片描述

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

计算步骤

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

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

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

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

    • D=∣x2+y2∣D = \sqrt{|x^2 + y^2|}D=x2+y2H=zH = zH=z
  • L3L_3L3xxx 方向和 yyy 方向的分量:

    • xα=L3×cos⁡(αrad)x_{\alpha} = L_3 \times \cos(\alpha_{rad})xα=L3×cos(αrad)yα=L3×sin⁡(αrad)y_{\alpha} = L_3 \times \sin(\alpha_{rad})yα=L3×sin(αrad)
  • 连杆L1L_1L1L2L_2L2xxxyyy 分量:

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

    • dθ=xθ2+yθ2d_{\theta} = \sqrt{x_{\theta}^2 + y_{\theta}^2}dθ=xθ2+yθ2
  • 连杆L1L_1L1L2L_2L2之间的夹角 Θ\ThetaΘ

    • Θ=arccos⁡(L12+L22−dθ22×L1×L2)\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Θ​​ (转换为角度)
  • 角度θ21\theta_{2_1}θ21θ22\theta_{2_2}θ22

    • θ21=arccos⁡(L12+dθ2−L222×L1×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)θ22=arccos⁡(xθdθ)\theta_{2_2} = \arccos(\frac{x_{\theta}}{d_{\theta}})θ22=arccos(dθxθ)
  • 舵机 5 的角度 θ2\theta_2θ2

    • θ2=180−(θ21180+θ22180)\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=∣Θ−αrad+θ21+θ22180−90∣\theta_4 = |\frac{\Theta - \alpha_{rad} + \theta_{2_1} + \theta_{2_2}}{180} - 90|θ4=180Θαrad+θ21+θ2290

5.串口通信协议

stm32中控 >>> stm32机械臂

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

在这里插入图片描述

  • 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])   # 1send_data_str += "%03d%03d" % (global_board_center[2],global_board_center[3])   # 2send_data_str += "%03d%03d" % (global_board_center[4],global_board_center[5])   # 3send_data_str += "%03d%03d" % (global_board_center[6],global_board_center[7])   # 4send_data_str += "%03d%03d" % (global_board_center[8],global_board_center[9])   # 5send_data_str += "%03d%03d" % (global_board_center[10],global_board_center[11])   # 6send_data_str += "%03d%03d" % (global_board_center[12],global_board_center[13])   # 7send_data_str += "%03d%03d" % (global_board_center[14],global_board_center[15])   # 8send_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 = 0if 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=0while(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+=1for (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=0while(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 * rhoy0 = b * rhox1 = 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 % 180if 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 = point1x2, y2 = point2distance = 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 - cxtemp_y = y - cynew_x = temp_x * cos_angle - temp_y * sin_angle + cxnew_y = temp_x * sin_angle + temp_y * cos_angle + cyreturn 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) * sizey = center[1] + (j - 1.5) * sizerotated_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) * sizegrid_center_y = center[1] + (j - 1) * sizeangle_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_centerfor 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])   # 1send_data_str += "%03d%03d" % (global_board_center[2],global_board_center[3])   # 2send_data_str += "%03d%03d" % (global_board_center[4],global_board_center[5])   # 3send_data_str += "%03d%03d" % (global_board_center[6],global_board_center[7])   # 4send_data_str += "%03d%03d" % (global_board_center[8],global_board_center[9])   # 5send_data_str += "%03d%03d" % (global_board_center[10],global_board_center[11])   # 6send_data_str += "%03d%03d" % (global_board_center[12],global_board_center[13])   # 7send_data_str += "%03d%03d" % (global_board_center[14],global_board_center[15])   # 8send_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 = 0if(len(send_LvBo_1)>20):for data1,num in send_LvBo_1:if(num>num_lvbo):num_lvbo = numsend_data_str = data1send_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 = 0if 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=0while(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+=1for (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=0while(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 = 0if(len(send_LvBo_1)>20):for data1,num in send_LvBo_1:if(num>num_lvbo):num_lvbo = numsend_data_str = data1send_LvBo_1.clear() print(send_data_str,'\n')ser.write(send_data_str.encode())  # 串口发送

全部代码

import cv2
import numpy as np
import math
import serial
ser = serial.Serial("/dev/ttyTHS1",115200,timeout=0.01)
print("serial Open")# 全局变量定义=========================================================
global_angle = 0
global_angle_lvbo=[]width = 0
global_width_mid = (width//2)height = 0
global_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 = point1x2, y2 = point2distance = 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 - cxtemp_y = y - cynew_x = temp_x * cos_angle - temp_y * sin_angle + cxnew_y = temp_x * sin_angle + temp_y * cos_angle + cyreturn 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) * sizey = center[1] + (j - 1.5) * sizerotated_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) * sizegrid_center_y = center[1] + (j - 1) * sizeangle_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_centerfor 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 = 2
max_degree = 179
MIN_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:breakframe = 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)+11global_height_mid = (height//2)cless_width = 200# 棋盘左右边界 用于区分棋盘内外棋子Left_boundary = global_width_mid-cless_widthRight_boundary = global_width_mid+cless_widthcv2.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 = 0if 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=0while(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+=1for (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=0while(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 * rhoy0 = b * rhox1 = 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 % 180if 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])   # 1send_data_str += "%03d%03d" % (global_board_center[2],global_board_center[3])   # 2send_data_str += "%03d%03d" % (global_board_center[4],global_board_center[5])   # 3send_data_str += "%03d%03d" % (global_board_center[6],global_board_center[7])   # 4send_data_str += "%03d%03d" % (global_board_center[8],global_board_center[9])   # 5send_data_str += "%03d%03d" % (global_board_center[10],global_board_center[11])   # 6send_data_str += "%03d%03d" % (global_board_center[12],global_board_center[13])   # 7send_data_str += "%03d%03d" % (global_board_center[14],global_board_center[15])   # 8send_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 = 0if(len(send_LvBo_1)>20):for data1,num in send_LvBo_1:if(num>num_lvbo):num_lvbo = numsend_data_str = data1send_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 <math.h>
#include <stdbool.h>#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坐标+末端位姿 alpha
int 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); // 舵机 4double 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); // 舵机 5theta4 = fabs(Rad2Deg(Theta - alpha + theta2_1 + theta2_2) - 90); // 舵机 3if (!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 即可获取」

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/89400.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/89400.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac上最佳SSH工具:Termius实用指南

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;SSH是一种安全网络协议&#xff0c;广泛用于Mac系统远程登录。Termius是Mac上一款功能强大的SSH客户端&#xff0c;提供直观的用户界面和全面的SSH功能&#xff0c;支持Intel和M1架构芯片的Mac设备。它包括多会…

面试高频题 力扣 695.岛屿的最大面积 洪水灌溉(FloodFill) 深度优先遍历 暴力搜索 C++解题思路 每日一题

目录零、题目描述一、为什么这道题值得一看&#xff1f;二、题目拆解&#xff1a;提取核心要素与约束三、算法实现&#xff1a;基于 DFS 的面积计算代码拆解时间复杂度空间复杂度四、与「岛屿数量」的代码对比&#xff08;一目了然看差异&#xff09;五、坑点总结六、举一反三七…

2023 年 3 月青少年软编等考 C 语言八级真题解析

目录 T1. 最短路径问题 思路分析 T2. Freda 的越野跑 思路分析 T3. 社交网络 思路分析 T4. 旅行 思路分析 T1. 最短路径问题 题目链接:SOJ D1249 平面上有 n n n 个点( n ≤ 100 n\le 100 n≤100),每个点的坐标均在 − 10000 ∼ 10000 -10000\sim 10000 −10000∼10000…

UEditor富文本编辑器

UEditor配置部分在该项目中插入uediterUEditor是由百度FEX 前端团队开发并开源的一款功能强大、可定制性高的所见即所得&#xff08;WYSIWYG&#xff09;富文本编辑器。它的核心目标是帮助用户在网页上轻松编辑和发布格式丰富的内容&#xff08;如新闻、博客、论坛帖子、产品描…

Node.js 常用工具

Node.js 常用工具 引言 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它允许开发者使用 JavaScript 编写服务器端应用程序。随着 Node.js 生态的日益完善,涌现出大量高效的工具,使得开发过程更加高效。本文将详细介绍一些在 Node.js 开发中常用的工具。 一、…

【unitrix】 6.7 基本结构体(types.rs)

一、源码 这是一个使用 Rust 类型系统实现类型级二进制数的方案&#xff0c;通过泛型和嵌套结构体在编译期表示数值。 //! 类型级二进制数表示方案 //! //! 使用嵌套泛型结构体表示二进制数&#xff0c;支持整数和实数表示。 //! //! ## 表示规则 //! - 整数部分: B<高位, 低…

基于Scikit-learn的机器学习建模与SHAP解释分析

基于Scikit-learn的机器学习建模与SHAP解释分析 1. 项目概述 本项目将使用Python的scikit-learn库对一个包含400条记录的数据集进行完整的机器学习建模流程,包括数据预处理、特征工程、模型训练和模型解释。我们将重点关注以下几个方面: 数据预处理:包括连续变量的标准化/…

QA:备份一般存储这块是怎么考虑?备份服务器如何选择?

1. 性能需求与架构设计 大数据平台的备份需满足高并发、加密传输、增量扫描、重复数据删除&#xff08;重删&#xff09;、数据压缩等复杂操作&#xff0c;对备份服务器的计算能力、存储吞吐及网络带宽提出极高要求。建议采用多节点集群架构&#xff0c;通过横向扩展提升备份效…

【东枫科技】用于汽车和工业传感器应用的高性能、集成式 24 GHz FMCW 雷达收发器芯片组

用于汽车和工业传感器应用的高性能、集成式 24 GHz FMCW 雷达收发器芯片组 ADF5904是一款高度集成的4通道、24 GHz接收机下变频器MMIC&#xff0c;具有卓越的低噪声性能、高线性度和低功耗组合。ADF5904集成式多通道接收机下变频器具有10 dB噪声系数性能&#xff0c;优于竞争型…

新版本flutter(3.32.7) android 端集成百度地图sdk

新版本flutter(3.32.7) android 端集成百度地图sdk 因为官方文档有很多地方没有说清楚,导致在适配过程中踩了很多坑,本文档基于已经实现集成的flutter安卓端应用编写。 官方文档地址:https://lbs.baidu.com/faq/api?title=flutter/loc/create-project/configure Flutt…

FreeRTOS—列表和列表项

文章目录一、列表与列表项1.1.列表与列表项的简介1.2.列表与列表项相关结构体1.2.1.列表结构体1.2.2.列表项结构体1.2.3.迷你列表项二、列表相关API函数2.1.列表相关API函数介绍2.1.1.vListInitalise( )初始化列表函数2.1.2.vListInitaliseItem( )初始化列表项函数2.1.3.vListI…

超详细 anji-captcha滑块验证uniapp微信小程序前端组件

由于步骤太多&#xff0c;字数太多&#xff0c;废话也太多&#xff0c;所以前后端分开讲了&#xff0c;后端文章请看&#xff1a; 超详细 anji-captcha滑块验证springbootuniapp微信小程序前后端组合https://blog.csdn.net/new_public/article/details/149116742 anji-captcha…

面向对象编程篇

文章目录一、思维导图二、详细内容第 6 章&#xff1a;面向对象编程基础6.1 面向对象编程的概念和优势6.2 类和对象的定义与创建6.3 类的属性和方法6.4 构造函数&#xff08;__init__&#xff09;和析构函数&#xff08;__del__&#xff09;6.5 封装、继承和多态的实现第 7 章&…

虚拟商品自动化实践:闲鱼订单防漏发与模板化管理的技术解析

最近阿灿发现了一款闲鱼虚拟商品卖家必备神器&#xff01;告别手动发货&#xff0c;订单自动处理&#xff0c;防错防漏&#xff0c;支持课程、激活码、电子书等多种商品&#xff0c;预设模板更省心。文末获取工具&#xff01;最厉害的是&#xff0c;你完全不用一直开着电脑。以…

【Zephyr开发实践系列】08_NVS文件系统调试记录

文章目录前言一、NVS原理介绍&#xff1a;二、BUG-NO1&#xff1a;将NVS运用在NAND-Flash类大容量存储设备2.1 情况描述&#xff1a;2.2 BUG复现&#xff1a;文件系统设备树构建测试应用编写&#xff08;导致错误部分&#xff09;&#xff1a;问题呈现&#xff1a;2.3 问题简述…

网络安全第二次作业

靶场闯关1~8 1. 在url后的name后输入payload ?name<script>alert(1)</script> 2. 尝试在框中输入上一关的payload,发现并没有通过&#xff0c;此时我们可以点开页面的源代码看看我们输入的值被送到什么地方去了 从图中可以看到&#xff0c;我们输入的值被送到i…

LangChain 源码剖析(七)RunnableBindingBase 深度剖析:给 Runnable“穿衣服“ 的装饰器架构

每一篇文章都短小精悍&#xff0c;不啰嗦。一、功能定位&#xff1a;Runnable 的 "增强包装器"RunnableBindingBase 是 LangChain 中实现装饰器模式的核心组件。它就像给原有 Runnable 套上一件 "功能外套"—— 不改变原有 Runnable 的核心逻辑&#xff0c…

为 Git branch 命令添加描述功能

写在最前面的使用方式 查看 所有分支的备注 git branch.notes创建分支并为分支添加备注 git co -b feat/oauth -m 第三方用户登录对分支描述的添加与清除 添加 git branch.note --add 清除 git branch.note --clear &#x1f4dd; 为 Git branch 命令添加描述功能 &#x…

LeetCode|Day18|20. 有效的括号|Python刷题笔记

LeetCode&#xff5c;Day18&#xff5c;20. 有效的括号&#xff5c;Python刷题笔记 &#x1f5d3;️ 本文属于【LeetCode 简单题百日计划】系列 &#x1f449; 点击查看系列总目录 >> &#x1f4cc; 题目简介 题号&#xff1a;20. 有效的括号 难度&#xff1a;简单 题目…

使⽤Pytorch构建⼀个神经⽹络

关于torch.nn:使⽤Pytorch来构建神经⽹络, 主要的⼯具都在torch.nn包中.nn依赖于autograd来定义模型, 并对其⾃动求导.构建神经⽹络的典型流程:定义⼀个拥有可学习参数的神经⽹络遍历训练数据集处理输⼊数据使其流经神经⽹络计算损失值将⽹络参数的梯度进⾏反向传播以⼀定的规则…