车道线检测

方案一:

(1)参考链接:数字图像处理:基于霍夫变换的车道线检测

(2)代码: (python)

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

# 灰度图转换
def grayscale(image):
    return cv.cvtColor(image, cv.COLOR_RGB2GRAY)

# Canny边缘检测
def canny(image, low_threshold, high_threshold):
    return cv.Canny(image, low_threshold, high_threshold)

# 高斯滤波
def gaussian_blur(image, kernel_size):
    return cv.GaussianBlur(image, (kernel_size, kernel_size), 0)

# 生成感兴趣区域即Mask掩模
def region_of_interest(image, vertices):

    mask = np.zeros_like(image)  # 生成图像大小一致的zeros矩

    # 填充顶点vertices中间区域
    if len(image.shape) > 2:
        channel_count = image.shape[2]
        ignore_mask_color = (255,) * channel_count
    else:
        ignore_mask_color = 255

    # 填充函数
    cv.fillPoly(mask, vertices, ignore_mask_color)
    masked_image = cv.bitwise_and(image, mask)
    return masked_image

# 原图像与车道线图像按照a:b比例融合
def weighted_img(img, initial_img, a=0.8, b=1., c=0.):
    return cv.addWeighted(initial_img, a, img, b, c)

# def reset_global_vars():
#
#     global SET_LFLAG
#     global SET_RFLAG
#     global LAST_LSLOPE
#     global LAST_RSLOPE
#     global LAST_LEFT
#     global LAST_RIGHT
#
#     SET_RFLAG = 0
#     SET_LFLAG = 0
#     LAST_LSLOPE = 0
#     LAST_RSLOPE = 0
#     LAST_RIGHT = [0, 0, 0]
#     LAST_LEFT = [0, 0, 0]

def draw_lines(image, lines, color=[255,0,0], thickness=2):

    right_y_set = []
    right_x_set = []
    right_slope_set = []

    left_y_set = []
    left_x_set = []
    left_slope_set = []

    slope_min = .35  # 斜率低阈值
    slope_max = .85  # 斜率高阈值
    middle_x = image.shape[1] / 2  # 图像中线x坐标
    max_y = image.shape[0]  # 最大y坐标

    for line in lines:
        for x1, y1, x2, y2 in line:
            fit = np.polyfit((x1, x2), (y1, y2), 1)    # 拟合成直线
            slope = fit[0]  # 斜率

            if slope_min < np.absolute(slope) <= slope_max:

                # 将斜率大于0且线段X坐标在图像中线右边的点存为右边车道线
                if slope > 0 and x1 > middle_x and x2 > middle_x:
                    right_y_set.append(y1)
                    right_y_set.append(y2)
                    right_x_set.append(x1)
                    right_x_set.append(x2)
                    right_slope_set.append(slope)

                # 将斜率小于0且线段X坐标在图像中线左边的点存为左边车道线
                elif slope < 0 and x1 < middle_x and x2 < middle_x:
                    left_y_set.append(y1)
                    left_y_set.append(y2)
                    left_x_set.append(x1)
                    left_x_set.append(x2)
                    left_slope_set.append(slope)

    # 绘制左车道线
    if left_y_set:
        lindex = left_y_set.index(min(left_y_set))  # 最高点
        left_x_top = left_x_set[lindex]
        left_y_top = left_y_set[lindex]
        lslope = np.median(left_slope_set)   # 计算平均值

    # 根据斜率计算车道线与图片下方交点作为起点
    left_x_bottom = int(left_x_top + (max_y - left_y_top) / lslope)

    # 绘制线段
    cv.line(image, (left_x_bottom, max_y), (left_x_top, left_y_top), color, thickness)

    # 绘制右车道线
    if right_y_set:
        rindex = right_y_set.index(min(right_y_set))  # 最高点
        right_x_top = right_x_set[rindex]
        right_y_top = right_y_set[rindex]
        rslope = np.median(right_slope_set)

    # 根据斜率计算车道线与图片下方交点作为起点
    right_x_bottom = int(right_x_top + (max_y - right_y_top) / rslope)

    # 绘制线段
    cv.line(image, (right_x_top, right_y_top), (right_x_bottom, max_y), color, thickness)

def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):

    # rho:线段以像素为单位的距离精度
    # theta : 像素以弧度为单位的角度精度(np.pi/180较为合适)
    # threshold : 霍夫平面累加的阈值
    # minLineLength : 线段最小长度(像素级)
    # maxLineGap : 最大允许断裂长度
    lines = cv.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
    return lines

def process_image(image):

    rho = 1       # 霍夫像素单位
    theta = np.pi / 180    # 霍夫角度移动步长
    hof_threshold = 20   # 霍夫平面累加阈值threshold
    min_line_len = 30    # 线段最小长度
    max_line_gap = 60    # 最大允许断裂长度

    kernel_size = 5      # 高斯滤波器大小size
    canny_low_threshold = 75     # canny边缘检测低阈值
    canny_high_threshold = canny_low_threshold * 3    # canny边缘检测高阈值

    alpha = 0.8   # 原图像权重
    beta = 1.     # 车道线图像权重
    lambda_ = 0.

    imshape = image.shape  # 获取图像大小

    # 灰度图转换
    gray = grayscale(image)

    # 高斯滤波
    blur_gray = gaussian_blur(gray, kernel_size)

    # Canny边缘检测
    edge_image = canny(blur_gray, canny_low_threshold, canny_high_threshold)

    # 生成Mask掩模
    vertices = np.array([[(0, imshape[0]), (9 * imshape[1] / 20, 11 * imshape[0] / 18),
                          (11 * imshape[1] / 20, 11 * imshape[0] / 18), (imshape[1], imshape[0])]], dtype=np.int32)
    masked_edges = region_of_interest(edge_image, vertices)

    # 基于霍夫变换的直线检测
    lines = hough_lines(masked_edges, rho, theta, hof_threshold, min_line_len, max_line_gap)
    line_image = np.zeros_like(image)

    # 绘制车道线线段
    draw_lines(line_image, lines, thickness=10)

    # 图像融合
    lines_edges = weighted_img(image, line_image, alpha, beta, lambda_)
    return lines_edges

if __name__ == '__main__':
    # cap = cv.VideoCapture("./test_videos/solidYellowLeft.mp4")
    # while(cap.isOpened()):
    #     _, frame = cap.read()
    #     processed = process_image(frame)
    #     cv.imshow("image", processed)
    #     cv.waitKey(1)





    image = cv.imread('./test_images/solidYellowCurve2.jpg')
    line_image = process_image(image)
    cv.imshow('image',line_image)
    cv.waitKey(0)

(3)效果

方案二:

参考链接:车道检测(Advanced Lane Finding Project)

代码:(C++)

#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <unordered_set>
#include <opencv2/opencv.hpp>
#include <ceres/ceres.h>

/*#define IMG_WIDTH 1280
#define IMG_HIGHT 720
 #define RGB 1
std::string image_path = "1.jpg";*/

#define IMG_WIDTH 1600
#define IMG_HIGHT 1200
#define RGB 0
std::string image_path = "1.png";

#define WIN_MARGIN 130
#define WIN_MINPIX 50
#define WIN_NUM 16
std::vector<cv::Point> src_point;

cv::Mat abs_sobel_thresh(cv::Mat img, std::string orient = "x", double thresh_min = 0, double thresh_max = 255) {
    // 转化为灰度图像
    cv::Mat gray;
    if (RGB) {
        cv::cvtColor(img, gray, cv::COLOR_RGB2GRAY);
    } else {
        cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
    }


    // 求X或Y方向的方向梯度
    cv::Mat abs_sobel;
    if (orient == "x") {
        cv::Sobel(gray, abs_sobel, CV_64F, 1, 0);
    } else if (orient == "y") {
        cv::Sobel(gray, abs_sobel, CV_64F, 0, 1);
    }
    cv::abs(abs_sobel);

    // 阈值过滤
    double max_value;
    cv::Point2i max_location;
    cv::minMaxLoc(abs_sobel, NULL, &max_value, NULL, &max_location);

    cv::Mat scaled_sobel;
    cv::convertScaleAbs(abs_sobel, scaled_sobel, 255 / max_value);

    cv::Mat binary_output = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_32FC1);
    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (scaled_sobel.at<uchar>(row, col) >= thresh_min && scaled_sobel.at<uchar>(row, col) <= thresh_max)
                binary_output.at<float>(row, col) = 255;
        }
    }
    return binary_output;
}

cv::Mat color_thresh(cv::Mat img, cv::Point2f s_thresh, cv::Point2f l_thresh, cv::Point2f b_thresh, cv::Point2f v_thresh) {
    // 将RGB图像转化成HLS,LUV,lab,HSV图像
    cv::Mat hls, luv, lab, hsv;
    if (RGB) {
        cv::cvtColor(img, hls, cv::COLOR_RGB2HLS);
        cv::cvtColor(img, luv, cv::COLOR_RGB2Luv);
        cv::cvtColor(img, lab, cv::COLOR_RGB2Lab);
        cv::cvtColor(img, hsv, cv::COLOR_RGB2HSV);
    } else {
        cv::cvtColor(img, hls, cv::COLOR_BGR2HLS);
        cv::cvtColor(img, luv, cv::COLOR_BGR2Luv);
        cv::cvtColor(img, lab, cv::COLOR_BGR2Lab);
        cv::cvtColor(img, hsv, cv::COLOR_BGR2HSV);
    }


    // 提取hls中的s通道,luv中的l通道,lab中的b通道, hsv中的v通道
    std::vector<cv::Mat> channels;
    split(hls, channels);
    cv::Mat s_channel = channels[2];
    split(luv, channels);
    cv::Mat l_channel = channels[0];
    split(lab, channels);
    cv::Mat b_channel = channels[2];
    split(hsv, channels);
    cv::Mat v_channel = channels[2];

    //
    cv::Mat s_binary = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_8UC1);
    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (s_channel.at<uchar>(row, col) >= s_thresh.x && s_channel.at<uchar>(row, col) <= s_thresh.y)
                s_binary.at<uchar>(row, col) = 255;
        }
    }
    cv::imshow("s_binary", s_binary);
    cv::waitKey(0);

    cv::Mat l_binary = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_8UC1);
    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (l_channel.at<uchar>(row, col) >= l_thresh.x && l_channel.at<uchar>(row, col) <= l_thresh.y)
                l_binary.at<uchar>(row, col) = 255;
        }
    }
    cv::imshow("l_binary", l_binary);
    cv::waitKey(0);

    cv::Mat b_binary = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_8UC1);
    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (b_channel.at<uchar>(row, col) >= b_thresh.x && b_channel.at<uchar>(row, col) <= b_thresh.y)
                b_binary.at<uchar>(row, col) = 255;
        }
    }
    cv::imshow("b_binary", b_binary);
    cv::waitKey(0);

    cv::Mat v_binary = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_8UC1);
    cv::adaptiveThreshold(v_channel, v_binary, 255 ,cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 115, -20);
/*    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (v_channel.at<uchar>(row, col) >= v_thresh.x && v_channel.at<uchar>(row, col) <= v_thresh.y)
                v_binary.at<uchar>(row, col) = 255;
        }
    }*/
    cv::imshow("v_binary", v_binary);
    cv::waitKey(0);

    // 提取同时满足以上4个通道阈值的像素点
    cv::Mat combined = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_8UC1);
    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            //if (s_binary.at<uchar>(row, col) && l_binary.at<uchar>(row, col)
            //        && b_binary.at<uchar>(row, col) && v_binary.at<uchar>(row, col))
            if(v_binary.at<uchar>(row, col))
                combined.at<uchar>(row, col) = 255;
        }
    }

    return combined;
}

cv::Mat perspective_transform(cv::Mat combined_binary, cv::Mat& M) {
    // 求映射的关系矩阵
    cv::Point2f src[4], dst[4];
/*    src[0] = cv::Point2f(560, 470); // top left
    src[1] = cv::Point2f(730, 470); // top right
    src[2] = cv::Point2f(1080, 720); // bottom right
    src[3] = cv::Point2f(200, 720); // bottom left*/
    src[0] = src_point[0]; //cv::Point2f(669, 666); // top left
    src[1] = src_point[1]; //cv::Point2f(818, 666); // top right
    src[2] = src_point[2]; //cv::Point2f(1443, 1199); // bottom right
    src[3] = src_point[3]; //cv::Point2f(76, 1199); // bottom left

/*    dst[0] = cv::Point2f(200,0);
    dst[1] = cv::Point2f(1100,0);
    dst[2] = cv::Point2f(1100,720);
    dst[3] = cv::Point2f(200,720);*/
    dst[0] = cv::Point2f(200, 0);
    dst[1] = cv::Point2f(1399, 0);
    dst[2] = cv::Point2f(1399, 1199);
    dst[3] = cv::Point2f(200, 1199);

    M = cv::getPerspectiveTransform(src, dst);

    cv::Mat warped;
    cv::warpPerspective(combined_binary, warped, M, cv::Size(IMG_WIDTH, IMG_HIGHT));

    return warped;
}

cv::Mat rgb_select(cv::Mat img, cv::Point2f r_thresh, cv::Point2f g_thresh, cv::Point2f b_thresh) {
    std::vector<cv::Mat> channels;
    split(img, channels);
    cv::Mat r_channel = channels[0];
    cv::Mat g_channel = channels[1];
    cv::Mat b_channel = channels[2];

    cv::Mat r_binary = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_8UC1);
    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (r_channel.at<uchar>(row, col) >= r_thresh.x && r_channel.at<uchar>(row, col) <= r_thresh.y)
                r_binary.at<uchar>(row, col) = 255;
        }
    }

    cv::Mat g_binary = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_8UC1);
    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (g_channel.at<uchar>(row, col) >= g_thresh.x && g_channel.at<uchar>(row, col) <= g_thresh.y)
                g_binary.at<uchar>(row, col) = 255;
        }
    }

    cv::Mat b_binary = cv::Mat::zeros(720, IMG_WIDTH, CV_8UC1);
    for (int row = 0; row < 720; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (b_channel.at<uchar>(row, col) >= b_thresh.x && b_channel.at<uchar>(row, col) <= b_thresh.y)
                b_binary.at<uchar>(row, col) = 255;
        }
    }

    cv::Mat combined = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_8UC1);
    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (r_binary.at<uchar>(row, col) && g_binary.at<uchar>(row, col)
                && b_binary.at<uchar>(row, col))
                combined.at<uchar>(row, col) = 255;
        }
    }

    return combined;
}

void draw_histogram(cv::Mat& image, cv::Mat& histogram){
    //绘制直方图
    int bins = IMG_WIDTH;
    int scale = 1;
    int his_height = IMG_HIGHT;
    int his_width = bins * scale;

    cv::Mat dstImage(his_height, his_width, CV_8UC3, cv::Scalar::all(0));
    for (int i = 0; i < bins; ++i) {
        int bin_value = cv::countNonZero(image.col(i));
        histogram.at<float>(i) = bin_value;
        if (i == bins-1) break;
        cv::rectangle(dstImage, cv::Point(i*scale, his_height-1), cv::Point((i+1)*scale, his_height-bin_value*his_height/IMG_HIGHT), cv::Scalar(255, 255, 255));
        int bin_value_next = cv::countNonZero(image.col(i+1));
        cv::line(dstImage, cv::Point(i*scale, his_height-bin_value-1), cv::Point((i+1)*scale, his_height-bin_value_next-1),cv::Scalar(0, 0, 255),2,8,0);
    }

    cv::imshow("histogram", dstImage);
    cv::waitKey(0);
}

struct Hash {
    size_t operator() (const cv::Point &p) const {
        return p.x;
    }
};

cv::Mat draw_sliding_window(cv::Mat image, std::vector<cv::Point> left_box_pos, std::vector<cv::Point> right_box_pos,
                         std::unordered_set<cv::Point, Hash> left_lane_points, std::unordered_set<cv::Point, Hash> right_lane_points) {
    cv::Mat disp;
    cv::cvtColor(image, disp, CV_GRAY2BGR);
    // draw box
    std::for_each(left_box_pos.cbegin(), left_box_pos.cend(),
                  [&disp](cv::Point point){cv::rectangle(disp, cv::Rect(point.x,point.y,2*WIN_MARGIN,IMG_HIGHT/WIN_NUM), cv::Scalar(0,255,0));});
    std::for_each(right_box_pos.cbegin(), right_box_pos.cend(),
                  [&disp](cv::Point point){cv::rectangle(disp, cv::Rect(point.x,point.y,2*WIN_MARGIN,IMG_HIGHT/WIN_NUM), cv::Scalar(0,255,0));});
    // draw lane point
    std::for_each(left_lane_points.cbegin(), left_lane_points.cend(),
                  [&disp](cv::Point point){cv::circle(disp, point, 1, cv::Scalar(0,0,255));});
    std::for_each(right_lane_points.cbegin(), right_lane_points.cend(),
                  [&disp](cv::Point point){cv::circle(disp, point, 1, cv::Scalar(255,0,0));});
    cv::imshow("disp", disp);
    cv::waitKey(0);
    return disp;
}

void callback(int event, int x, int y, int flags, void*) {
    if(event == CV_EVENT_LBUTTONDOWN) {
        src_point.push_back(cv::Point(x, y));
        std::cout << src_point.size() << "th: (" << x << " " << y << ")" << std::endl;
    } else if(event == CV_EVENT_RBUTTONDOWN) {
        if (src_point.size() > 0) {
            src_point.pop_back();
            std::cout << "pop back !" << std::endl;
        } else {
            std::cout << "empty !" << std::endl;
        }

    }
}

struct ExponentialResidual {
    ExponentialResidual(double x, double y) : x_(x), y_(y) {}

    template <typename T>
    bool operator()(const T* const k, T* residual) const {
        residual[0] = T(x_) - (k[0]*T(y_)*T(y_) + k[1]*T(y_) + k[2]);
        return true;
    }
private:
    const double x_;
    const double y_;
};

std::vector<double> curve_fitting(const std::unordered_set<cv::Point, Hash>& lane_points) {
    std::vector<double> params = {0,800,1}; // 优化变量初值

    ceres::LossFunction* loss_function; // 鲁棒核函数
    loss_function = new ceres::HuberLoss(1.0);
    //loss_function = new ceres::CauchyLoss(0.5);

    ceres::Problem problem;
    for (cv::Point point : lane_points) {
        ceres::CostFunction* cost_function =
                new ceres::AutoDiffCostFunction<ExponentialResidual, 1, 3>(
                        new ExponentialResidual(point.x, point.y));
        problem.AddResidualBlock(cost_function, loss_function, params.data()); // 默认squared loss
    }

    ceres::Solver::Options options;
    options.linear_solver_type = ceres::DENSE_QR; // 增量方程如何求解
    options.minimizer_progress_to_stdout = true; // 输出到cout

    ceres::Solver::Summary summary;
    ceres::Solve (options, &problem, &summary);  // 开始优化
    std::cout << summary.BriefReport() << std::endl; // .FullReport()

    std::cout << "point_num: " << lane_points.size() << std::endl;
    std::cout << "a: " << params[0] << "  b: " << params[1] << "  c: " << params[2] << std::endl;
    return params;
}

void draw_curve(cv::Mat& canvas, std::vector<double> params) {
    for (int y = 0; y < canvas.rows; ++y) {
        int x = params[0]*y*y + params[1]*y + params[2];
        cv::circle(canvas, cv::Point(x, y), 1, cv::Scalar(0,255,255));
        cv::circle(canvas, cv::Point(x-1, y), 1, cv::Scalar(0,255,255));
        cv::circle(canvas, cv::Point(x+1, y), 1, cv::Scalar(0,255,255));
    }
}

int main(int argc, char **argv) {
    cv::Mat color_image_source = cv::imread(image_path,cv::IMREAD_COLOR);
    cv::namedWindow("source_image");
    cv::imshow("source_image", color_image_source);

    cv::Mat gray_source;
    cv::cvtColor(color_image_source, gray_source, cv::COLOR_BGR2GRAY);
    cv::adaptiveThreshold(gray_source, gray_source, 255 ,cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 131, -15);
    cv::imshow("gray", gray_source);
    //cv::waitKey(0);

    cv::setMouseCallback("source_image", callback, 0);
    cv::waitKey(0);

    // 畸变矫正
    //cv::undistort();
    cv::Mat color_image_distorted = color_image_source;

    cv::Mat M;
    cv::Mat warped = perspective_transform(color_image_distorted, M);
    cv::imshow("warped", warped);
    cv::waitKey(0);

    // 索贝尔梯度
    cv::Mat sobel_x = abs_sobel_thresh(warped, "x", 0, 255);
    cv::Mat sobel_y = abs_sobel_thresh(warped, "y", 0, 255);
    cv::Mat sobel_magnitude;
    std::cout << "sobel_x type" << sobel_x.type() << std::endl;
    cv::magnitude(sobel_x, sobel_y, sobel_magnitude);
    cv::Mat binary_magnitude = cv::Mat::zeros(IMG_HIGHT, IMG_WIDTH, CV_8UC1);
    for (int row = 0; row < IMG_HIGHT; ++row) {
        for (int col =0; col < IMG_WIDTH; ++col) {
            if (sobel_magnitude.at<float>(row, col) >= 100 && sobel_magnitude.at<float>(row, col) <= 255)
                binary_magnitude.at<uchar>(row, col) = 255;
        }
    }

    cv::imshow("sobel_magnitude", binary_magnitude);
    cv::waitKey(0);

    cv::Mat sobel_combined;
    //cv::max(sobel_x, sobel_y, binary_magnitude);
    cv::imshow("sobel_combined", binary_magnitude);
    cv::waitKey(0);

    // 使用hsv中的s通道,lab中的b通道,luv中的l通道,hsv中的v通道,提取满足阈值的像素点
    //cv::Point2f s_thresh(230,255), l_thresh(60,255), b_thresh(50,255), v_thresh(200,255);
    cv::Point2f s_thresh(100,255), l_thresh(60,255), b_thresh(50,255), v_thresh(100,255);
    cv::Mat color_combined = color_thresh(warped, s_thresh, l_thresh, b_thresh, v_thresh);
    cv::imshow("color_combined", color_combined);
    cv::waitKey(0);

    // 使用rgb通道,提取满足阈值的像素点
    cv::Point2f R_thresh(200,255), G_thresh(200,255), B_thresh(0,255);
    cv::Mat rgb_combined = rgb_select(warped, R_thresh, G_thresh, B_thresh);
    cv::imshow("rgb_combined", rgb_combined);
    cv::waitKey(0);

    cv::Mat combined;
    cv::max(binary_magnitude, rgb_combined, combined);
    cv::max(combined, color_combined, combined);
    cv::imshow("combined", combined);
    cv::waitKey(0);

    cv::Mat histogram(1, IMG_WIDTH, CV_32FC1);
    draw_histogram(combined, histogram);

    cv::Mat left_hist = histogram.colRange(0, IMG_WIDTH/2).clone();
    cv::Mat right_hist = histogram.colRange(IMG_WIDTH/2, IMG_WIDTH).clone();
    double left_max_val, right_max_val;
    cv::Point left_max_loc, right_max_loc;
    cv::minMaxLoc(left_hist, NULL, &left_max_val, NULL, &left_max_loc);
    cv::minMaxLoc(right_hist, NULL, &right_max_val, NULL, &right_max_loc);
    std::cout << "left_max_val: " << left_max_val << std::endl;
    std::cout << "left_max_loc: " << left_max_loc.x << std::endl;
    std::cout << "right_max_val: " << right_max_val << std::endl;
    std::cout << "right_max_loc: " << (right_max_loc.x + IMG_WIDTH/2) << std::endl;

    //通过滑移窗口找到图片中的车位线的位置
    //使用使用滑动窗多项式拟合(sliding window polynomial fitting)来获取车道边界。这里使用9个200px宽的滑动窗来定位一条车道线像素:
    //int nwindows = 15;
    int window_height = IMG_HIGHT / WIN_NUM;

    // Current positions to be updated for each window
    int leftx_current = left_max_loc.x;
    int rightx_current = right_max_loc.x + IMG_WIDTH/2;

    // Set the width of the windows +/- margin
    //int margin = 50;
    // Set minimum number of pixels found to recenter window
    //int minpix = 50;
    // Create empty lists to receive left and right lane pixel indices
    std::unordered_set<cv::Point, Hash> points_in_left_box, points_in_right_box;
    std::unordered_set<cv::Point, Hash> left_lane_points, right_lane_points;
    std::vector<cv::Point> left_box_pos, right_box_pos;
    left_box_pos.push_back(cv::Point(leftx_current-WIN_MARGIN, IMG_HIGHT - window_height));
    right_box_pos.push_back(cv::Point(rightx_current-WIN_MARGIN, IMG_HIGHT - window_height));

    // Step through the windows one by one
    for (int i = 0; i < WIN_NUM; ++i) {
        // Identify window boundaries in x and y (and right and left)
        int win_y_low = combined.rows - (i+1) * window_height;
        int win_y_high = combined.rows - i * window_height;
        int win_xleft_low = leftx_current - WIN_MARGIN;
        int win_xleft_high = leftx_current + WIN_MARGIN;
        int win_xright_low = rightx_current - WIN_MARGIN;
        int win_xright_high = rightx_current + WIN_MARGIN;
        // Identify the nonzero pixels in x and y within the window
        for (int row = 0; row < combined.rows; ++row) {
            for (int col = 0; col < combined.cols; ++col) {
                if (combined.at<uchar>(row, col)) {
                    if (row >= win_y_low && row < win_y_high) {
                        if (col >= win_xleft_low && col < win_xleft_high) {
                            points_in_left_box.insert(cv::Point(col, row));
                        }
                        if (col >= win_xright_low && col < win_xright_high) {
                            points_in_right_box.insert(cv::Point(col, row));
                        }
                    }
                }
            }
        }

        // If you found > minpix pixels, recenter next window on their mean position
        if (points_in_left_box.size() > WIN_MINPIX) {
            int sum = 0;
            for (cv::Point p : points_in_left_box) {
                sum += p.x;
            }
            leftx_current = sum / points_in_left_box.size();
        }
        if (points_in_right_box.size() > WIN_MINPIX) {
            int sum = 0;
            for (cv::Point p : points_in_right_box) {
                sum += p.x;
            }
            rightx_current = sum / points_in_right_box.size();
        }
        left_box_pos.push_back(cv::Point(leftx_current-WIN_MARGIN, IMG_HIGHT - window_height * (i+2)));
        right_box_pos.push_back(cv::Point(rightx_current-WIN_MARGIN, IMG_HIGHT - window_height * (i+2)));

        left_lane_points.insert(points_in_left_box.cbegin(), points_in_left_box.cend());
        right_lane_points.insert(points_in_right_box.cbegin(), points_in_right_box.cend());

        points_in_left_box.clear();
        points_in_right_box.clear();
    }

    cv::Mat disp = draw_sliding_window(combined, left_box_pos, right_box_pos, left_lane_points, right_lane_points);
    std::vector<double> left_curve_params, right_curve_params;
    left_curve_params = curve_fitting(left_lane_points);
    right_curve_params = curve_fitting(right_lane_points);
    draw_curve(disp, left_curve_params);
    draw_curve(disp, right_curve_params);
    cv::imshow("disp", disp);
    cv::waitKey(0);


    cv::Mat lane_mask;
    cv::warpPerspective(disp, lane_mask, M.inv(), cv::Size(IMG_WIDTH, IMG_HIGHT));

    cv::Mat result;
    cv::addWeighted(color_image_source, 1, lane_mask, 0.3, 0, color_image_source);
    cv::imshow("result", color_image_source);
    cv::waitKey(0);

    return 0;
}

效果:

https://www.zhihu.com/question/54379931/answer/265236577

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值