智能车数字图像处理算法入门及C语言实现

https://www.bilibili.com/video/BV1eL411L7NR?spm_id_from=333.999.0.0&vd_source=d59d7bc22106d87da35c63b8af6491e8

图像简单处理附加提升

二值化 - 边缘提取 - 特征识别 - 补线 - 中心巡线 - 偏差曲率计算

图像二值化

0 - 255 >> 0 - 1
1. 固定阈值
最简单,不用
2. 动态阈值
由近向远逐行迭代阈值
3. 大津法
灰度直方图找谷底

数据示例

一行数据:
38 36 40 60 55 78 99 111 114 121 115 120 118 100 110 106 80 60 40 35 42 40 36

二值化:
0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0

边缘提取:
0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0

差比和计算偏差

if( (dat[0]-dat[1]/dat[0]+dat[1]) > 斜率阈值) dat[0]=1; dat[1]=1;

大津阈值法代码实现

// DMA 模块+外部中断结合,自动读取摄像头数据
#define pixel_num 4800
uchar image[pixel_num];	// 图像数组 60*80

// 大津法二值化
uint huidu[256]={0};	// 灰度直方图数组
uchar YUZHI;	// 二值化阈值

for(i=0; i<4800; i++)	// 遍历所有像素
{
	huidu[image[i]]++;	// 统计灰度直方图
}

uint H1=0, H2=0;	//第一、第二高峰海拔值
uchar D1=0, D2=0;	// 第一、第二高峰位置

#define KUAN 30	// 山峰宽度阈值

for(i=0; i<255; i++)	// 遍历灰度直方图
{
	if(huidu[i] > H1)	// 寻找最大值
	{
		H1 = huidu[i];	// 记录海拔
		D1 = i;	// 记录位置
	}
	
}

bit OK = 0;	// 是否找到第二高峰
/* 要找的是第二高峰,不是第二高点 */
for(i=H1-5; i>0; i-=5)	// 向下切,找第二高峰
{
	for(j=0; j<256; j++)	// 遍历这一行
	{
		if(huidu[j] > i && abs(j-D1) > KUAN)	// 有上面部分
		{
			H2 = i;	// 记录第二高峰海拔
			D2 = j;	// 记录第二高峰位置
			OK = 1;	// 标志置位
			break;
		}
	}
	if(OK)	break;	// 如果找到,直接跳出
}

uint L3=pixel_num;	// 山谷海拔值
uchar D3=0;	// 山谷位置
if(OK)	// 已经找到2座山
{
	if(D1<D2)	// 找山谷
	{
		for(i=D1; i<D2; i++)
		{
			if(huidu[i]<H3)	// 寻找最小值
			{
				H3 = huidu[i];
				D3 = i;
			}
		}
	}
	else
	{
		for(i=D2; i<D1; i++)
		{
			if(huidu[i]<H3)
			{
				H3 = huidu[i];
				D3 = i;
			}
		}
	}

	YUZHI = D3;	// 获取阈值
}

在这里插入图片描述

图像二值化代码实现

uchar image1[60][80];	// 二维图像临时处理数组1
uchar image2[60][80];	// 二维图像临时处理数组2

for(i=0; i<4800; i++)
{
	if(image[i]>YUZHI)	// 判断灰度值
	{
		image1[i/80][i%80] = 1;	// 白点
	}
	else
	{
		image1[i/80][i%80] = 0;	// 黑点
	}
}

图像处理

灵魂
通过关键点特征来判断
在这里插入图片描述

uchar AX, AY;	// A点
uchar BX, BY;	// B点
uchar CX, CY;	// C点
uchar DX, DY;	// D点

AY = 59;	// 获取 AB 点
BY = 59;
for(i=39; i>=1; i--)	// 从中间向左找上升
{
	if(image1[59][i] - image1[59][i-1] == 1// 找到上升沿 
	{
		AX = i;	// A 横坐标
	}
}

for(i=39; i<79; i++)	// 从中间向右找上升沿
{
	if(image1[59][i] - image1[59][i+1] == 1)	// 找到上升沿
	{
		BX = i;	// B 横坐标
	} 
}

CY = AY-1;	// 迭代 C 点
CX = AX-1;	// 去到下一行的边界黑点
for(i=CY; i>0; i--)	// 由近及远
{
	for(j=CX; j<80; j++)	// 由左向右
	{
		if(image[i][j] == 1)	// 找到白点
		{
			CX = j-1;	// 得到上一行黑点X位置
			break;
		}
	}
	if(image1[i-1][CX] == 1)	// 判断上方是否还有黑点
	{
		CY = i;	// 得到C点Y位置
		break;
	}
}

DY = BY-1;	// 迭代D点
DX = BX+1;	//	得到下面一行黑点 
for(i=DY; i>0; i--)	// 由近及远
{
	for(j=DX; j>0; j--)
	{
		if(image1[i][j] == 1)	// 找到白点
		{
			DX = j + 1;
			break;
		}
	}
	if(image1[i-1][DX] == 1)
	{
		DY = i;
		break;
	}
}

if(abs(CY-DY)<10) && CY > 30 && DY > 30)	// 初级判断十字路口 
{
	uchar Y = min(CY, DY);	// 获取CD高度较小值
	uchar HEI = 0;	// 十字路口上方区域黑点数量
	for(i=Y; i>Y-10; i-=2)	// Y抽点轮训
	{
		for(j=10; j<70; j+=5)	// X抽点轮训
		{
			if(image1[i][j] == 0)	// 如果有黑点
			{
				HEI++;	// 计数变量++
			}
		}
	}
	if(HEI < 10)	// 最终判断十字路口,并补线
	{
		float K;	// 补线斜率
		K = (CX-AX)/(CY-AY);	// 计算AC点斜率

		for(i=CY; i>CY-20; i--)	// 补AC延长2像素宽线
		{
			image1[i][CX+(CY-i)*K] = 0;	//把图像对应点涂黑
			image1[i][(CX+(CY-i)*K)-1] = 0;
		}

		K = (DX-BX)/(DY-BY);	// 计算AC点斜率

		for(i=DY; i>DY-20; i--)	// 补AC延长2像素宽线
		{
			image1[i][DX+(DY-i)*K] = 0;	//把图像对应点涂黑
			image1[i][(DX+(DY-i)*K)-1] = 0;
		}
	}
}

找中线代码实现

uchar ZHONGXIAN[60] = {39};	// 中线位置
uchar ZUO[60] = {0};	// 左线位置
ucahr YOU[60] = {79};	// 右线位置

// 先找最底下一行中心线
for(i=ZHONGXIAN[59]; i>=1; i--)	// 从中间向左找上升
{
	if(image1[59][i] - image1[59][i-1] == 1// 找到上升沿 
	{
		ZUO[59] = i;
	}
}

for(i=ZHONGJIAN[59]; i<79; i++)	// 从中间向右找上升沿
{
	if(image1[59][i] - image1[59][i+1] == 1)	// 找到上升沿
	{
		YOU[59] = i;	// 右线
	} 
}

ZHONGJIAN[59] = (ZUO[59] + YOU[59]) /2;	// 最底下一行中心线位置找到

完整实现

uchar ZHONGXIAN[60] = {39};	// 中线位置
uchar ZUO[60] = {0};	// 左线位置
ucahr YOU[60] = {79};	// 右线位置

for(i=59; i>=0; i--)	// 向上迭代中心线 ,从靠近摄像头到远离摄像头
{
	for(j=ZHONGJIAN[i]; j>=1; j--)	// 从中间向左找上升沿
	{
		if(image1[i][j] - image1[i][j-1] == 1)	// 找到上升沿
			ZUO[i] = j;	// 左线
	}
	for(j=ZHONGJIAN[i+1]; j<79; j++)	// 从中间向右找上升沿
	{
		if(image1[i][j] - image1[i][j+1] == 1)	// 找到上升沿
			YOU[i] = j;	// 右线
	}
	ZHONGJIAN[i] = (ZUO[i] + YOU[i]) / 2;	// 计算当前行中心点
}

求车身横向偏差

可以通过求曲率,太复杂这里不展示

 uchar QIANZHAN = 15;	// 摄像头前瞻
 uchar YUAN, ZHONG, JIN;	// 中线所在位置
 char ERR = 0;	// 前瞻偏差
 char YERR = 0;	// 车身横向偏差
 
 JIN = ZHONGJIAN[59];
 ZHONG = ZHONGJIAN[59-QIANZHAN];
 YUAN = ZHONGJIAN[59-QIANZHAN*2];
 
 /*  分情况讨论,右负左正 */
 if(YUAN<ZHONGJIAN && ZHONG < JIN)	// 情况1
 {
 	ERR = ( (ZHONG - YUAN ) + (JIN - ZHONG) )/2;	// 获取前瞻偏差
 }
 else if(YUAN < ZHONG && ZHONG >= JIN)	// 情况2
 {
 	ERR = JIN - ZHONG;	// 获取前瞻偏差
 }
 else if(YUAN >= ZHONG && ZHONG < JIN)	// 情况3
 {
 	ERR = JIN - ZHONG;	// 获取前瞻偏差
 }
 else
 {
 	ERR = ( (ZHONG - YUAN ) + (JIN - ZHONG) )/2;	// 获取前瞻偏差
 }

YERR = JIN - 39;	// 获取车身横向偏差

方向 PD 控制

PID参数随机编撰

float KP = 1.0;	// 方向控制前瞻比例系数
float KD = 1.0;	// 方向控制前瞻微分系数
float YKP = 1.0;	// 方向横向控制比例系数

float GYRO_Z;	// 车身Z轴角速度

#define DUOji_ZHONGZHI 840	// 前轮正方向的舵机占空值

uint DUOJI_PWM;	// 舵机PWM

GET_z = GET_GYRO(Z);	// 获取车身Z轴角速度

DUOJI_PWM = DUOJI_ZHONGZHI + KP*ERR + YKP*YERR - KD*GYRO_Z;	// 舵机参数计算

PWM_OUT(DUOJI_PWM);	// 控制舵机

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>