最近在做一个智能小车的项目,需要让小车自动识别黑色矩形区域并停在指定位置。经过一番调研,最终选择了STM32F103C8T6作为主控芯片,搭配OpenMV摄像头来实现这个功能。这个组合性价比很高,特别适合学生和创客群体。
STM32F103C8T6是ST公司的一款经典MCU,72MHz主频,64KB Flash,20KB RAM,完全够用。最重要的是它价格便宜,淘宝上十几块钱就能买到核心板。OpenMV则是一款开源的机器视觉模块,内置MicroPython解释器,可以直接运行图像处理算法,省去了自己移植OpenCV的麻烦。
硬件连接很简单:
这里有个小坑要注意:OpenMV的工作电压是3.3V,而有些STM32开发板默认是5V电平,记得要确认电平匹配,否则可能会烧坏OpenMV。
第一次使用OpenMV时,需要先安装OpenMV IDE。这个IDE基于Qt开发,支持Windows/Mac/Linux三大平台。安装过程很简单,但有几个关键点需要注意:
测试摄像头是否正常工作可以用这个简单脚本:
python复制import sensor, image, time
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time=2000)
while(True):
img = sensor.snapshot()
print("FPS:", clock.fps())
如果能看到实时图像和FPS数据,说明环境配置成功了。我实测下来,在QVGA分辨率下帧率能达到30FPS左右,完全够用。
OpenMV提供了find_rects()方法来检测矩形,但直接使用效果可能不太理想。经过多次调试,我总结出几个优化技巧:
优化后的完整代码如下:
python复制import sensor, image, time
sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE) # 灰度模式更快
sensor.set_framesize(sensor.QQVGA) # 160x120分辨率
sensor.skip_frames(time=2000)
# 镜头校正系数,需要根据实际镜头调整
lens_corr_param = 1.8
while(True):
img = sensor.snapshot().lens_corr(lens_corr_param)
# 检测矩形,threshold值需要根据场景调整
rects = img.find_rects(threshold=8000)
if len(rects) > 0:
# 找面积最大的矩形
max_rect = max(rects, key=lambda r: r.w() * r.h())
# 绘制矩形边框
img.draw_rectangle(max_rect.rect(), color=255)
# 获取四个角点坐标
corners = max_rect.corners()
print("Corners:", corners)
实际测试时发现,环境光线对检测效果影响很大。建议在固定场景下使用,或者增加自动曝光控制。
通信协议设计很关键,我采用了简单的自定义协议:
OpenMV端发送代码:
python复制import ustruct
from pyb import UART
uart = UART(3, 115200)
uart.init(115200, bits=8, parity=None, stop=1)
def send_corners(corners):
# 打包数据
data = bytearray()
for x,y in corners:
data.extend(ustruct.pack('<hh', x, y)) # 小端模式,short类型
# 发送完整帧
uart.write(bytearray([0xB3, 0xB3]))
uart.write(data)
uart.write(bytearray([0x0D, 0x0A]))
STM32端接收代码(HAL库):
c复制#define BUF_SIZE 64
uint8_t rx_buf[BUF_SIZE];
uint8_t rx_index = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
static uint8_t state = 0;
if(rx_buf[rx_index] == 0xB3) {
state++;
if(state == 2) {
// 收到帧头,准备接收数据
rx_index = 0;
return;
}
} else {
state = 0;
}
if(++rx_index >= BUF_SIZE) {
rx_index = 0;
}
HAL_UART_Receive_IT(huart, &rx_buf[rx_index], 1);
}
实际调试中发现,串口通信偶尔会出现数据错位。解决方法是在STM32端增加校验机制,如果数据长度不符合预期就直接丢弃。
联调阶段遇到了几个典型问题:
c复制#define FILTER_SIZE 5
int16_t x_history[FILTER_SIZE] = {0};
int16_t y_history[FILTER_SIZE] = {0};
uint8_t filter_index = 0;
void filter_coord(int16_t *x, int16_t *y) {
x_history[filter_index] = *x;
y_history[filter_index] = *y;
filter_index = (filter_index + 1) % FILTER_SIZE;
int32_t x_sum = 0, y_sum = 0;
for(int i=0; i<FILTER_SIZE; i++) {
x_sum += x_history[i];
y_sum += y_history[i];
}
*x = x_sum / FILTER_SIZE;
*y = y_sum / FILTER_SIZE;
}
这个系统最终用在了智能小车项目上,实现了以下功能:
关键的控制代码如下:
c复制void control_task() {
int16_t target_x = 80; // 目标位置中心x坐标
int16_t target_y = 60; // 目标位置中心y坐标
while(1) {
if(new_data_flag) { // 收到新坐标数据
new_data_flag = 0;
// 计算中心点坐标
int16_t center_x = (corner1_x + corner2_x + corner3_x + corner4_x) / 4;
int16_t center_y = (corner1_y + corner2_y + corner3_y + corner4_y) / 4;
// 计算偏差
int16_t err_x = center_x - target_x;
int16_t err_y = center_y - target_y;
// PID控制
motor_ctrl(pid_calc(&pid_x, err_x), pid_calc(&pid_y, err_y));
}
osDelay(10);
}
}
实测下来,系统定位精度能达到±2cm,完全满足项目需求。整个开发过程中最大的收获是:嵌入式视觉项目一定要考虑实时性和稳定性的平衡,算法不需要太复杂,够用就好。