2023电赛E题解题思路与部分代码分享

本文介绍了使用OpenMV或K210等设备进行屏幕、红蓝点及矩形检测的解题思路,包括找到激光点与舵机角度关系、计算移动速度关系等。还给出了OpenMV代码,通过串口发送关键坐标信息。同时展示了STM32单片机部分代码,实现舵机控制、数据接收解析与运动控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 前提说明

        个人并不是在校学生,在机缘巧合下指导学生做比赛,思路可能不成熟,没有经过太大批量的测试,但是能跑。仅是个人愚见,大家理性参考,欢迎提出建议。

2 解题思路

        使用OpenMV或者K210又或者能够跑OpenCV的设备也可以做屏幕和红蓝点以及矩形的检测,对于整个题目,我们只需要屏幕四角坐标,A4纸四角坐标,红点位置坐标。可以直接使用OpenMV/K210等驱动云台,也可以将坐标信息发送到云台的控制板做单独的控制。

        算法思路:先找到激光点位置与舵机角度的关系,然后根据红色激光笔和目标点位做X和Y方向的差值,并计算两个差值的比例关系,得到斜率k,这样就可以计算X和Y上移动的速度关系,当然也可以根据其他的数学关系让光点跑圆形等图形。

3 OpenMV代码

        OpenMV在识别1mm的细线时识别率太低,所以做成了屏幕最中间的一个矩形,根据实际场地用摄像头内的矩形与场地上的50*50矩形重合即可。

        数据部分通过串口发送各个关键坐标信息出去,20ms发送一次。

import sensor, image, time, math, pyb
from pyb import LED, UART

#led灯的定义
LedRed = LED(1)
LedGreen = LED(2)
LedBlue = LED(3)

sensor.reset()
sensor.set_pixformat(sensor.RGB565) # 灰度更快(160x120 max on OpenMV-M7)
sensor.set_framesize(sensor.QQVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_whitebal(False)
clock = time.clock()
uart = UART(3, 115200, timeout_char=1000)
uart.init(115200, bits=8, parity=None, stop=1, timeout_char=1000)


#定义红色激光点的颜色阈值
#red_threshold = (48, 88, 4, 41, -4, 32)
#red_threshold = (65, 91, 0, 22, 3, 15)
#red_threshold = (68, 91, -4, 17, 6, 18)
red_threshold = (73, 64, 0, 17, 6, 15)
#green_threshold = (80, 100, -69, 9, -10, 40)


#定义外框的像素宽度大小以及左上角xy坐标
exteRectW = 86
exteRectX = int((160-exteRectW)/2)
exteRectY = int((120-exteRectW)/2)

#外框周边四角坐标,从左上角顺时针为顺序
exteRect = [[exteRectX,exteRectY],[exteRectX+exteRectW,exteRectY],\
            [exteRectX+exteRectW,exteRectY+exteRectW],[exteRectX,exteRectY+exteRectW]]
#外框区域ROI
exteROI = [exteRectX,exteRectY,exteRectW,exteRectW]

#定义框内查询ROI
searchDif = 5
inteSearchROI = [exteRectX+searchDif,exteRectY+searchDif,exteRectW-searchDif,exteRectW-searchDif]

inteRect = [[0,0],[0,0],[0,0],[0,0]] #内框周边四角坐标

#定义红色与绿色激光点的位置
redPoint = [0,0]
greenPoint = [0,0]


#整合数据、串口发送
#帧头 内部矩形四角坐标 红色光点坐标 绿色光电坐标 校验和
def com_sendbyte():
    x0h = (inteRect[0][0] & 0xFF00) >> 8
    x0l = inteRect[0][0] & 0x00FF
    y0h = (inteRect[0][1] & 0xFF00) >> 8
    y0l = inteRect[0][1] & 0x00FF

    x1h = (inteRect[1][0] & 0xFF00) >> 8
    x1l = inteRect[1][0] & 0x00FF
    y1h = (inteRect[1][1] & 0xFF00) >> 8
    y1l = inteRect[1][1] & 0x00FF

    x2h = (inteRect[2][0] & 0xFF00) >> 8
    x2l = inteRect[2][0] & 0x00FF
    y2h = (inteRect[2][1] & 0xFF00) >> 8
    y2l = inteRect[2][1] & 0x00FF

    x3h = (inteRect[3][0] & 0xFF00) >> 8
    x3l = inteRect[3][0] & 0x00FF
    y3h = (inteRect[3][1] & 0xFF00) >> 8
    y3l = inteRect[3][1] & 0x00FF

    rxh = (redPoint[0] & 0xFF00) >> 8
    rxl = redPoint[0] & 0x00FF
    ryh = (redPoint[1] & 0xFF00) >> 8
    ryl = redPoint[1] & 0x00FF

    gxh = (greenPoint[0] & 0xFF00) >> 8
    gxl = greenPoint[0] & 0x00FF
    gyh = (greenPoint[1] & 0xFF00) >> 8
    gyl = greenPoint[1] & 0x00FF

    check = x0h + x0l + y0h + y0l + x1h + x1l + y1h + y1l + x2h + x2l + y2h + y2l + \
            x3h + x3l + y3h + y3l + rxh + rxl + ryh + ryl + gxh + gxl + gyh + gyl

    print(x0h , x0l , y0h , y0l , x1h , x1l , y1h , y1l , x2h , x2l , y2h , y2l , \
          x3h , x3l , y3h , y3l , rxh , rxl , ryh , ryl , gxh , gxl , gyh , gyl, check)
    arr = bytearray([x0h , x0l , y0h , y0l , x1h , x1l , y1h , y1l , x2h , x2l , y2h , y2l , \
                     x3h , x3l , y3h , y3l , rxh , rxl , ryh , ryl , gxh , gxl , gyh , gyl, check])
    uart.write(arr)



def find_max(blobs):
    max_size=0
    for blob in blobs:
        if blob[2]*blob[3] > max_size:
            max_blob=blob
            max_size = blob[2]*blob[3]
    return max_blob


sendtime = pyb.millis()
while(True):
    clock.tick()
    img = sensor.snapshot().lens_corr(strength = 1.2, zoom = 1.2)
    #img = sensor.snapshot()
    #1 先在屏幕中央绘制外框ROI,比赛时可用于调试摄像头位置
    img.draw_rectangle(exteROI, color = (255, 255, 0))
    img.draw_cross(80, 60, color = (255, 255, 0), size=3)
    #for ep in exteRect: img.draw_circle(ep[0], ep[1], 3, color = (255, 255, 0))
    #2 根据外框ROI查询内框
    for r in img.find_rects(roi=inteSearchROI,threshold = 12000):
        for ip in r.corners(): img.draw_circle(ip[0], ip[1], 3, color = (255, 255, 0))
        for i in range(4): inteRect[i] = r.corners()[i]
        print(inteRect)
    #寻找红色激光点
    blobs = img.find_blobs([red_threshold])
    if blobs:
        max_blob = find_max(blobs)
        #img.draw_rectangle(max_blob[0:4], color = (255, 255, 0)) # rect
        img.draw_cross(max_blob[5], max_blob[6], color = (255, 255, 0)) # cx, cy
        print("找到了:", max_blob[5], max_blob[6])
        redPoint[0] = max_blob[5]
        redPoint[1] = max_blob[6]
    else:
        print("未找到")
    if pyb.millis() - sendtime > 20:#20ms发送一次数据到单片机
        sendtime = pyb.millis()
        com_sendbyte()

4 单片机部分代码(STM32)

        在单片机中我们首先实现舵机控制代码,然后实现串口接收数据并解析数据代码。我是直接在串口接收处理函数中进行舵机的运行控制。

4.1 舵机代码

void Motor_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

	TIM_TimeBaseStructure.TIM_Period = 20000-1;//重装载值,20ms,50hz
	TIM_TimeBaseStructure.TIM_Prescaler = 72-1;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 1500;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

	TIM_OC1Init(TIM5, &TIM_OCInitStructure);
	TIM_OC2Init(TIM5, &TIM_OCInitStructure);


	TIM_OC1PreloadConfig(TIM5, TIM_OCPreload_Enable);
	TIM_OC2PreloadConfig(TIM5, TIM_OCPreload_Enable);

	TIM_ARRPreloadConfig(TIM5, ENABLE);

	TIM_Cmd(TIM5, ENABLE);
}


void Motor_SetDJ_PWM(u8 djnum, float pwm)
{
	switch(djnum)
	{
		case 1:TIM_SetCompare1(TIM5, pwm);break;
		case 2:TIM_SetCompare2(TIM5, pwm);break;
	}
}


uint16_t Motor_ReadDJ_PWM(u8 djnum)
{
	uint16_t value = 0;
	switch(djnum)
	{
		case 1:value = TIM_GetCapture1(TIM5);break;
		case 2:value = TIM_GetCapture2(TIM5);break;
	}
	return value;
}

4.2 运动控制头文件中定义内容


typedef enum {
	INIT_STATE = 0,//找原点
	EXTE0_STATE = 1,//找外框的第一个坐标点
	EXTE1_STATE = 2,//找外框的第二个坐标点
	EXTE2_STATE = 3,//找外框的第三个坐标点
	EXTE3_STATE = 4,//找外框的第四个坐标点
	INTE0_STATE = 5,//找内框的第一个坐标点
	INTE1_STATE = 6,//找内框的第二个坐标点
	INTE2_STATE = 7,//找内框的第三个坐标点
	INTE3_STATE = 8,//找内框的第四个坐标点
	GREEN_STATE = 9,//绿色找红色
}sysStateDef;


#define OPENMV_MAXSIZE	128
typedef struct{
	u8 openmv_rxbuff[OPENMV_MAXSIZE];//接收缓存区
	u8 openmv_rxcount;
	u8 openmv_rxover;
}OpenMV_TypeDef;

typedef struct{
	uint16_t inteRect[4][2];
	uint16_t redPoint[2];
	uint16_t greenPoint[2];
	uint8_t state;
}Taget_TypeDef;


//定义外框的像素宽度大小以及左上角xy坐标
#define exteRectW  86			//宽度要根据实际摄像头测量到的50*50矩形大小像素点对应
#define exteRectX  ((160-exteRectW)/2.0)	//左上角X坐标
#define exteRectY  ((120-exteRectW)/2.0)	//左上角Y坐标
#define exteCenterX	80	//中心点X坐标
#define exteCenterY	60  //中心点Y坐标

#define	PUL_R	1225	//右限  
#define PUL_L	1580	//左限
#define	PUL_U	1130	//上限  
#define	PUL_D	1365	//下限

#define abs(x)	((x)<0?(-(x)):(x))
#define INC_X	((PUL_L-PUL_R)/exteRectW/2)
#define INC_Y	((PUL_D-PUL_U)/exteRectW/2)

4.3 数据解析与运动控制核心代码

OpenMV_TypeDef openmv = {0};
//中断 USART1
void USART1_IRQHandler(void)
{
	//如果没有接收完成,则缓存数据
	if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET && openmv.openmv_rxover == 0)
	{
		USART_ClearFlag(USART1, USART_FLAG_RXNE);
		openmv.openmv_rxbuff[openmv.openmv_rxcount++] = USART_ReceiveData(USART1);
		openmv.openmv_rxcount %= OPENMV_MAXSIZE;//防止溢出
	}
	//空闲中断:接收完成
	if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) == SET)
	{
		u32 state = USART1->SR;
		state = USART1->DR;				//清标志
		openmv.openmv_rxover = 1;	//接收完成标志位	
	}
}


//外框周边四角坐标,从左上角顺时针为顺序
uint16_t exteRect[4][2] = {{exteRectX, exteRectY},
						   {exteRectX+exteRectW, exteRectY},
						   {exteRectX+exteRectW, exteRectY+exteRectW},
						   {exteRectX, exteRectY+exteRectW}};

//定义一个红色的目标点,用于引导红色激光笔移动
uint16_t rTarPoint[2] = {exteCenterX, exteCenterY};

//定义一个绿色的目标点,用于引导绿色激光笔移动
uint16_t gTarPoint[2] = {exteCenterX, exteCenterY};

//openmv数据解析函数
Taget_TypeDef taget = {0};



float pulseX = 0, incX = INC_X;
float pulseY = 0, incY = INC_Y;
float k = 1;//X差值对于Y差值的比例关系,用于移动时的协同
void OpenMV_Analysis(void)
{
	uint8_t check = 0, i = 0, j = 0;
	if(openmv.openmv_rxover == 0)	return;		//保证接收完成才解析
	for(i=0; i<openmv.openmv_rxcount; i++) {
		printf("%d ", openmv.openmv_rxbuff[i]);
	}
	printf("\r\n");
	
	for(i=0; i<openmv.openmv_rxcount-1; i++) {
		check += openmv.openmv_rxbuff[i];
	}
	if(check == openmv.openmv_rxbuff[openmv.openmv_rxcount-1]) {
		printf("校验正确\r\n");
		taget.inteRect[0][0] = (openmv.openmv_rxbuff[0]<<8) + openmv.openmv_rxbuff[1];
		taget.inteRect[0][1] = (openmv.openmv_rxbuff[2]<<8) + openmv.openmv_rxbuff[3];
		
		taget.inteRect[1][0] = (openmv.openmv_rxbuff[4]<<8) + openmv.openmv_rxbuff[5];
		taget.inteRect[1][1] = (openmv.openmv_rxbuff[6]<<8) + openmv.openmv_rxbuff[7];
		
		taget.inteRect[2][0] = (openmv.openmv_rxbuff[8]<<8) + openmv.openmv_rxbuff[9];
		taget.inteRect[2][1] = (openmv.openmv_rxbuff[10]<<8) + openmv.openmv_rxbuff[11];
		
		taget.inteRect[3][0] = (openmv.openmv_rxbuff[12]<<8) + openmv.openmv_rxbuff[13];
		taget.inteRect[3][1] = (openmv.openmv_rxbuff[14]<<8) + openmv.openmv_rxbuff[15];	

		taget.redPoint[0] = (openmv.openmv_rxbuff[16]<<8) + openmv.openmv_rxbuff[17];
		taget.redPoint[1] = (openmv.openmv_rxbuff[18]<<8) + openmv.openmv_rxbuff[19];	

		taget.greenPoint[0] = (openmv.openmv_rxbuff[20]<<8) + openmv.openmv_rxbuff[21];
		taget.greenPoint[1] = (openmv.openmv_rxbuff[22]<<8) + openmv.openmv_rxbuff[23];	
		printf("内部矩形坐标:");
		for(i=0; i<4; i++) {
			for(j=0; j<2; j++) {
				printf("%d ", taget.inteRect[i][j]);
			}
		}
		printf("\r\n");
		printf("红色激光点坐标:%d %d\r\n", taget.redPoint[0], taget.redPoint[1]);
		printf("绿色激光点坐标:%d %d\r\n", taget.greenPoint[0], taget.greenPoint[1]);
		
		//根据当前状态更新目标位置
		printf("S:%d abs:%d %d\r\n", taget.state, abs(taget.redPoint[0]-rTarPoint[0]), abs(taget.redPoint[1]-rTarPoint[1]));
		if(abs(taget.redPoint[0]-rTarPoint[0]) < 4 && abs(taget.redPoint[1]-rTarPoint[1]) < 4) {
			taget.state++;
			taget.state %= 5;
		}
		
		switch(taget.state) {
			case INIT_STATE: rTarPoint[0] = exteCenterX; rTarPoint[1] = exteCenterY; break;
			case EXTE0_STATE: rTarPoint[0] = exteRect[0][0]; rTarPoint[1] = exteRect[0][1]; break;
			case EXTE1_STATE: rTarPoint[0] = exteRect[1][0]; rTarPoint[1] = exteRect[1][1]; break;
			case EXTE2_STATE: rTarPoint[0] = exteRect[2][0]; rTarPoint[1] = exteRect[2][1]; break;
			case EXTE3_STATE: rTarPoint[0] = exteRect[3][0]; rTarPoint[1] = exteRect[3][1]; break;
		}
		printf("红色激光点目标:%d %d\r\n", rTarPoint[0], rTarPoint[1]);
		k = (rTarPoint[0] - taget.redPoint[0])/(rTarPoint[1] - taget.redPoint[1]);
		k = abs(k);
		incX = k*INC_X;
		printf("%0.2f\r\n", incX);
		
		if(taget.redPoint[0] != 0)
		{
			pulseX = Arm_ReadDJ_PWM(DUOJI1_P2_X);
			if(abs(taget.redPoint[0]-rTarPoint[0]) > 3) {
				if(taget.redPoint[0]-rTarPoint[0] > 3) {	
					pulseX += incX;
				}
				else if(taget.redPoint[0]-rTarPoint[0] < -3) {
					pulseX -= incX;
				}
				pulseX = (pulseX<(PUL_R-5))?(PUL_R-5):((pulseX>(PUL_L+5))?(PUL_L+5):pulseX);
				Arm_SetDJ_PWM(DUOJI1_P2_X, pulseX);
			}
		}
		
		if(taget.redPoint[1] != 0)
		{
			pulseY = Arm_ReadDJ_PWM(DUOJI2_P3_Y);
			if(abs(taget.redPoint[1]-rTarPoint[1]) > 3) {
				if(taget.redPoint[1]-rTarPoint[1] > 3) {	
					pulseY -= incY;
				}
				else if(taget.redPoint[1]-rTarPoint[1] < -3) {
					pulseY += incY;
				}
				1100	上限  下限 1350
				pulseY = (pulseY<(PUL_U-5))?(PUL_U-5):((pulseY>(PUL_D+5))?(PUL_D+5):pulseY);
				Arm_SetDJ_PWM(DUOJI2_P3_Y, pulseY);
			}
		}
		
	}
	else {
		printf("校验失败\r\n");
	}
	memset(&openmv, 0, sizeof(openmv));//执行完一次之后清理数据
}


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值