BIT數字影像處理大作業——純C++實作車道線檢測
- 本文禁止轉載,違者必究!
- 1. 前言:
- 2. 基本思路:
- 3. 灰度圖變換:
- 4. 直方圖均衡化:
- 5. 閾值分割:
- 6. 中值濾波:
- 7. 邊緣檢測:
- 8. 直線檢測:
- 9. 后續思路:
- 獲取完整專案代碼:
本文禁止轉載,違者必究!
1. 前言:
沒錯這又是大作業,這次是數字影像處理的作業:


本來打算用 LaneNet 搞定,結果發現要求是:
- 不能用神經網路(即不能用影像分割演算法了);
- 除讀取和顯示影像,不能用Opencv(即不能調包);
所以只能老老實實手寫那些影像處理代碼了,
2. 基本思路:
目前的基本思路如下:
- 將影像轉到灰度圖;
- 使用直方圖均衡化預處理影像;
- 使用閾值分割方法繪制車道線二值圖;
- 對二值圖使用中值濾波去除噪點;
- 對二值圖使用邊緣檢測算子進行濾波操作;
- 使用霍夫變換進行直線檢測;
- 使用聚類演算法聚集直線束(還沒寫);
原圖:

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,但是我們也錯過了一些其他的直線,
后續的思路可以觀察這張圖:

- 我們可以減小中值濾波器大小,二而使用自定義算子去去除水平的干擾線;
- 對不同的連通區域分別生成二值圖,然后分別做霍夫變換,防止交叉線的產生;
- 暫時還沒有別的思路了,
獲取完整專案代碼:
感興趣的同學關注我的公眾號——可達鴨的深度學習教程,回復“車道線”獲取完整Visual Studio專案:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/254937.html
標籤:其他
