BIT数字图像处理大作业——纯C++实现车道线检测

本文禁止转载,违者必究!

1. 前言:

没错这又是大作业,这次是数字图像处理的作业:
在这里插入图片描述
在这里插入图片描述

本来打算用 LaneNet 搞定,结果发现要求是:

  1. 不能用神经网络(即不能用图像分割算法了);
  2. 除读取和显示图像,不能用Opencv(即不能调包);

所以只能老老实实手写那些图像处理代码了。

2. 基本思路:

目前的基本思路如下:

  1. 将图像转到灰度图;
  2. 使用直方图均衡化预处理图像;
  3. 使用阈值分割方法绘制车道线二值图;
  4. 对二值图使用中值滤波去除噪点;
  5. 对二值图使用边缘检测算子进行滤波操作;
  6. 使用霍夫变换进行直线检测;
  7. 使用聚类算法聚集直线束(还没写);

原图:
在这里插入图片描述

3. 灰度图变换:

cv::Mat Color2Gray(cv::Mat src_image)
{
    
    
	cv::Mat gray_image(src_image.rows, src_image.cols, CV_8UC1);
	if (src_image.channels() != 1)
	{
    
    
		for (int i = 0; i < src_image.rows; i++)
			for (int j = 0; j < src_image.cols; j++)
				gray_image.at<uchar>(i, j) = (src_image.at<cv::Vec3b>(i, j)[0] + src_image.at<cv::Vec3b>(i, j)[1] + src_image.at<cv::Vec3b>(i, j)[2]) / 3;
	}
	else
		gray_image = src_image.clone();
	return gray_image;

效果:
在这里插入图片描述

4. 直方图均衡化:

cv::Mat equalize_hist(cv::Mat input) {
    
    
	cv::Mat output = input.clone();
	int gray_sum = input.cols * input.rows;
	int gray[256] = {
    
     0 };  //记录每个灰度级别下的像素个数
	double gray_prob[256] = {
    
     0 };  //记录灰度分布密度
	double gray_distribution[256] = {
    
     0 };  //记录累计密度
	int gray_equal[256] = {
    
     0 };  //均衡化后的灰度值
	//统计每个灰度下的像素个数
	for (int i = 0; i < input.rows; i++)
	{
    
    
		uchar* p = input.ptr<uchar>(i);
		for (int j = 0; j < input.cols; j++)
		{
    
    
			int vaule = p[j];
			gray[vaule]++;
		}
	}
	//统计灰度频率
	for (int i = 0; i < 256; i++)
	{
    
    
		gray_prob[i] = ((double)gray[i] / gray_sum);
	}
	//计算累计密度
	gray_distribution[0] = gray_prob[0];
	for (int i = 1; i < 256; i++)
	{
    
    
		gray_distribution[i] = gray_distribution[i - 1] + gray_prob[i];
	}

	//重新计算均衡化后的灰度值
	for (int i = 0; i < 256; i++)
	{
    
    
		gray_equal[i] = (uchar)(255 * gray_distribution[i] + 0.5);
	}
	//直方图均衡化,更新原图每个点的像素值
	for (int i = 0; i < output.rows; i++)
	{
    
    
		uchar* p = output.ptr<uchar>(i);
		for (int j = 0; j < output.cols; j++)
		{
    
    
			p[j] = gray_equal[p[j]];
		}
	}
	return output;
}

在这里插入图片描述

5. 阈值分割:

cv::Mat Image2Binary(cv::Mat src_image, int threshold, float init_h, float end_h) {
    
    
	int value;
	float start_i = init_h*float(src_image.rows);
	float end_i = end_h * float(src_image.rows);
	cv::Mat binary_image(src_image.rows, src_image.cols, CV_8UC1);
	for (int i = 0; i < src_image.rows; i++) {
    
    
		for (int j = 0; j < src_image.cols; j++) {
    
    
			value = src_image.at<uchar>(i, j);
			if (value > threshold && i > start_i && i < end_i) {
    
    
				binary_image.at<uchar>(i, j) = 255;
			}
			else {
    
    
				binary_image.at<uchar>(i, j) = 0;
			}
		}
	}
	return binary_image;
}

在这里插入图片描述

6. 中值滤波:

cv::Mat BiMedianBlur(cv::Mat src_image, int size) {
    
    
	int count_w, count_b, value, ds=(size-1)/2;
	cv::Mat result(src_image.rows, src_image.cols, CV_8UC1);
	for (int i = 0; i < src_image.rows; i++) {
    
    
		for (int j = 0; j < src_image.cols; j++) {
    
    
			count_w = 0;
			count_b = 0;
			for (int di = -ds; di < ds; di++) {
    
    
				for (int dj = -ds; dj < ds; dj++) {
    
    
					if (i + di >= 0 && j + dj >= 0) {
    
    
						if (i + di < src_image.rows && j + dj < src_image.cols) {
    
    
							value = src_image.at<uchar>(i+di, j+dj);
							if (value == 0) {
    
    
								count_b += 1;
							}
							else {
    
    
								count_w += 1;
							}
						}
					}
				}
			}
			if (count_b > count_w) {
    
    
				result.at<uchar>(i, j) = 0;
			}
			else {
    
    
				result.at<uchar>(i, j) = 255;
			}
		}
	}
	return result;
}

在这里插入图片描述

7. 边缘检测:

cv::Mat ConvLap(cv::Mat src_image, int thresh) {
    
    
	int value;
	cv::Mat result = cv::Mat::zeros(src_image.size(), CV_8U);
	for (int i = 1; i < src_image.rows - 1; i++) {
    
    
		for (int j = 1; j < src_image.cols - 1; j++) {
    
    
			value = -4 * src_image.at<uchar>(i, j);
			value += src_image.at<uchar>(i - 1, j);
			value += src_image.at<uchar>(i + 1, j);
			value += src_image.at<uchar>(i, j - 1);
			value += src_image.at<uchar>(i, j + 1);
			if (value < 0) {
    
    
				value = -value;
			}
			if (value > thresh) {
    
    
				value = 255;
			}
			else
			{
    
    
				value = 0;
			}
			result.at<uchar>(i, j) = value;
		}
	}
	return result;
}

在这里插入图片描述

8. 直线检测:

std::vector<float> hough_line_v(cv::Mat img, int threshold)
{
    
    
	int row, col;
	int i, k;
	//参数空间的参数极角angle(角度),极径p;
	int angle, p;

	//累加器
	int **socboard;
	int *buf;
	int w, h;
	w = img.cols;
	h = img.rows;
	int Size;
	int offset;
	std::vector<float> lines;
	//申请累加器空间并初始化
	Size = w * w + h * h;
	Size = 2 * sqrt(Size) + 100;
	offset = Size / 2;
	socboard = (int **)malloc(Size * sizeof(int*));
	if (!socboard)
	{
    
    
		printf("mem err\n");
		return lines;
	}

	for (i = 0; i < Size; i++)
	{
    
    
		socboard[i] = (int *)malloc(181 * sizeof(int));
		if (socboard[i] == NULL)
		{
    
    
			printf("buf err\n");
			return lines;
		}
		memset(socboard[i], 0, 181 * sizeof(int));
	}

	//遍历图像并投票
	int src_data;
	p = 0;
	for (row = 0; row < img.rows; row++)
	{
    
    
		for (col = 0; col < img.cols; col++)
		{
    
    
			//获取像素点
			src_data = img.at<uchar>(row, col);

			if (src_data == 255)
			{
    
    
				for (angle = 0; angle < 181; angle++)
				{
    
    
					p = col * cos(angle * PI / 180.0) + row * sin(angle * PI / 180.0) + offset;

					//错误处理
					if (p < 0)
					{
    
    
						printf("at (%d,%d),angle:%d,p:%d\n", col, row, angle, p);
						printf("warrning!");
						printf("size:%d\n", Size / 2);
						continue;
					}
					//投票计分
					socboard[p][angle]++;

				}
			}
		}
	}

	//遍历计分板,选出符合阈值条件的直线
	int count = 0;
	int Max = 0;
	int kp, kt, r;
	kp = 0;
	kt = 0;
	for (i = 0; i < Size; i++)//p
	{
    
    
		for (k = 0; k < 181; k++)//angle
		{
    
    
			if (socboard[i][k] > Max)
			{
    
    
				Max = socboard[i][k];
				kp = i - offset;
				kt = k;
			}

			if (socboard[i][k] >= threshold)
			{
    
    
				r = i - offset;
				//lines_w.push_back(std::);
				lines.push_back(-1.0 * float(std::cos(k*PI / 180) / std::sin(k*PI / 180)));
				lines.push_back(float(r)/std::sin(k*PI / 180));
				count++;
			}
		}
	}
	//释放资源
	for (int e = 0; e < Size; e++)
	{
    
    
		free(socboard[e]);
	}
	free(socboard);
	return lines;
}

在这里插入图片描述

9. 后续思路:

可以看到,上面的处理其实效果还ok,但是我们也错过了一些其他的直线。

后续的思路可以观察这张图:
在这里插入图片描述

  1. 我们可以减小中值滤波器大小,二而使用自定义算子去去除水平的干扰线;
  2. 对不同的连通区域分别生成二值图,然后分别做霍夫变换,防止交叉线的产生;
  3. 暂时还没有别的思路了。

获取完整项目代码:

感兴趣的同学关注我的公众号——可达鸭的深度学习教程,回复“车道线”获取完整Visual Studio项目:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44936889/article/details/113405936