OpenMV实现Apriltag码识别与STM32串口通信

        本文使用带有独立处理图像模块的摄像头Openmv进行Apriltag码的识别,并将Openmv与stm32进行串口通信,将Apriltag码的ID、中心位置相对于Openmv摄像头中心坐标的偏移量、以及Apriltag码相对于Openmv镜头的距离通过串口通信传输给stm32。

        接线图Openmv通过电脑USB口供电,Openmv接三根线,一根与stm32共地,一根将Openmv的P4与stm32的A10相连接,另一根将Openmv的P5管脚与stm32端的A9相连接(即两者的Rx和Tx交错链接,以实现串口通信):

        

串口通信采取的方式:

        采取串口通信发送hex数据包的方式,通讯协议为两个帧头和一个帧尾。由于整型数据较为容易发送,可以通过将Openmv端的tag_id(int)直接发送、Apriltag码相对于Openmv镜头中心的横向偏移量x_translation(float)、以及距离distance(float)乘以1000或者10000进行发送。另外,发送负数也会导致错误,因此加入一个标志位,在Openmv端只发送正整数,通过标志位的值,在stm32端判断是否需要加上负号。

        可以通过time.sleep_ms()来设置摄像头的帧数,实际上每秒发送数据的速度达不到帧数。

        采取的串口通信为使用串口com3,波特率为9600,数据位8位,无校验位,停止位1。事实上返回的两个float都是相对距离,并不是实际距离,若要实际距离只需要如下:

# f_x 是x的像素为单位的焦距。对于标准的OpenMV,应该等于2.8/3.984*656,这个值是用毫米为单位的焦距除以x方向的感光元件的长度,乘以x方向的感光元件的像素(OV7725)
# f_y 是y的像素为单位的焦距。对于标准的OpenMV,应该等于2.8/2.952*488,这个值是用毫米为单位的焦距除以y方向的感光元件的长度,乘以y方向的感光元件的像素(OV7725)

Openmv端python代码:

import sensor
import image
import time
import struct
from pyb import UART
# 使用openmv的串口3(com3)
# 波特率要跟需要通信的设备一样 Tx 和 Rx 引脚交错连接

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA) #QQVGA
sensor.skip_frames(30)
sensor.set_auto_gain(False)  # 关闭
sensor.set_auto_whitebal(False)  # 关闭
clock = time.clock()

uart = UART (3, 9600)   #初始化串口3,波特率为9600(注意:下位机stm32记得也配置成9600)
uart.init(9600, bits=8, parity=None, stop=1)  #设置波特率为9600,数据位8位,无校验位,停止位1位
#编码模式UTF-8

# 注意!与find_qrcodes不同,find_apriltags 不需要软件矫正畸变就可以工作。

# f_x 是x的像素为单位的焦距。对于标准的OpenMV,应该等于2.8/3.984*656,这个值是用毫米为单位的焦距除以x方向的感光元件的长度,乘以x方向的感光元件的像素(OV7725)
# f_y 是y的像素为单位的焦距。对于标准的OpenMV,应该等于2.8/2.952*488,这个值是用毫米为单位的焦距除以y方向的感光元件的长度,乘以y方向的感光元件的像素(OV7725)
# c_x 是图像的x中心位置   c_y 是图像的y中心位置

tag_families = 0
tag_families |= image.TAG36H11  # comment out to disable this family (default family)

f_x = (2.8 / 3.984) * 160 # 默认值
f_y = (2.8 / 2.952) * 120 # 默认值
c_x = 160 * 0.5 # 默认值(image.w * 0.5)
c_y = 120 * 0.5 # 默认值(image.h * 0.5)

#apriltag家族tag36h11
def family_name(tag):
    if tag.family() == image.TAG36H11:
        return "TAG36H11"

while(True):
    clock.tick()
    img = sensor.snapshot()
    for tag in img.find_apriltags(families=tag_families,fx=f_x, fy=f_y, cx=c_x, cy=c_y): # TAG36H11
        img.draw_rectangle(tag.rect(), color = (255, 0, 0))
        img.draw_cross(tag.cx(), tag.cy(), color = (0, 255, 0))
        print_args = (family_name(tag), tag.id(), tag.x_translation(), tag.y_translation(), tag.z_translation())
        print("Tag Family %s, Tag ID %d, Tx: %f, Ty %f, Tz %f" % print_args)
        #帧头 + 帧头 + id信息 + x坐标 + z距离 + 标志位 + 帧尾
        #使用struct将数据打包并发送,保证float型x坐标数据准确及其信息的完整性
        if tag.x_translation() >= 0:
            data = struct.pack("<bbiiibb",
                                0xAA,     #帧头
                                0xAE,     #帧头
                                tag.id(), #数据id
                                #x的坐标
                                int(10000*tag.x_translation()),#数据1
                                #z的坐标
                                -int(10000*tag.z_translation()),#数据2
                                0xBF,     #标志位表示大于零
                                0xAC)     #帧尾
        else:
            data = struct.pack("<bbiiibb",
                               0xAA,     #帧头
                               0xAE,     #帧头
                               tag.id(), #数据id
                               #x的坐标
                               -int(1000*tag.x_translation()),#数据1
                               #z的坐标
                               -int(10000*tag.z_translation()),#数据2
                               0xCF,     #标志位表示小于零
                               0xAC)     #帧尾
        uart.write(data)  #com3串口发送
        time.sleep_ms(50)
       # uart.write("Tag Family %s, Tag ID %d, Tx: %f, Ty %f, Tz %f" % print_args+"\r\n")#与windows通信
    print(clock.fps())

将python文件复制粘贴到Openmv端的main.py中,即可实现Openmv脱机自动执行Apriltag码识别

stm32端代码:

Serial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t receive_data[13];
uint8_t Serial_RxFlag;					//定义接收数据包标志位
int tag_id;			//apriltag码的id 包括 0 ,1 ,2三个数据
//偏移距离数值越大与openmv镜头中心偏离越远
//相对距离数值越大距离openmv镜头越远
//x_translation的正负表示物体相对openmv镜头中心的右左
//x_translation<0,表示apriltag码相对于openmv镜头中心在左
//x_translation>0,表示apriltag码相对于openmv镜头中心在右
int x_translation;  //apriltag码偏离openmv镜头中心的相对位移 
int distance;		//apriltag码距离openmv镜头中心的相对距离
int Sign;			//标志位
void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;		
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure ;
	USART_InitStructure.USART_BaudRate= 9600;	//串口通信波特率为9600
	USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStructure);

	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd =	ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	USART_Cmd(USART1,ENABLE);
}
/**
  * 函    数:获取串口接收数据包标志位
  * 参    数:无
  * 返 回 值:串口接收数据包标志位,范围:0~1,接收到数据包后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)			//如果标志位为1
	{
		Serial_RxFlag = 0;
		return 1;					//则返回1,并自动清零标志位
	}
	return 0;						//如果标志位为0,则返回0
}

/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;		//定义表示当前状态机状态的静态变量
	static uint8_t pRxPacket = 0;	//定义表示当前接收数据位置的静态变量
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断
	{
		uint8_t RxData = USART_ReceiveData(USART1);				//读取数据寄存器,存放在接收的数据变量
		
		/*使用状态机的思路,依次处理数据包的不同部分*/
		
		/*当前状态为0,接收数据包包头*/
		if (RxState == 0)
		{
			if (RxData == 0xAA)			//如果数据确实是包头
			{
				RxState = 1;			//置下一个状态			//数据包的位置归零
			}
		}
		/*当前状态为1,接收数据包数据*/
		else if (RxState == 1)
		{	
			if (RxData == 0xAE)
			{
				RxState = 2;
				pRxPacket = 0;
			}
		}
		/*当前状态为2,接收数据包包尾*/
		else if (RxState == 2)
		{
			receive_data[pRxPacket] = RxData;	//将数据存入数据包数组的指定位置
			pRxPacket ++;				//数据包的位置自增
			if (pRxPacket >= 13)			//如果收够12个数据
			{
				RxState = 3;			//置下一个状态
			}
		}
		else if(RxState == 3)
		{
			if (RxData == 0xAC)			//如果数据确实是包尾部
			{
				RxState = 0;			//状态归0
				Serial_RxFlag = 1;		//接收数据包标志位置1,成功接收一个数据包	
			}
		}
		tag_id = receive_data[3] << 24 | receive_data[2] << 16 | receive_data[1] << 8 | receive_data[0];//位移运算,将hex16进制转换成int整型
		if(receive_data[12] == 0xBF)
		{
			x_translation = receive_data[7] << 24 | receive_data[6] << 16 | receive_data[5] << 8 | receive_data[4];
		}
		else if(receive_data[12] == 0xCF)
		{
			x_translation = -(receive_data[7] << 24 | receive_data[6] << 16 | receive_data[5] << 8 | receive_data[4]);
		}
		distance = receive_data[11] << 24 | receive_data[10] << 16 | receive_data[9] << 8 | receive_data[8];
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
	}
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
void Serial_Init(void);
uint8_t Serial_GetRxFlag(void);
void USART1_IRQHandler(void);
extern uint8_t receive_data[];
uint8_t Serial_GetRxFlag(void);
extern int tag_id;
extern int x_translation;
extern int distance;
#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "OLED.h"
#include "Serial.h"

int main(void)
{
	OLED_Init();
	Serial_Init();
	
	OLED_ShowString(1,1,"tag_id:");
	OLED_ShowString(2,1,"x_trs:");
	OLED_ShowString(3,1,"Diace:");
	while(1)
	{
		OLED_ShowNum(1,10,tag_id,2);
		OLED_ShowSignedNum(2,9,x_translation,5);
		OLED_ShowNum(3,10,distance,5);
    }
}

作者:电子厂打工仔转行码农

物联沃分享整理
物联沃-IOTWORD物联网 » OpenMV实现Apriltag码识别与STM32串口通信

发表回复