STM32F103C8T6搭配OV7725摄像头,如何用HSL颜色空间精准识别红色小球并串口输出坐标

张开发
2026/4/21 16:28:32 15 分钟阅读

分享文章

STM32F103C8T6搭配OV7725摄像头,如何用HSL颜色空间精准识别红色小球并串口输出坐标
STM32F103C8T6与OV7725摄像头实现红色小球追踪的工程实践在嵌入式视觉应用中颜色物体追踪是一个基础但极具实用价值的技术方向。本文将详细介绍如何利用STM32F103C8T6微控制器搭配OV7725摄像头模块通过HSL颜色空间实现红色小球的精准识别并稳定输出其中心坐标的完整工程方案。1. 硬件系统搭建与初始化配置1.1 硬件选型与连接STM32F103C8T6作为一款性价比极高的Cortex-M3内核微控制器其丰富的外设接口和适中的处理能力非常适合嵌入式视觉应用。OV7725则是一款30万像素的CMOS图像传感器支持VGA分辨率输出通过FIFO缓存可以有效减轻主控的处理负担。关键硬件连接包括SCCB接口用于摄像头寄存器配置SIOC连接PB10SIOD连接PB11数据接口8位并行数据总线连接PE0-PE7控制信号VSYNC连接PA8HREF连接PA9PCLK连接PA10FIFO控制信号连接PB12-PB151.2 摄像头初始化配置OV7725的初始化需要通过SCCB接口配置其内部寄存器。以下是几个关键配置参数寄存器地址配置值功能说明0x120x80复位所有寄存器0x3D0x03设置输出格式为RGB5650x150x02设置VSYNC和HREF极性0x110x01设置内部时钟分频void OV7725_Init(void) { SCCB_WriteReg(0x12, 0x80); // 软复位 delay_ms(100); SCCB_WriteReg(0x12, 0x00); // 退出复位 SCCB_WriteReg(0x3D, 0x03); // RGB565输出 SCCB_WriteReg(0x15, 0x02); // VSYNC和HREF极性 // 其他必要寄存器配置... }2. 图像采集与预处理2.1 FIFO数据读取机制OV7725通过FIFO缓存图像数据主控可以按帧读取大大降低了实时性要求。典型的数据读取流程如下检测VSYNC下降沿表示新帧开始复位FIFO写指针等待HREF变高开始读取行数据在PCLK上升沿读取8位数据重复直到帧结束void Capture_Frame(void) { while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)); // 等待VSYNC变低 OV7725_WRST(0); // 复位FIFO写指针 OV7725_WRST(1); OV7725_WEN(1); // 允许写入FIFO for(int row0; row240; row) { while(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_9)); // 等待HREF变高 for(int col0; col320; col) { while(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10)); // 等待PCLK上升沿 pixel_data GPIOE-IDR 0x00FF; // 读取数据 } } }2.2 RGB565转HSL颜色空间HSL色相、饱和度、亮度颜色空间比RGB更接近人类对颜色的感知特别适合颜色识别应用。转换过程包括将RGB565转换为RGB888从RGB888计算HSL值关键转换公式色相H计算H \begin{cases} 0° \text{if } \max \min \\ 60° \times \frac{g-b}{\max-\min} 0° \text{if } \max r \\ 60° \times \frac{b-r}{\max-\min} 120° \text{if } \max g \\ 60° \times \frac{r-g}{\max-\min} 240° \text{if } \max b \end{cases}饱和度S计算S \begin{cases} 0 \text{if } \max 0 \\ \frac{\max - \min}{\max} \text{otherwise} \end{cases}亮度L计算L \frac{\max \min}{2}实现代码typedef struct { uint8_t H; // 色相 [0,240] uint8_t S; // 饱和度 [0,240] uint8_t L; // 亮度 [0,240] } HSL_Color; void RGB565_to_HSL(uint16_t rgb565, HSL_Color *hsl) { // 提取RGB分量 uint8_t r (rgb565 11) 0x1F; uint8_t g (rgb565 5) 0x3F; uint8_t b rgb565 0x1F; // 转换为0-255范围 r (r 3) | (r 2); g (g 2) | (g 4); b (b 3) | (b 2); // 计算HSL uint8_t max MAX3(r, g, b); uint8_t min MIN3(r, g, b); // 亮度计算 hsl-L (max min) / 2; if(max min) { hsl-H 0; hsl-S 0; } else { // 饱和度计算 if(hsl-L 128) { hsl-S 255 * (max - min) / (max min); } else { hsl-S 255 * (max - min) / (510 - max - min); } // 色相计算 if(max r) { hsl-H 43 * (g - b) / (max - min); } else if(max g) { hsl-H 85 43 * (b - r) / (max - min); } else { hsl-H 171 43 * (r - g) / (max - min); } } }3. 红色小球的识别算法3.1 HSL阈值设定红色在HSL空间中的典型值范围为色相H0-20或220-240考虑色相环的连续性饱和度S100排除灰暗色调亮度L50-200排除过暗或过亮区域实际应用中需要通过实验确定最佳阈值typedef struct { uint8_t H_min; uint8_t H_max; uint8_t S_min; uint8_t S_max; uint8_t L_min; uint8_t L_max; } ColorThreshold; const ColorThreshold red_threshold { .H_min 220, .H_max 20, // 注意H_max H_min表示跨0°的情况 .S_min 100, .S_max 240, .L_min 50, .L_max 200 }; int is_red_color(HSL_Color *hsl) { // 检查饱和度和亮度 if(hsl-S red_threshold.S_min || hsl-S red_threshold.S_max || hsl-L red_threshold.L_min || hsl-L red_threshold.L_max) { return 0; } // 检查色相 if(red_threshold.H_min red_threshold.H_max) { return (hsl-H red_threshold.H_min hsl-H red_threshold.H_max); } else { return (hsl-H red_threshold.H_min || hsl-H red_threshold.H_max); } }3.2 基于腐蚀算法的目标定位腐蚀中心算法通过迭代收缩目标边界来精确定位物体中心具体步骤初始搜索在图像中扫描寻找可能的目标区域腐蚀迭代从初始点向四周扩展确定目标边界中心计算根据边界坐标计算中心位置算法实现关键点typedef struct { uint16_t x; uint16_t y; uint16_t width; uint16_t height; } ObjectInfo; int find_red_object(uint8_t image[240][40], ObjectInfo *obj) { static uint16_t last_x 120, last_y 160; // 上次找到的位置 uint16_t left, right, top, bottom; uint8_t found 0; // 从上次位置附近开始搜索 for(int y MAX(0, last_y-50); y MIN(240, last_y50); y) { for(int x MAX(0, last_x-50); x MIN(320, last_x50); x) { if(is_red_pixel(image, x, y)) { // 找到红色像素开始腐蚀算法 left right x; top bottom y; // 向左腐蚀 while(left 0 is_red_pixel(image, left-1, y)) left--; // 向右腐蚀 while(right 319 is_red_pixel(image, right1, y)) right; // 向上腐蚀 while(top 0 is_red_pixel(image, x, top-1)) top--; // 向下腐蚀 while(bottom 239 is_red_pixel(image, x, bottom1)) bottom; // 检查区域是否合理 if((right - left) 10 (bottom - top) 10) { obj-x (left right) / 2; obj-y (top bottom) / 2; obj-width right - left; obj-height bottom - top; last_x obj-x; last_y obj-y; return 1; } } } } return 0; }4. 系统优化与稳定性提升4.1 光照自适应算法环境光照变化会影响颜色识别的准确性实现自适应阈值调整void adaptive_threshold(uint8_t image[240][40], ColorThreshold *th) { uint32_t avg_l 0; uint32_t pixel_count 0; // 计算图像平均亮度 for(int y 0; y 240; y) { for(int x 0; x 320; x) { if(is_red_pixel(image, x, y)) { HSL_Color hsl; RGB565_to_HSL(get_pixel(x, y), hsl); avg_l hsl.L; pixel_count; } } } if(pixel_count 0) { avg_l / pixel_count; // 根据平均亮度调整阈值 if(avg_l 80) { // 低光照环境 th-L_min 30; th-L_max 150; th-S_min 80; } else if(avg_l 160) { // 高光照环境 th-L_min 80; th-L_max 220; th-S_min 120; } else { // 正常光照 th-L_min 50; th-L_max 200; th-S_min 100; } } }4.2 坐标滤波与输出为消除检测抖动采用移动平均滤波处理坐标数据#define FILTER_WINDOW 5 typedef struct { uint16_t x_buf[FILTER_WINDOW]; uint16_t y_buf[FILTER_WINDOW]; uint8_t index; } CoordinateFilter; void filter_coordinate(CoordinateFilter *filter, uint16_t x, uint16_t y, uint16_t *filtered_x, uint16_t *filtered_y) { // 更新缓冲区 filter-x_buf[filter-index] x; filter-y_buf[filter-index] y; filter-index (filter-index 1) % FILTER_WINDOW; // 计算平均值 uint32_t sum_x 0, sum_y 0; for(int i 0; i FILTER_WINDOW; i) { sum_x filter-x_buf[i]; sum_y filter-y_buf[i]; } *filtered_x sum_x / FILTER_WINDOW; *filtered_y sum_y / FILTER_WINDOW; } void send_coordinate(uint16_t x, uint16_t y) { uint8_t buffer[4]; buffer[0] (x 8) 0xFF; // X高字节 buffer[1] x 0xFF; // X低字节 buffer[2] (y 8) 0xFF; // Y高字节 buffer[3] y 0xFF; // Y低字节 for(int i 0; i 4; i) { while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, buffer[i]); } }4.3 性能优化技巧区域检测优化只在运动区域或上一帧目标附近检测减少计算量分辨率调整适当降低检测分辨率提高速度内存管理合理使用DMA传输图像数据void optimize_detection_area(ObjectInfo *last_obj) { // 如果上一帧找到目标则只在目标周围区域检测 if(last_obj-width 0) { search_area.x_start MAX(0, last_obj-x - last_obj-width); search_area.x_end MIN(320, last_obj-x last_obj-width); search_area.y_start MAX(0, last_obj-y - last_obj-height); search_area.y_end MIN(240, last_obj-y last_obj-height); } else { // 全图检测 search_area.x_start 0; search_area.x_end 320; search_area.y_start 0; search_area.y_end 240; } }5. 实际应用与调试技巧5.1 系统集成流程硬件连接检查确认所有信号线连接正确检查电源稳定性摄像头初始化验证通过调试接口读取寄存器值确认图像数据格式正确颜色识别调试使用单色卡片测试阈值调整HSL范围参数坐标输出验证通过串口调试助手查看数据检查坐标变化是否平滑5.2 常见问题解决问题现象可能原因解决方案图像全黑摄像头未正确初始化检查SCCB通信和寄存器配置颜色识别不稳定HSL阈值设置不当使用实际场景样本重新校准坐标输出抖动缺乏滤波处理增加移动平均滤波或卡尔曼滤波帧率过低处理算法复杂优化检测区域或降低分辨率5.3 进阶扩展方向多目标追踪扩展算法支持同时识别多个红色小球轨迹预测基于历史坐标预测未来位置无线传输通过蓝牙或WiFi模块传输坐标数据机械控制将坐标转换为舵机控制信号实现自动跟踪// 多目标识别示例框架 #define MAX_OBJECTS 3 typedef struct { ObjectInfo objects[MAX_OBJECTS]; uint8_t count; } MultiObjectResult; void find_multiple_objects(uint8_t image[240][40], MultiObjectResult *result) { result-count 0; uint8_t marked[240][40] {0}; for(int y 0; y 240; y) { for(int x 0; x 320; x) { if(!marked[y][x/8] is_red_pixel(image, x, y)) { if(result-count MAX_OBJECTS) { // 使用泛洪填充算法标记连通区域 flood_fill(image, marked, x, y, result-objects[result-count]); result-count; } } } } }在实际项目中这套系统已经成功应用于智能小车追踪、工业分拣机器人等场景。一个关键经验是在室内环境中使用漫反射光源可以显著提高颜色识别的稳定性而对于户外应用则需要更复杂的光照补偿算法。

更多文章