实验思路
通过将垂直投影与一维滤波相减与一阈值比较来记录LR,找到LR的中点即为可能存在于行道线上的点,将这些点进行霍夫变换,得到一条落点最多的极坐标曲线,即为行道线所在的线。
实验代码
- 计算投影:
void CalProj(BYTE *pImg, int width, int height,int *projOrg)
{
int realHeight = height / 15;
int x, y;
BYTE *pRow, *pCol;
memset(projOrg, 0, sizeof(int)*width);
for(pRow=pImg, y=0;y<realHeight;y++,pRow++)
for (x = 0, pCol = pRow; x < width; x++, pCol++)
{
projOrg[x] += *pCol;
}
for (x = 0; x < width; x++)
{
projOrg[x] /= realHeight;
}
}
因为要将将图像分块处理,所以计算时只能计算一部分的投影,将每列相加后归一 化即得到垂直投影图。
2. 一维均值滤波
void AverFilter(int *pData, int width,int windowLength,int *pResult)
{
int i, j, sum;
int halfWindow = windowLength / 2;
memset(pResult, 0, sizeof(int)*width);
for (i = halfWindow; i <= width - halfWindow; i++)
{
sum = 0;
for (j = i - halfWindow; j <= i + halfWindow; j++)
{
sum += pData[j];
}
pResult[i] = sum / windowLength;
}
}
这里直接将窗口内求平均然后赋值,然后用垂直投影图减去滤波后的投影图,对这个图求阈值
3. Otsu求阈值
int Otsu(int *hist)
{
int threshold;
int gmin,gmax;
gmin = 0;
gmax = 256;
int sum = 0, size = 0;
float u1 = 0, u2 = 0, u1sum = 0, w1 = 0, w2 = 0, dist, distMax = 0;
while (hist[gmax] == 0)
--gmax;
while (hist[gmin] == 0)
++gmin;
for (int i = gmin; i < gmax; i++)
{
sum += hist[i] * i;
size += hist[i];
}
for (int i = gmin; i < gmax; i++)
{
dist = 0;
w1 += hist[i];
u1sum += i * hist[i];
u1 = u1sum / w1;
w2 = size - w1;
u2 = (sum - u1sum) / w2;
dist = w1 * w2*pow(u1 - u2, 2);
if (dist > distMax)
{
distMax = dist;
threshold = i;
}
}
return threshold;
}
求阈值时先根据直方图求出全图的最大、最小像素值,这样可以缩小搜索范围,计算w2、u2时可以通过整体减去w1、u1来求得可以加快速度,最终返回这个阈值。
4. 找到行道线的中点
int GetMid(int *pProjOrg, int *pProjFilt,int width)
{
bool isBegin;
int hist[256];
int midsum = 0;
int begin[32], end[32],mid[32];
memset(hist, 0, sizeof(int) * 256);
int num;
for (int i = 0; i < width; i++)
{
num = abs(pProjOrg[i] - pProjFilt[i]);
hist[num]++;
}
int threshold = Otsu(hist);
for (int i = 0, isBegin = true; i < width; i++)
{
num = pProjOrg[i] - pProjFilt[i];
if (num > threshold)
{
if (isBegin)
{
begin[midsum] = i;
}
isBegin = false;
}
else
{
if (!isBegin)
{
end[midsum] = i - 1;
midsum++;
}
isBegin = true;
}
mid[midsum] = (end[midsum] - begin[midsum]) / 2;
}
return mid[32];
}
当投影与投影的滤波相减大于阈值后记录为行道线的起点,当差小于阈值后记录为终点,将中点计算出记录进一个数组中,返回重点的数组。
5. 图像分块处理
void Divide(BYTE *pImg, int width,int height)
{
int *pProj, *pProjFilt;
int block = width * height / 15;
int mid[32], allMid[32][15];
for (int i = 0; i < 15; i++)
{
CalProj(pImg+i*block, width, height, pProj);
AverFilter(pProj, width, 3, pProjFilt);
mid[32] = GetMid(pProj, pProjFilt, width);
for (int j = 0; j < 32; j++)
{
allMid[j][i] = mid[j];
}
}
}
将图像分成15块,对每一块进行求垂直投影、滤波、求这一块的中点数组的过程,并将所有中点数组存进二维数组,第一维记录所有的中点值,第二维记录这些值所在哪一块。
6. 霍夫变换
void houghTransform(int mid[32][15])
{
const double pi = 3.14;
double LUTsin[91], LUTcos[91];
int rou, theat;
int count1[533][91];
int count2[533][91];
for (int i = 0; i <= 90; i++)
{
float cur = i * pi / 180;
LUTsin[i] = sin(cur);
LUTcos[i] = cos(cur);
}
int x, int y;
for(int i=0;i<32;i++)
for (int j = 0; j < 15; j++)
{
x = mid[i][j];
y = (j+1)*23;
if (x <= 452)//图像分左右两块处理
{
for (int theat = 1; theat < 90; theat++)
{
rou = x * LUTcos[theat] + y * LUTsin[theat];
if (rou < 553 && rou>0)
{
count1[rou][theat]++;
}
}
}
if (x > 452)
{
for (int theat = 1; theat < 90; theat++)
{
rou = x * LUTcos[theat] + y * LUTsin[theat];
if (rou < 553 && rou>0)
{
count2[rou][theat]++;
}
}
}
}
int max1 = 0;
int max1_rou = 0, max1_theat = 0;
max1 = count1[0][0];
for (int i = 1; i < 553; i++)
for (int j = 1; j < 90; j++)
{
if (count1[i][j] > max1)
{
max1 = count1[i][j];
max1_rou = i;
max1_theat = j;
}
}
int max2 = 0;
int max2_rou = 0, max2_theat = 0;
max2 = count2[0][0];
for (int i = 1; i < 553; i++)
for (int j = 1; j < 90; j++)
{
if (count2[i][j] > max2)
{
max2 = count2[i][j];
max2_rou = i;
max2_theat = j;
}
}
int k1, k2, b1, b2;
exchange(max1_rou, max1_theat, &k1, &b1);
exchange(max2_rou, max2_theat, &k2, &b2);
}
将二维数组中的值转换为x、y进行霍夫变换,并对图像分左右处理,因为霍夫变换投票前两名的结果会很接近,其实是一边的行道线,所以分左右处理,得到两边的极坐标方程的参数转换为直角坐标画出如图:
总结
这次实验让我学习到了在处理实际问题中,代码对于适应不同环境的能力的重要性,之前觉得对于道路的照片直接进行二值化也可以得到行道线的位置,但没有考虑光照等的因素,这样在天黑的时候二值化就什么也得不到了,但是通过计算投影及投影的滤波的差就可以很好地解决光线过暗的问题,但我在求左右两条行道线的时候把图像分成了两块,这样似乎也是不妥的,因为车遇到拐弯的时候同一条行道线可能会在左右两半图像中出现,这样就不能识别为一条线了,但是霍夫变换的投票又不能只取前两名,应该可以设定一个阈值,将投票结果高的都显示出来,这样应该能显示出图像上很多线段,但在例图的情况下还是分块处理效果比较好。