原文于2023.10.25发布于本人CSDN主页,现同步至cnblogs
1 普通二值化的缺点
当场地光线不均时,基础的固定阈值二值化无法适应光线的变化。此外,对大量与道路无关的像素点进行二值化处理会造成算力的浪费。
2 使用差比和
对于MT9V03X拍摄的画面,其由0-255级灰度的像素点组成。二值化的思路是把浅色的前景部分变为255(白色),深色的背景部分变为0(黑色),所有255与0的分界线便是提取的道路边线。
那么使用差比和,则无需对图像进行变化,而是直接辨识浅色部分与深色部分的交界处。
关于差比和的原理,可参考视频:
18届智能车软件培训之差和比扫线
可以直接使用差比和,也可以先进行其他处理后再使用差比和。
参考代码如下:
Main.c
#include "zf_common_headfile.h"
#include "camera.h"
#include "screen.h"
#include "track.h"
#pragma section all "cpu0_dsram"
#define IPS200_TYPE (IPS200_TYPE_SPI)
int core0_main(void)
{
clock_init(); // 获取时钟频率<务必保留>
debug_init(); // 初始化默认调试串口
//外设初始化代码
ips200_init(IPS200_TYPE);
ips200_show_string(0, 0, "mt9v03x init.");
while(1)
{
if(mt9v03x_init())
ips200_show_string(0, 80, "mt9v03x reinit.");
else
break;
system_delay_ms(500); // 短延时快速闪灯表示异常
}
ips200_show_string(0, 16, "init success.");
//外设初始化代码
cpu_wait_event_ready(); // 等待所有核心初始化完毕
while (TRUE)
{
//循环执行的代码
if(mt9v03x_finish_flag)
{
image_boundary_process();
switch_trackline();
show_line();
ips200_show_uint(0, 200, leftline_num, 3);
ips200_show_uint(0, 216, rightline_num, 3);
ips200_displayimage03x((const uint8 *)mt9v03x_image, MT9V03X_W, MT9V03X_H); // 显示原始图像
//ips200_show_gray_image(0, 188, (const uint8 *)mt9v03x_image, MT9V03X_W, MT9V03X_H, 240, 180, 64); // 显示二值化图像
mt9v03x_finish_flag = 0;
}
//循环执行的代码
}
}
#pragma section all restore
camera.c
/*
* camera.c
*
* Created on: 2023年10月24日
* Author: lychee
*/
#include "camera.h"
int16 centerline[MT9V03X_H];
int16 leftline[MT9V03X_H];
int16 rightline[MT9V03X_H];
uint8 leftline_num;//左线点数量
uint8 rightline_num;//右线点数量
int16 sar_thre = 17;//差比和阈值
uint8 pix_per_meter = 20;//每米的像素数
//逐行寻找边界点
void image_boundary_process(void){
uint8 row;//行
//uint8 col = MT9V03X_W/2;//列
uint8 start_col = MT9V03X_W / 2;//各行起点的列坐标,默认为MT9V03X_W / 2
//清零之前的计数
leftline_num = 0;
rightline_num = 0;
for(row = MT9V03X_H - 1; row >= 1; row--){
//选用上一行的中点作为下一行计算起始点,节省速度,同时防止弯道的左右两边均出现与画面一侧
if(row != MT9V03X_H - 1){
start_col = (uint8)(0.4 * centerline[row] + 0.3 * start_col + 0.1 * MT9V03X_W);//一阶低通滤波,防止出现噪点影响下一行的起始点
}
else if(row == MT9V03X_H - 1){
start_col = MT9V03X_W / 2;
}
//逐行作差比和
difsum_left(row,start_col);
difsum_right(row,start_col);
centerline[row] = 0.5 * (rightline[row] + leftline[row]);
}
}
//差比和寻找左侧边界点
void difsum_left(uint8 y,uint8 x){
float sum,dif,sar;//和,差,比
uint8 col;//列
uint8 mov = 2;//每次作差后的移动量,默认为2,可以根据画面分辨率调整
//计算第x行的左边界
leftline[y] = 0;//未找到左边界时输出为0
for(col = x; col >= mov + 1; col -= mov){
dif = (float)((mt9v03x_image[y][col] - mt9v03x_image[y][col - mov - 1])<<8);//左移8位即乘256,可避免浮点数乘,加快速度
sum = (float)((mt9v03x_image[y][col] + mt9v03x_image[y][col - mov - 1]));
sar = fabs(dif / sum);//求取差比和
if(sar > sar_thre){//差比和大于阈值代表深浅色突变
leftline[y] = (int16)(col - mov);
leftline_num ++;//左线点计数+
break;//找到边界后退出
}
}
}
//差比和寻找右侧边界点
void difsum_right(uint8 y,uint8 x){
float sum,dif,sar;//和,差,比
uint8 col;//列
uint8 mov = 2;//每次作差后的移动量,默认为2,可以根据画面分辨率调整
//计算第x行的左边界
rightline[y] = MT9V03X_W - 1;//未找到右边界时输出为187
for(col = x; col <= MT9V03X_W - mov - 1; col += mov){
dif = (float)((mt9v03x_image[y][col] - mt9v03x_image[y][col + mov + 1])<<8);//左移8位即乘256,可避免浮点数乘,加快速度
sum = (float)((mt9v03x_image[y][col] + mt9v03x_image[y][col + mov + 1]));
sar = fabs(dif / sum);//求取差比和
if(sar > sar_thre){//差比和大于阈值代表深浅色突变
rightline[y] = (int16)(col + mov);
rightline_num ++;//右线点计数+
break;//找到边界后退出
}
}
}
camera.h
/*
* camera.h
*
* Created on: 2023年10月24日
* Author: lychee
*/
#ifndef CODE_CAMERA_H_
#define CODE_CAMERA_H_
#include "zf_common_headfile.h"
#include "track.h"
extern int16 centerline[MT9V03X_H];
extern int16 leftline[MT9V03X_H];
extern int16 rightline[MT9V03X_H];
extern uint8 leftline_num;//左线点数量
extern uint8 rightline_num;//右线点数量
extern int16 sar_thre;//差比和阈值
extern uint8 pix_per_meter;//每米的像素数
void image_boundary_process(void);
void difsum_left(uint8 y,uint8 x);
void difsum_right(uint8 y,uint8 x);
#endif /* CODE_CAMERA_H_ */
track.c
/*
* track.c
* Created on: 2023年10月24日
* Author: lychee
*/
#include "track.h"
int16 trackline[MT9V03X_H];//跟踪线
enum track_type_e track_type = TRACK_MID;//默认循中线
//将边线赋值给循迹跟踪线
void switch_trackline(void){
if(track_type == TRACK_MID){
for(int i = 0; i < MT9V03X_H;i ++){
trackline[i] = centerline[i];//循中线
}
}
if(track_type == TRACK_LEFT){
for(int i = 0; i < MT9V03X_H;i ++){
trackline[i] = (leftline[i] + pix_per_meter) > 187 ? 187 : (int16)(leftline[i] + 0.2 * pix_per_meter);//循左线,左线应向右侧偏移道路宽度一半,即0.2米对应的像素数
}
}
if(track_type == TRACK_RIGHT){
for(int i = 0; i < MT9V03X_H;i ++){
trackline[i] = ((rightline[i]) - pix_per_meter) < 0 ? 0 : (int16)(rightline[i] - 0.2 * pix_per_meter);//循左线,右线应向左侧偏移
}
}
}
//选择如何循线,大家可以自由发挥
void choose_tracktype(void){
//track_type = TRACK_LEFT;
}
track.h
/*
* track.h
*
* Created on: 2023年10月24日
* Author: lychee
*/
#ifndef CODE_TRACK_H_
#define CODE_TRACK_H_
#include "zf_common_headfile.h"
#include "camera.h"
extern int16 trackline[MT9V03X_H];
//定义巡线模式枚举类型
enum track_type_e {
TRACK_LEFT,//沿左边线
TRACK_RIGHT,//沿右边线
TRACK_MID,//沿中线
};
//定义当前循线模式
extern enum track_type_e track_type;
void switch_trackline(void);
void choose_tracktype(void);
#endif /* CODE_TRACK_H_ */
screen.c
/*
* screen.c
*
* Created on: 2023年10月24日
* Author: lychee
*/
#include "screen.h"
void show_line(void){
for(uint16 i = 0; i < MT9V03X_H; i = i + 2){
ips200_draw_point((uint16)trackline[i], i, RGB565_BLACK);//黑色跟踪
}
for(uint16 i = 0; i < MT9V03X_H; i ++){
ips200_draw_point((uint16)leftline[i], i, RGB565_RED);//红色左线
ips200_draw_point((uint16)rightline[i], i, RGB565_BLUE);//蓝色右线
ips200_draw_point((uint16)centerline[i], i, RGB565_PURPLE);//紫色中线
}
}
screen.h
/*
* screen.h
*
* Created on: 2023年10月24日
* Author: lychee
*/
#ifndef CODE_SCREEN_H_
#define CODE_SCREEN_H_
#include "zf_common_headfile.h"
#include "camera.h"
#include "track.h"
void show_line(void);
#endif /* CODE_SCREEN_H_ */
测试效果如上图,可见在左右两侧亮度相差较大的情况下,仍可正常寻线。
3 差比和的优缺点
差和比的计算只与灰度值的对比度有关,无关灰度值本身的大小,在不同光线情况下的适应能力更强。计算至边界点时退出循环,可避免在背景像素中浪费时间进行计算。
差比和仍需对每个前景像素进行计算,仍会造成一定浪费,寻线策略有待进一步优化。