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+y2∣,H=zH = zH=z
-
L3L_3L3在 xxx 方向和 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_1L1和L2L_2L2的 xxx 和 yyy 分量:
- xθ=D−xαx_{\theta} = D - x_{\alpha}xθ=D−xα ,yθ=H−yαy_{\theta} = H - y_{\alpha}yθ=H−yα
-
连杆L2L_2L2末端到原点的距离:
- dθ=xθ2+yθ2d_{\theta} = \sqrt{x_{\theta}^2 + y_{\theta}^2}dθ=xθ2+yθ2
-
连杆L1L_1L1与L2L_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+L22−dθ2)
-
舵机 4 的角度 θ3\theta_3θ3:
- θ3=180−π×1.5−Θ180\theta_3 = 180 - \frac{\pi \times 1.5 - \Theta}{180}θ3=180−180π×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θ2−L22) ,θ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+θ22−90∣
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)
数据计算
-
九个格中心点的坐标:根据棋盘中心点坐标、棋盘旋转角度和棋盘尺寸,计算九个格中心点的坐标
-
棋盘状态:根据棋子的圆心坐标和棋盘格中心点的坐标,计算两点间的距离判断棋子是否在棋盘格内
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屏幕显示
-
主菜单
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(); }
-
第一题界面
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(); }
-
第二、三题界面
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(); }
-
第四、五、六题界面
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算法,采用优先级判断
- 判断是否结束
- 判断是否有赢的机会
- 判断是否有堵的机会
- 判断中心方格是否为空
- 判断四个角的方格是否为空
- 判断四个边的方格是否为空
//将接收到的棋盘信息储存
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 即可获取」