opencv車道線檢測

opencv車道線檢測


完成的功能

  1. 圖像裁剪:經過設定圖像ROI區域,拷貝圖像得到裁剪圖像
  2. 反透視變換:用的是老師給的視頻,沒有對應的變換矩陣。因此創建二維座標,經過四點映射的方法計算矩陣,進行反透視變化。後因ROI區域的設置易形成變換矩陣獲取困難和插值像素獲得的透視圖效果不理想,故沒應用。
  3. 二值化:先變化爲灰度圖,而後設定閾值直接變成二值化圖像。
  4. 形態學濾波:對二值化圖像進行腐蝕,去除噪點;而後對圖像進行膨脹,彌補對車道線的腐蝕。
  5. 邊緣檢測:canny變化、sobel變化和laplacian變化中選擇了效果比較好的canny變化,三者在代碼中都可用,canny變化效果稍微好一點。
  6. 直線檢測:實現了兩種方法①使用opencv庫封裝好的霍夫直線檢測函數,在原圖對應區域用紅線描出車道線②本身寫了一種直線檢測,在頭文件中,遍歷ROI區域進行特定角度範圍的直線檢測。 兩種方法都可在視頻中體現,第一種方法運行效率較快。
  7. 按鍵控制:空格暫停,其他鍵退出,方便調試和截圖。

實現的效果

圖1
在亮度良好道路條件良好的狀況下,檢測車前區域的車道線實現比較成功,排除掉高速護欄的影響,並且原圖像還能完總體現。ios

圖2
車子行駛在高速公路大型彎道上,能夠在必定角度範圍內認定車道線還是直線,檢測出爲直線。ide

圖3
車子切換過程當中只有一根車道線被識別,可是穩定回變換車道後,實現效果良好。減速線爲黃色,二值化是也被過濾,沒形成影響。函數

圖4
圖5
剛進入隧道時,攝像機光源基本處於高光狀態,拍攝亮度基本不變,二值化圖像時狀況良好,噪聲比較多可是沒產生多大線狀影響;當攝像頭自動調節亮度,圖像亮度變低,二值化時同一閾值把車道線給過濾掉,形成沒法識別車道線的現象。ui

圖7
在道路損壞的狀況下,因爲閾值必定,基本上檢測不出車道線。spa

結論

實現的功能:實現了車道線檢測的基本功能,反透視變換矩陣實現了但效果不太理想,使用本身寫的直線檢測部分,車道線識別抗干擾能力較強。3d

缺點:整個識別系統都是固定的參數,只能在特定的環境產生良好的效果。指針

改進空間:提取所有關鍵參數,每次對ROI圖像進行快速掃描更新參數,不然使用默認參數。例如,能夠選擇每次5間隔取點,以像素最高點的85%做爲該次二值化的閾值。從而作到動態車道線識別。調試

完整代碼

方法一:
main.cppcode

#include<cv.h>
#include<cxcore.h>
#include<highgui.h>
#include"mylinedetect.h"

#include<cstdio>
#include<iostream>
using namespace std;

int main(){
    //聲明IplImage指針
    IplImage* pFrame = NULL;
    IplImage* pCutFrame = NULL;
    IplImage* pCutFrImg = NULL;
    //聲明CvCapture指針
    CvCapture* pCapture = NULL;
    //聲明CvMemStorage和CvSeg指針
    CvMemStorage* storage = cvCreateMemStorage();
    CvSeq* lines = NULL;
    //生成視頻的結構
    VideoWriter writer("result.avi", CV_FOURCC('M', 'J', 'P', 'G'), 25.0, Size(856, 480));
    //當前幀數
    int nFrmNum = 0;
    //裁剪的天空高度
    int CutHeight = 310;
    //窗口命名
    cvNamedWindow("video", 1);
    cvNamedWindow("BWmode", 1);
    //調整窗口初始位置
    cvMoveWindow("video", 300, 0);
    cvMoveWindow("BWmode", 300, 520);
    //不能打開則退出
    if (!(pCapture = cvCaptureFromFile("lane.avi"))){
        fprintf(stderr, "Can not open video file\n");
        return -2;
    }
    //每次讀取一楨的視頻
    while (pFrame = cvQueryFrame(pCapture)){
        //設置ROI裁剪圖像
        cvSetImageROI(pFrame, cvRect(0, CutHeight, pFrame->width, pFrame->height - CutHeight));
        nFrmNum++;
        //第一次要申請內存p
        if (nFrmNum == 1){
            pCutFrame = cvCreateImage(cvSize(pFrame->width, pFrame->height - CutHeight), pFrame->depth, pFrame->nChannels);
            cvCopy(pFrame, pCutFrame, 0);
            pCutFrImg = cvCreateImage(cvSize(pCutFrame->width, pCutFrame->height), IPL_DEPTH_8U, 1);
            //轉化成單通道圖像再處理
            cvCvtColor(pCutFrame, pCutFrImg, CV_BGR2GRAY);
        }
        else{
            //得到剪切圖
            cvCopy(pFrame, pCutFrame, 0);
#if 0       //反透視變換
            //二維座標下的點,類型爲浮點
            CvPoint2D32f srcTri[4], dstTri[4];
            CvMat* warp_mat = cvCreateMat(3, 3, CV_32FC1);
            //計算矩陣反射變換
            srcTri[0].x = 10;
            srcTri[0].y = 20;
            srcTri[1].x = pCutFrame->width - 5;
            srcTri[1].y = 0;
            srcTri[2].x = 0;
            srcTri[2].y = pCutFrame->height - 1;
            srcTri[3].x = pCutFrame->width - 1;
            srcTri[3].y = pCutFrame->height - 1;
            //改變目標圖像大小
            dstTri[0].x = 0;
            dstTri[0].y = 0;
            dstTri[1].x = pCutFrImg->width - 1;
            dstTri[1].y = 0;
            dstTri[2].x = 0;
            dstTri[2].y = pCutFrImg->height - 1;
            dstTri[3].x = pCutFrImg->width - 1;
            dstTri[3].y = pCutFrImg->height - 1;
            //得到矩陣
            cvGetPerspectiveTransform(srcTri, dstTri, warp_mat);
            //反透視變換
            cvWarpPerspective(pCutFrame, pCutFrImg, warp_mat);
#endif
            //前景圖轉換爲灰度圖
            cvCvtColor(pCutFrame, pCutFrImg, CV_BGR2GRAY);
            //二值化前景圖
            cvThreshold(pCutFrImg, pCutFrImg, 80, 255.0, CV_THRESH_BINARY);
            //進行形態學濾波,去掉噪音
            cvErode(pCutFrImg, pCutFrImg, 0, 2);
            cvDilate(pCutFrImg, pCutFrImg, 0, 2);
            //canny變化
            cvCanny(pCutFrImg, pCutFrImg, 50, 120);
            //sobel變化
            //Mat pCutFrMat(pCutFrImg);
            //Sobel(pCutFrMat, pCutFrMat, pCutFrMat.depth(), 1, 1);
            //laplacian變化
            //Laplacian(pCutFrMat, pCutFrMat, pCutFrMat.depth());
#if 1       //0爲下面的代碼,1爲上面的代碼
    #pragma region Hough直線檢測
            lines = cvHoughLines2(pCutFrImg, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 100, 15, 15);
            printf("Lines number: %d\n", lines->total);
            //畫出直線
            for (int i = 0; i<lines->total; i++){
                CvPoint* line = (CvPoint*)cvGetSeqElem(lines, i);
                double k = ((line[0].y - line[1].y)*1.0 / (line[0].x - line[1].x));
                cout<<"nFrmNum "<<nFrmNum<<" 's k = "<<k<<endl;
                if(!(abs(k)<0.1))//去掉水平直線
                    cvLine(pFrame, line[0], line[1], CV_RGB(255, 0, 0), 6, CV_AA);
            }
    #pragma endregion
#else
    #pragma region mylinedetect
            Mat edge(pCutFrImg);
            vector<struct line> lines = detectLine(edge, 60);
            Mat pFrameMat(pFrame);
            drawLines(pFrameMat, lines);
            namedWindow("mylinedetect", 1);
            imshow("mylinedetect", pFrameMat);
    #pragma endregion
#endif
            //恢復ROI區域
            cvResetImageROI(pFrame);
            //寫入視頻流
            writer << pFrame;
            //顯示圖像
            cvShowImage("video", pFrame);
            cvShowImage("BWmode", pCutFrImg);
            //按鍵事件,空格暫停,其餘跳出循環
            int temp = cvWaitKey(2);
            if (temp == 32){
                while (cvWaitKey() == -1);
            }
            else if (temp >= 0){
                break;
            }
        }
    }
    //銷燬窗口
    cvDestroyWindow("video");
    cvDestroyWindow("BWmode");
    //釋放圖像
    cvReleaseImage(&pCutFrImg);
    cvReleaseImage(&pCutFrame);
    cvReleaseCapture(&pCapture);

    return 0;
}

mylinedetect.horm

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <vector>
#include <cmath>
using namespace cv;
using namespace std;

const double pi = 3.1415926f;
const double RADIAN = 180.0 / pi;

struct line{
    int theta;
    int r;
};

vector<struct line> detectLine(Mat &img, int threshold){
    vector<struct line> lines;
    int diagonal = floor(sqrt(img.rows*img.rows + img.cols*img.cols));
    vector< vector<int> >p(360, vector<int>(diagonal));
    //統計數量
    for (int j = 0; j < img.rows; j++) {
        for (int i = 0; i < img.cols; i++) {
            if (img.at<unsigned char>(j, i) > 0){
                for (int theta = 0; theta < 360; theta++){
                    int r = floor(i*cos(theta / RADIAN) + j*sin(theta / RADIAN));
                    if (r < 0)
                        continue;
                    p[theta][r]++;
                }
            }
        }
    }
    //得到最大值
    for (int theta = 0; theta < 360; theta++){
        for (int r = 0; r < diagonal; r++){
            int thetaLeft = max(0, theta - 1);
            int thetaRight = min(359, theta + 1);
            int rLeft = max(0, r - 1);
            int rRight = min(diagonal - 1, r + 1);
            int tmp = p[theta][r];
            if (tmp > threshold
                && tmp > p[thetaLeft][rLeft] && tmp > p[thetaLeft][r] && tmp > p[thetaLeft][rRight]
                && tmp > p[theta][rLeft] && tmp > p[theta][rRight]
                && tmp > p[thetaRight][rLeft] && tmp > p[thetaRight][r] && tmp > p[thetaRight][rRight]){
                struct line newline;
                newline.theta = theta;
                newline.r = r;
                lines.push_back(newline);
            }
        }
    }
    return lines;
}

void drawLines(Mat &img, const vector<struct line> &lines){
    for (int i = 0; i < lines.size(); i++){
        vector<Point> points;
        int theta = lines[i].theta;
        int r = lines[i].r;

        double ct = cos(theta / RADIAN);
        double st = sin(theta / RADIAN);

        //公式 r = x*ct + y*st
        //計算左邊
        int y = int(r / st);
        if (y >= 0 && y < img.rows){
            Point p(0, y);
            points.push_back(p);
        }
        //計算右邊
        y = int((r - ct*(img.cols - 1)) / st);
        if (y >= 0 && y < img.rows){
            Point p(img.cols - 1, y);
            points.push_back(p);
        }
        //計算上邊
        int x = int(r / ct);
        if (x >= 0 && x < img.cols){
            Point p(x, 0);
            points.push_back(p);
        }
        //計算下邊
        x = int((r - st*(img.rows - 1)) / ct);
        if (x >= 0 && x < img.cols){
            Point p(x, img.rows - 1);
            points.push_back(p);
        }
        //畫線
        cv::line(img, points[0], points[1], Scalar(255, 0, 0), 5, CV_AA);
    }
}

方法二:

#include<cv.h>
#include<cxcore.h>
#include<highgui.h>

#include<cstdio>
#include<iostream>
using namespace std;

int main(){
    //聲明IplImage指針
    IplImage* pFrame = NULL;
    IplImage* pCutFrame = NULL;
    IplImage* pCutFrImg = NULL;
    IplImage* pCutBkImg = NULL;
    //聲明CvMat指針
    CvMat* pCutFrameMat = NULL;
    CvMat* pCutFrMat = NULL;
    CvMat* pCutBkMat = NULL;
    //聲明CvCapture指針
    CvCapture* pCapture = NULL;
    //聲明CvMemStorage和CvSeg指針
    CvMemStorage* storage = cvCreateMemStorage();
    CvSeq* lines = NULL;
    //當前幀數
    int nFrmNum = 0;
    //裁剪的天空高度
    int CutHeight = 250;
    //窗口命名
    cvNamedWindow("video", 1);
    //cvNamedWindow("background", 1);
    cvNamedWindow("foreground", 1);
    //調整窗口初始位置
    cvMoveWindow("video", 300, 30);
    cvMoveWindow("background", 100, 100);
    cvMoveWindow("foreground", 300, 370);
    //不能打開則退出
    if (!(pCapture = cvCaptureFromFile("lane.avi"))){
        fprintf(stderr, "Can not open video file\n");
        return -2;
    }
    //每次讀取一楨的視頻
    while (pFrame = cvQueryFrame(pCapture)){
        //設置ROI裁剪圖像
        cvSetImageROI(pFrame, cvRect(0, CutHeight, pFrame->width, pFrame->height - CutHeight));
        nFrmNum++;
        //第一次要申請內存p
        if (nFrmNum == 1){
            pCutFrame = cvCreateImage(cvSize(pFrame->width, pFrame->height - CutHeight), pFrame->depth, pFrame->nChannels);
            cvCopy(pFrame, pCutFrame, 0);
            pCutBkImg = cvCreateImage(cvSize(pCutFrame->width, pCutFrame->height), IPL_DEPTH_8U, 1);
            pCutFrImg = cvCreateImage(cvSize(pCutFrame->width, pCutFrame->height), IPL_DEPTH_8U, 1);
            
            pCutBkMat = cvCreateMat(pCutFrame->height, pCutFrame->width, CV_32FC1);
            pCutFrMat = cvCreateMat(pCutFrame->height, pCutFrame->width, CV_32FC1);
            pCutFrameMat = cvCreateMat(pCutFrame->height, pCutFrame->width, CV_32FC1);
            //轉化成單通道圖像再處理
            cvCvtColor(pCutFrame, pCutBkImg, CV_BGR2GRAY);
            cvCvtColor(pCutFrame, pCutFrImg, CV_BGR2GRAY);
            //轉換成矩陣
            cvConvert(pCutFrImg, pCutFrameMat);
            cvConvert(pCutFrImg, pCutFrMat);
            cvConvert(pCutFrImg, pCutBkMat);
        }
        else{
            //得到剪切圖
            cvCopy(pFrame, pCutFrame, 0);
            //前景圖轉換爲灰度圖
            cvCvtColor(pCutFrame, pCutFrImg, CV_BGR2GRAY);
            cvConvert(pCutFrImg, pCutFrameMat);
            //高斯濾波先,以平滑圖像
            cvSmooth(pCutFrameMat, pCutFrameMat, CV_GAUSSIAN, 3, 0, 0.0);
            //當前幀跟背景圖相減
            cvAbsDiff(pCutFrameMat, pCutBkMat, pCutFrMat);
            //二值化前景圖
            cvThreshold(pCutFrMat, pCutFrImg, 35, 255.0, CV_THRESH_BINARY);
            //進行形態學濾波,去掉噪音
            cvErode(pCutFrImg, pCutFrImg, 0, 1);
            cvDilate(pCutFrImg, pCutFrImg, 0, 1);
            //更新背景
            cvRunningAvg(pCutFrameMat, pCutBkMat, 0.003, 0);
            //pCutBkMat = cvCloneMat(pCutFrameMat);
            //將背景轉化爲圖像格式,用以顯示
            //cvConvert(pCutBkMat, pCutBkImg);
            cvCvtColor(pCutFrame, pCutBkImg, CV_BGR2GRAY);
            //canny變化
            cvCanny(pCutFrImg, pCutFrImg, 50, 100);
            #pragma region Hough檢測
            lines = cvHoughLines2(pCutFrImg, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 100, 30, 15);
            printf("Lines number: %d\n", lines->total);
            //畫出直線
            for (int i = 0; i<lines->total; i++){
                CvPoint* line = (CvPoint* )cvGetSeqElem(lines, i);
                cvLine(pCutFrame, line[0], line[1], CV_RGB(255, 0, 0), 6, CV_AA);
            }
            #pragma endregion
            //顯示圖像
            cvShowImage("video", pCutFrame);
            cvShowImage("background", pCutBkImg);
            cvShowImage("foreground", pCutFrImg);
            //按鍵事件,空格暫停,其餘跳出循環
            int temp = cvWaitKey(2);
            if (temp == 32){
                while (cvWaitKey() == -1);
            }
            else if (temp >= 0){
                break;
            }
        }
        //恢復ROI區域(多餘可去掉)
        cvResetImageROI(pFrame);
    }
    //銷燬窗口
    cvDestroyWindow("video");
    cvDestroyWindow("background");
    cvDestroyWindow("foreground");
    //釋放圖像和矩陣
    cvReleaseImage(&pCutFrImg);
    cvReleaseImage(&pCutBkImg);
    cvReleaseImage(&pCutFrame);
    cvReleaseMat(&pCutFrameMat);
    cvReleaseMat(&pCutFrMat);
    cvReleaseMat(&pCutBkMat);
    cvReleaseCapture(&pCapture);

    return 0;
}
相關文章
相關標籤/搜索