本文使用带有独立处理图像模块的摄像头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文章来源:https://www.toymoban.com/news/detail-859318.html
#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文章来源地址https://www.toymoban.com/news/detail-859318.html
#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);
}
}
到了这里,关于Openmv识别Apriltag码并与stm32进行串口通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!