赞
踩
本文使用带有独立处理图像模块的摄像头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)
- 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码识别
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);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。