A classical vision pipeline
This project detects road lanes using C++14 and OpenCV. It works directly with image colors, edges, and line geometry: each video frame passes through a sequence of filters before the estimated left and right lane boundaries are drawn back onto the image.
The demonstration below comes from the repository’s lane_detection.gif, converted to video for playback.
The processing sequence
The LaneDetection function makes the pipeline explicit. Color filtering retains likely lane markings; a region-of-interest mask focuses on the road; Canny extracts edges; the Hough transform finds line segments; and a weighted blend combines the resulting overlay with the original frame.
cv::Mat LaneDetection(cv::Mat src){
cv::Mat color_masked, roi_img, canny_img, hough_img, final_img;
color_masked = colorFilter(src);
roi_img = ROI(color_masked);
canny_img = canny(roi_img);
hough_img = lineDetect(canny_img);
final_img = weighted_img(hough_img, src);
return final_img;
}Finding yellow and white markings
The color filter converts the image to HLS and constructs separate masks for yellow and white. The yellow mask uses lower and upper bounds of (10, 0, 90) and (50, 255, 255). The white mask selects high lightness with a lower bound of 190. Combining the masks retains candidate markings before the geometric stages.
cv::Mat colorFilter(cv::Mat src){
cv::Mat hls, yellowmask, whitemask, mask, masked;
cv::cvtColor(src, hls, cv::COLOR_RGB2HLS);
cv::inRange(hls, cv::Scalar(10,0,90), cv::Scalar(50,255,255), yellowmask);
cv::inRange(hls, cv::Scalar(0,190,0), cv::Scalar(255,255,255), whitemask);
cv::bitwise_or(yellowmask, whitemask, mask);
cv::bitwise_and(src, src, masked, mask=mask);
return masked;
}Focusing on the road
The region of interest is a trapezoid. Its lower edge spans the image width, while its upper edge runs from 45% to 55% of the image width at 60% of the image height. The implementation fills this polygon into a mask and applies it with bitwise_and, removing image regions outside the expected road area.
int x = src.cols;
int y = src.rows;
cv::Point polygon_vertices[1][4];
polygon_vertices[0][0] = cv::Point(0,y);
polygon_vertices[0][1] = cv::Point(x,y);
polygon_vertices[0][2] = cv::Point(
(int)std::round(0.55*x), (int)std::round(0.6*y));
polygon_vertices[0][3] = cv::Point(
(int)std::round(0.45*x), (int)std::round(0.6*y));From edges to line segments
Canny runs on the grayscale masked image with thresholds of 50 and 120. The probabilistic Hough transform then uses a one-pixel distance resolution, a one-degree angular resolution, a vote threshold of 10, a minimum line length of 20 pixels, and a maximum gap of 100 pixels.
cv::Mat canny(cv::Mat img){
cv::Mat edges;
cv::Canny(grayscale(img), edges, 50, 120);
return edges;
}
cv::Mat lineDetect(cv::Mat img){
return hough_lines(img, 1, CV_PI/180, 10, 20, 100);
}cv::Mat hough_lines(cv::Mat img, double rho, double theta, int threshold, double min_line_len,double max_line_gap){
std::vector<cv::Vec4f> lines;
cv::Mat line_img(img.rows, img.cols, CV_8UC3, cv::Scalar(0,0,0));
cv::HoughLinesP(img, lines, rho, theta, threshold, min_line_len, max_line_gap);
drawLines(line_img, lines);
return line_img;
}Estimating the lane boundaries
The drawing stage separates candidate segments by slope and position. Right-lane candidates require a slope above 0.3 and an initial x-coordinate above 500 pixels. Left-lane candidates require a slope below −0.3 and an initial x-coordinate below 600 pixels.
For each side, the code averages slopes and intercepts, then extrapolates a boundary from 65% of the image height to the bottom. It fills the polygon between the boundaries and draws the two lane lines. The excerpt shows the averages and left-boundary calculation; the right boundary uses the same approach.
float left_intercept_avg = vectorAverage(leftIntercept);
float right_intercept_avg = vectorAverage(rightIntercept);
float left_slope_avg = vectorAverage(leftSlope);
float right_slope_avg = vectorAverage(rightSlope);
int left_line_x1 = (int)std::round(
(0.65*img.rows - left_intercept_avg)/left_slope_avg);
int left_line_x2 = (int)std::round(
(img.rows - left_intercept_avg)/left_slope_avg);The overlay is combined with the source image using addWeighted, with default weights of 0.8 for the overlay and 1.0 for the original frame.
Building and running the project
The CMake configuration requires CMake 3.17.2 or newer, selects C++14, and links the LaneDetection executable against OpenCV. Run the program on a machine with OpenCV installed and a graphical display. It prompts for a video path, processes frames until the stream ends, and also exits when Escape is pressed.
git clone https://github.com/hamptonjc/lane-detection-CPP.git
cd lane-detection-CPP
cmake -S . -B build
cmake --build build
./build/LaneDetection
# At the video-path prompt, enter:
# test_videos/solidWhiteRight.mp4Scope and next steps
The implementation is a compact, frame-by-frame lane detector. Its color thresholds, slope filters, and fixed horizontal cutoffs encode assumptions about the scene. The boundary model is straight-line based, and the code does not carry lane estimates forward between frames.
Useful next steps would be handling frames with no valid candidates before computing averages, replacing pixel-specific cutoffs with normalized coordinates, and adding temporal smoothing. These are possible extensions to the current code.
Code and demonstration
Browse the repository ↗
Read the complete implementation ↗
README and original demonstration ↗