openmv追踪颜色小车

目录​​​​​​​

目录

一、预想功能

二、系统框架

 三、硬件介绍

四、软件介绍

openmv程序

沁恒CH32V103最小系统

串口接收函数

 pwm初始化

小车位置判断和控制

五、实际效果

学习引用文章:


一、预想功能

制作基于沁恒CH32V103最小系统和openmv的颜色追踪小车,他可以识别到所需要的颜色进而追踪所需颜色。

二、系统框架

 三、硬件介绍

openmv:由openmv识别所需的色块,并且把所识别的色块中心xy坐标以数据包的形式发送给沁恒CH32V103最小系统。

沁恒CH32V103最小系统:接收openmv所发送过来的数据包,经过对xy坐标的判断和计算去通过pwm控制电机。

l298n:电机驱动模块,接收沁恒CH32V103最小系统的pwm来控制两边的两个电机。

四、软件介绍

openmv程序

import sensor, image, time, math
from pyb import UART
import json
import ustruct


blue_threshold   = (25, 67, -37, 26, -63, -26)
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # must be turned off for color tracking
sensor.set_auto_whitebal(False) # must be turned off for color tracking
clock = time.clock()

uart = UART(3,115200)   #定义串口3变量
uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters

def find_max(blobs):    #定义寻找色块面积最大的函数
    max_size=0
    for blob in blobs:
        if blob.pixels() > max_size:
            max_blob=blob
            max_size = blob.pixels()
    return max_blob

def sending_data(cx,cy):
    global uart;
    #frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B];
    #data = bytearray(frame)
    data = ustruct.pack("<bbhhb",              #格式为俩个字符俩个短整型(2字节)
                   0x2C,                       #帧头1
                   0x12,                       #帧头2
                   int(cx), # up sample by 4    #数据1
                   int(cy), # up sample by 4    #数据2
                   0x5B)
    uart.write(data);   #必须要传入一个字节数组

def recive_data():
    global uart
    if uart.any():
        tmp_data = uart.readline();
        print(tmp_data)


#mainloop
while(True):
    clock.tick() # Track elapsed milliseconds between snapshots().
    img = sensor.snapshot() # Take a picture and return the image.
    #  pixels_threshold=100, area_threshold=100
    blobs = img.find_blobs([blue_threshold], area_threshold=150);
    cx=0;cy=0;
    if blobs:
        #如果找到了目标颜色
        max_b = find_max(blobs);
        # Draw a rect around the blob.
        img.draw_rectangle(max_b[0:4]) # rect
        #用矩形标记出目标颜色区域
        img.draw_cross(max_b[5], max_b[6]) # cx, cy
        img.draw_cross(160, 120) # 在中心点画标记
        #在目标颜色区域的中心画十字形标记
        cx=max_b[5];
        cy=max_b[6];
        img.draw_line((160,120,cx,cy), color=(127));
        #img.draw_string(160,120, "(%d, %d)"%(160,120), color=(127));
        img.draw_string(cx, cy, "(%d, %d)"%(cx,cy), color=(127));

    sending_data(cx,cy); #发送点位坐标
    recive_data();
    print(cx,cy)
    #time.sleep(1000)

#pack各字母对应类型
#x   pad byte        no value            1
#c   char            string of length 1  1
#b   signed char     integer             1
#B   unsigned char   integer             1
#?   _Bool           bool                1
#h   short           integer             2
#H   unsigned short  integer             2
#i   int             integer             4
#I   unsigned int    integer or long     4
#l   long            integer             4
#L   unsigned long   long                4
#q   long long       long                8
#Q   unsilong long   long                8
#f   float           float               4
#d   double          float               8
#s   char[]          string              1
#p   char[]          string              1
#P   void *          long

沁恒CH32V103最小系统

串口接收函数

#include "Uart.H"
#include "Motor.H"

//u8 x_coordinate;
u8 data_Correctness;
u16 openmv_data[7];
u8 number=0;

void USART2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

//========================================================================
// 函数: void USARTx_CFG(void)
// 描述: 串口初始化函数
// 参数: 无
// 返回: 无
// 版本:
// 日期:
// 备注:USART2 TX-->A.2   RX-->A.3
//========================================================================
void USARTx_CFG(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
  NVIC_InitTypeDef  NVIC_InitStructure;

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能串口2时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //使能GPIOA时钟

  USART_DeInit(USART2);

  /* USART2 TX-->A.2   RX-->A.3 */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;              //设置PA2为复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;        //设置PA3为浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  USART_InitStructure.USART_BaudRate = 115200;                 //设置串口波特率为115200
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;  //字长为8位数据格式
  USART_InitStructure.USART_StopBits = USART_StopBits_1;       //1个停止位
  USART_InitStructure.USART_Parity = USART_Parity_No;          //无奇偶校验位
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件流控制
  USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //发送和接收模式
  USART_Init(USART2, &USART_InitStructure);                    //初始化串口

  NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;      //抢占优先级为1
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;           //子优先级为1
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;              //IRQ通道使能
  NVIC_Init(&NVIC_InitStructure);                              //中断优先级初始化

  USART_Cmd(USART2, ENABLE);                                   //使能串口
  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);               //开启中断
}

//========================================================================
// 函数: void USART2_IRQHandler(void)
// 描述: 串口2中断函数
// 参数: 无
// 返回: 无
// 版本:
// 日期:
// 备注:接收openmv传来的数据
//========================================================================

void USART2_IRQHandler(void)
{
    u8 temp;
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //中断产生
    {
        USART_ClearITPendingBit(USART2,USART_IT_RXNE);    //清除中断标志
        temp = USART_ReceiveData(USART2);               //接收数据
        Openmv_Receive_Data(temp);
    }

}




void Openmv_Receive_Data(u16 data)//接收Openmv传过来的数据
{
    static u8 state = 0;
    if(state==0&&data==0x2C)
    {
        state=1;
        openmv_data[number++]=data;
    }
    else if(state==1&&data==18)
    {
        state=2;
        openmv_data[number++]=data;
    }
    else if(state==2)
    {
        openmv_data[number++]=data;
        if(number>19||data == 0x5B) state=3;    //the last of char is openmv[19]
    }
    else if(state==3)       //state == 3  检测是否接受到结束标志
    {
            if(openmv_data[number-1] == 0x5B)
            {
                state = 0;
                data_Correctness = 1;
                USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);
            }
            else   //wrong thing
            {
                state = 0;
                number=0;
            }
    }
    else    //wrong thing
    {
        state = 0;
        number=0;
    }
}


void USART2_Rx_Task(void)
{
    u16  posX,posY;
    if(data_Correctness == 1)
    {

        posX = openmv_data[3]<<8 | openmv_data[2];
        posY = openmv_data[5]<<8 | openmv_data[4];
        data_Correctness = 0;
        number = 0;
        car_control(posX,posY);
        USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
    }
}






 pwm初始化

#include "pwm.h"
//========================================================================
// 函数: TIM1_PWMOut_Init( u16 arr, u16 psc, u16 ccp )
// 描述: pwm初始化函数
// 参数: arr 周期,psc 预分频器值,ccp 占空比
// 返回: 无
// 版本:
// 日期:
// 备注:
//========================================================================
void TIM1_PWMOut_Init( u16 arr, u16 psc, u16 ccp )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE );//使能GPIOA外设时钟和TIM1时钟

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;        //配置PA8引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //设置为复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//设置输出速度:50MHz
    GPIO_Init( GPIOA, &GPIO_InitStructure );         //GPIO初始化

    TIM_TimeBaseInitStructure.TIM_Period = arr;      //指定下次更新事件时要加载到活动自动重新加载寄存器中的周期值。
    TIM_TimeBaseInitStructure.TIM_Prescaler = psc;   //指定用于划分TIM时钟的预分频器值。
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //时钟分频因子
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//TIM计数模式,向上计数模式
    TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure); //根据指定的参数初始化TIMx的时间基数单位

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;            //指定TIM模式
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//指定TIM输出比较状态,即使能比较输出
    TIM_OCInitStructure.TIM_Pulse = ccp;                         //指定要加载到捕获比较寄存器中的脉冲值。
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;    //指定输出极性。
    TIM_OC1Init( TIM1, &TIM_OCInitStructure ); //根据TIM_OCInitStruct中指定的参数初始化TIM1 Channel1。
    TIM_OC2Init( TIM1, &TIM_OCInitStructure );

    TIM_OC1PreloadConfig( TIM1, TIM_OCPreload_Disable );         //使能CCR1上的TIM1外设预加载寄存器
    TIM_OC2PreloadConfig( TIM1, TIM_OCPreload_Disable );

    TIM_CtrlPWMOutputs(TIM1, ENABLE );                  //启用定时器1PWM输出
    TIM_ARRPreloadConfig( TIM1, ENABLE );               //使能ARR上TIM1外设预加载寄存器
    TIM_Cmd( TIM1, ENABLE );                            //使能TIM1
}

小车位置判断和控制

#include "Motor.H"


u8 motor;
u8 left = 0;
u8 right = 1;
u8 run = 2;




void car_control(u16 x_coordinate,u16 y_coordinate)
{
    if(x_coordinate < 50 && x_coordinate != 0)
    {
        motor = left;
    }
    else if (x_coordinate >= 200 && x_coordinate != 0)
    {
        motor = right;
    }
    else if (x_coordinate > 50 && x_coordinate < 200 && y_coordinate >0)
    {
        motor = run;
    }




    switch(motor)
    {
    case 0:TIM_SetCompare1(TIM1,400);
           TIM_SetCompare2(TIM1,100);
           break;
    case 1:TIM_SetCompare1(TIM1,100);
           TIM_SetCompare2(TIM1,400);
           break;
    case 2:TIM_SetCompare1(TIM1,x_coordinate/2-y_coordinate/4);
           TIM_SetCompare2(TIM1,x_coordinate/2+y_coordinate/4);
           break;

    }
}








五、实际效果

openmv追踪小车

学习引用文章:

(4条消息) 使用openMV3与stm32进行通讯_qq_43243338的博客-CSDN博客_openmv与stm32通信

(4条消息) 【DIY】基于OpenMV的STM32追球小车_淹死的鱼-CSDN博客_openmv stm32

  • 8
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
<项目介绍> 该资源内项目源码是个人的毕设,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到94.5分,放心下载使用! 该资源适合计算机相关专业(如人工智能、通信工程、自动化、软件工程等)的在校学生、老师或者企业员工下载,适合小白学习或者实际项目借鉴参考! 当然也可作为毕业设计、课程设计、课程作业、项目初期立项演示等。如果基础还行,可以在此代码基础之上做改动以实现更多功能。 1.物体识别 本次实验目的是使得小车可以跟踪目标,故首先确定跟踪目标,由于小车整体框架从零开始搭建,并没有太多的金钱可以选择昂贵的摄像头,故本次实验的目标识别选择较为简单的方式以减少硬件压力。本次实验首先识别纯色物体,是完成对纯色物体识别之后更进一步选择跟踪AprilTag。 AprilTag是一个视觉基准系统,可用于各种任务,包括AR,机器人和相机校准。这个tag可以直接用打印机打印出来,而AprilTag检测程序可以计算相对于相机的精确3D位置,方向和id。 AprilTag内容主要包含三个步骤: 第一步是如何根据梯度检测出图像中的各种边缘。 第二步即如何在边缘图像中找出需要的四边形图案并进行筛选,AprilTag尽可能的对检测出的边缘检测,首先剔除非直线边缘,在直线边缘进行邻接边缘查找,最终若形成闭环则为检测到一个四边形。对四边形进行解码确定Apriltag标签。 第三步确定四边形的中心点作为要跟踪的三维左边点。 Openmv对以上步骤进行了函数封装,可以用img.find_apriltags()函数定位Apriltag标签,并且可以通过该函数的返回值的方法确定三维坐标和三维角度:可以用获取x轴坐标tag.x_translation(), tag.y_translation()、tag.z_translation()是y、z轴坐标 。 2.云台追踪 openmv中搜索目标函数的返回值包括了目标物体中心的x、y坐标,原点是在图片的最左下角,就是说如果我们按照直接得到的坐标都是正的,但是我们要求云台追踪目标就是让目标始终出现在视野最中间,都是正的值我们无法判断图片到底是往哪边偏。为了解决这样的问题,我们只需要对得到的坐标进行简单的处理,openmv获得图片宽高都可以用函数获得,故已知图片宽width,高度height,目标中心点坐标x,y。按照相对比例来判断目标点在相机内的相对位置: $$ y1=y/height-0.5 $$ $$ x1=x/width-0.5 $$ 这样x1,y1就是我们最新获得的值,其取值范围均为[-0.5,0.5]。 为了实现云台始终追随目标,我们还需要将得到的坐标值转换为舵机旋转的角度,本实验云台为二自由度云台,如图1.1。下面的舵机控制偏航角与相机x轴相关,上面的舵机负责控制俯仰角与相机y轴相关,偏航角舵机的机械转角范围为[0,180],其中,当角度为0时,舵机朝向右侧,角度为180度时,舵机朝向左侧。俯仰角的机械转角范围为[90,180],其中,当角度为90度时,平台成水平,当角度为180度时,平台垂直水平面。 <img src="readme.imgs/1-16497681807631.jpg" style="zoom: 25%;" /> ​ **图1.1** 算法上的实现,算法上的实现可以使用pd控制,pd控制较为稳定更适合舵机。我们已知(x1,y1)为当前目标的坐标,目的是将其移动到镜头中央,那么目标点为(0,0),我们获得了x轴的偏差以及y轴的偏差error_x,error_y上一次x,y偏差为error_x_last,error_y_last。假设此时舵机角度为yaw_now,pitch_now,那么有简化版增量式pd算法为

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值