Skip to content

Commit

Permalink
Added lane detection
Browse files Browse the repository at this point in the history
  • Loading branch information
Adamczyk Piotr committed Mar 20, 2020
1 parent 290d66c commit b1bad09
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 0 deletions.
54 changes: 54 additions & 0 deletions .gitignore
Expand Up @@ -8,3 +8,57 @@ VisualStudio/.vs/OpenCVHelloWorld/v16/.suo
.vs/VSWorkspaceState.json
.vs/slnx.sqlite
.vs/SemiAutonomousLaneKeepingSystem/v16/.suo
VisualStudio/LaneKeeping/x64/Debug/LaneDetection.obj
VisualStudio/LaneKeeping/x64/Debug/LaneKeeping.log
VisualStudio/LaneKeeping/x64/Debug/LaneKeeping.tlog/CL.command.1.tlog
VisualStudio/LaneKeeping/x64/Debug/LaneKeeping.tlog/CL.read.1.tlog
VisualStudio/LaneKeeping/x64/Debug/LaneKeeping.tlog/CL.write.1.tlog
VisualStudio/LaneKeeping/x64/Debug/LaneKeeping.tlog/LaneKeeping.lastbuildstate
VisualStudio/LaneKeeping/x64/Debug/LaneKeeping.tlog/link.command.1.tlog
VisualStudio/LaneKeeping/x64/Debug/LaneKeeping.tlog/link.read.1.tlog
VisualStudio/LaneKeeping/x64/Debug/LaneKeeping.tlog/link.write.1.tlog
VisualStudio/LaneKeeping/x64/Debug/main.obj
VisualStudio/LaneKeeping/x64/Debug/vc142.idb
VisualStudio/LaneKeeping/x64/Debug/vc142.pdb
VisualStudio/x64/Debug/LaneKeeping.exe
VisualStudio/x64/Debug/LaneKeeping.ilk
VisualStudio/x64/Debug/LaneKeeping.pdb
VisualStudio/x64/Debug/opencv_annotation.exe
VisualStudio/x64/Debug/opencv_interactive-calibration.exe
VisualStudio/x64/Debug/opencv_version.exe
VisualStudio/x64/Debug/opencv_version_win32.exe
VisualStudio/x64/Debug/opencv_videoio_ffmpeg411_64.dll
VisualStudio/x64/Debug/opencv_visualisation.exe
VisualStudio/x64/Debug/opencv_world411.dll
VisualStudio/x64/Debug/opencv_world411.pdb
VisualStudio/x64/Debug/opencv_world411d.dll
VisualStudio/x64/Debug/opencv_world411d.pdb
VisualStudio/x64/Release/opencv_annotation.exe
VisualStudio/x64/Release/opencv_interactive-calibration.exe
VisualStudio/x64/Release/opencv_version.exe
VisualStudio/x64/Release/opencv_version_win32.exe
VisualStudio/x64/Release/opencv_videoio_ffmpeg411_64.dll
VisualStudio/x64/Release/opencv_visualisation.exe
VisualStudio/x64/Release/opencv_world411.dll
VisualStudio/x64/Release/opencv_world411.pdb
VisualStudio/x64/Release/opencv_world411d.dll
VisualStudio/x64/Release/opencv_world411d.pdb
VisualStudio/LaneKeeping/x64/Release/LaneDetection.obj
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.log
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.tlog/CL.command.1.tlog
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.tlog/CL.read.1.tlog
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.tlog/CL.write.1.tlog
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.tlog/LaneKeeping.lastbuildstate
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.tlog/LaneKeeping.write.1u.tlog
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.tlog/link.command.1.tlog
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.tlog/link.read.1.tlog
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.tlog/link.write.1.tlog
VisualStudio/LaneKeeping/x64/Release/main.obj
VisualStudio/LaneKeeping/x64/Release/vc142.pdb
VisualStudio/x64/Release/LaneKeeping.exe
VisualStudio/x64/Release/LaneKeeping.iobj
VisualStudio/x64/Release/LaneKeeping.ipdb
VisualStudio/x64/Release/LaneKeeping.pdb
VisualStudio/LaneKeeping/x64/Debug/LaneDetector.obj
VisualStudio/LaneKeeping/x64/Release/LaneDetector.obj
VisualStudio/LaneKeeping/x64/Release/LaneKeeping.tlog/link.delete.1.tlog
182 changes: 182 additions & 0 deletions VisualStudio/LaneKeeping/LaneDetection.cpp
@@ -0,0 +1,182 @@
#include "LaneDetection.h"

cv::Mat LaneDetection::s_frame;
int LaneDetection::s_frameCenter;
int LaneDetection::s_maxLineHeight;
cv::Mat LaneDetection::s_mask;
std::vector<cv::Vec4i> LaneDetection::s_lines;
std::vector<cv::Point> LaneDetection::s_rightLinePoints;
std::vector<cv::Point> LaneDetection::s_leftLinePoints;
std::array<cv::Point, 4> LaneDetection::s_boundaries;

void LaneDetection::createMask(const cv::Size& frameSize, double frameFormat) {
s_mask = cv::Mat::zeros(frameSize, frameFormat);

const float hScale = 0.625;

std::array<cv::Point, 4> points ({
cv::Point(0.08 * frameSize.width, frameSize.height),
cv::Point(0.42 * frameSize.width, hScale * frameSize.height),
cv::Point(0.56 * frameSize.width, hScale * frameSize.height),
cv::Point( frameSize.width, frameSize.height)
});

//Create trapezoid mask
cv::fillConvexPoly(s_mask, points.data(), 4, cv::Scalar(255, 0, 0));
}

inline void LaneDetection::applyMask() {
cv::bitwise_and(s_frame, s_mask, s_frame);
}

inline void LaneDetection::blur() {
cv::GaussianBlur(s_frame, s_frame, cv::Size(3, 3), 0, 0); //3x3px trial & error
}

inline void LaneDetection::edgeDetection() {

cv::cvtColor(s_frame, s_frame, cv::COLOR_RGB2GRAY);

//binarize gray image
cv::threshold(s_frame, s_frame, 140, 255, cv::THRESH_BINARY); //threshold trial & error

/*
Create the kernel [-1 0 1]
This kernel is based on the one found in the
Lane Departure Warning System by Mathworks
*/
cv::Point anchor = cv::Point(-1, -1);
cv::Mat kernel = cv::Mat(1, 3, CV_32F);
kernel.at<float>(0, 0) = -1;
kernel.at<float>(0, 1) = 0;
kernel.at<float>(0, 2) = 1;

//filter the binary image to obtain the edges //compare results to CannyEdge??
cv::filter2D(s_frame, s_frame, -1, kernel, anchor, 0, cv::BORDER_DEFAULT);
}

inline void LaneDetection::houghLines() {
s_lines.clear();

//calibrate once every x seconds?
HoughLinesP(s_frame, s_lines, 1, CV_PI / 180, 20, 20, 30); //rho & theta by trial & error
}

void LaneDetection::classifyLines() {
s_rightLinePoints.clear();
s_leftLinePoints.clear();

const float minSlope = 0.3f;
const float maxSlope = 1.5f;

for (const auto& line : s_lines) {

//slope = (y1 - y0) / (x1 - x0)
float slope = static_cast<float>(line[3] - line[1]);
slope /= ( static_cast<float>(line[2] - line[0]) + 0.00001f );

//filter too horizontal slopes
float absSlope = std::fabs(slope);
if (absSlope < minSlope || absSlope > maxSlope) continue;

if (slope > 0 && line[2] > s_frameCenter && line[0] > s_frameCenter) {
s_rightLinePoints.push_back(cv::Point(line[0], line[1]));
s_rightLinePoints.push_back(cv::Point(line[2], line[3]));
} else if (slope < 0 && line[2] < s_frameCenter && line[0] < s_frameCenter) {
s_leftLinePoints.push_back(cv::Point(line[0], line[1]));
s_leftLinePoints.push_back(cv::Point(line[2], line[3]));
}
}
}

void LaneDetection::leastSquaresRegression() {

/*
if left/right lane is not detected previous value is reused
issue: if at least 1 lane is not detected in 1st frame => garbage output
TODO: averaging over few frames
*/

//fit right lane
if (!s_rightLinePoints.empty()) {
cv::Vec4d right_line;

cv::fitLine(s_rightLinePoints, right_line, cv::DIST_L2, 0, 0.01, 0.01);
float right_m = right_line[1] / right_line[0];
cv::Point right_b = cv::Point(right_line[2], right_line[3]); // y = m*x + b

float right_ini_x = (static_cast<float>(s_frame.rows - right_b.y) / right_m) + right_b.x;
float right_fin_x = (static_cast<float>(s_maxLineHeight - right_b.y) / right_m) + right_b.x;
s_boundaries[0] = cv::Point(right_ini_x, s_frame.rows);
s_boundaries[1] = cv::Point(right_fin_x, s_maxLineHeight);
}

//fit left lane
if (!s_leftLinePoints.empty()) {
cv::Vec4d left_line;

cv::fitLine(s_leftLinePoints , left_line, cv::DIST_L2, 0, 0.01, 0.01);
float left_m = left_line[1] / left_line[0];
cv::Point left_b = cv::Point(left_line[2], left_line[3]);

float left_ini_x = (static_cast<float>(s_frame.rows - left_b.y) / left_m) + left_b.x;
float left_fin_x = (static_cast<float>(s_maxLineHeight - left_b.y) / left_m) + left_b.x;
s_boundaries[2] = cv::Point(left_ini_x, s_frame.rows);
s_boundaries[3] = cv::Point(left_fin_x, s_maxLineHeight);
}
}

void LaneDetection::prepare(const cv::Size& frameSize, double frameFormat) {
createMask(frameSize, frameFormat);
s_frameCenter = frameSize.width / 2;
s_maxLineHeight = static_cast<int>(0.66f * frameSize.height);
}

void LaneDetection::setFrame(const cv::Mat& frame) {
s_frame = frame;
}

void LaneDetection::process(cv::Mat& frame) {
setFrame(frame);

blur(); //remove noise by blurring image
edgeDetection(); //detect edges

applyMask(); //crop ROI
houghLines(); // use HoughLinesP

if (!s_lines.empty()) {
classifyLines(); //Classify which lines are for left or right lane
leastSquaresRegression(); //calculate lane regression

display(frame);
}
}

void LaneDetection::display(cv::Mat& frame) {

cv::Mat output;
frame.copyTo(output);

//create semi-transparent trapezoid
std::array<cv::Point, 4> poly_points;
poly_points[0] = s_boundaries[2];
poly_points[1] = s_boundaries[0];
poly_points[2] = s_boundaries[1];
poly_points[3] = s_boundaries[3];

cv::fillConvexPoly(output, poly_points.data(), 4, cv::Scalar(255, 255, 255), cv::LINE_AA, 0);
cv::addWeighted(output, 0.4, frame, 0.6, 0, frame);

//draw left & right lane
cv::line(frame, s_boundaries[0], s_boundaries[1], cv::Scalar(255, 255, 255), 7, cv::LINE_AA);
cv::line(frame, s_boundaries[2], s_boundaries[3], cv::Scalar(255, 255, 255), 7, cv::LINE_AA);

//display processed frame
cv::imshow("Lane detection", frame);
cv::waitKey(1);
}

36 changes: 36 additions & 0 deletions VisualStudio/LaneKeeping/LaneDetection.h
@@ -0,0 +1,36 @@
#pragma once
#include <opencv2/opencv.hpp>
#include <vector>
#include <array>

class LaneDetection {

static cv::Mat s_frame; //Current frame
static int s_frameCenter;
static int s_maxLineHeight;

static cv::Mat s_mask;
static std::vector<cv::Vec4i> s_lines;

static std::vector<cv::Point> s_rightLinePoints;
static std::vector<cv::Point> s_leftLinePoints;
static std::array<cv::Point, 4> s_boundaries;

static void createMask(const cv::Size& frameSize, double frameFormat);
static inline void applyMask();
static inline void blur();
static inline void edgeDetection();
static inline void houghLines();
static void classifyLines();
static void leastSquaresRegression();


public:
static void prepare(const cv::Size& frameSize, double frameFormat);
static void setFrame(const cv::Mat& frame);
static void process(cv::Mat& frame);
static void display(cv::Mat& frame);


};

6 changes: 6 additions & 0 deletions VisualStudio/LaneKeeping/LaneKeeping.vcxproj
Expand Up @@ -92,6 +92,7 @@
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\opencv\build\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>-D WITH_CUDA=OFF %(AdditionalOptions)</AdditionalOptions>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
Expand Down Expand Up @@ -126,6 +127,7 @@
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\opencv\build\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
Expand All @@ -136,8 +138,12 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="LaneDetection.cpp" />
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="LaneDetection.h" />
<ClInclude Include="Timer.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
Expand Down
12 changes: 12 additions & 0 deletions VisualStudio/LaneKeeping/LaneKeeping.vcxproj.filters
Expand Up @@ -15,7 +15,19 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="LaneDetection.cpp">
<Filter>Pliki źródłowe</Filter>
</ClCompile>
<ClCompile Include="main.cpp">
<Filter>Pliki źródłowe</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="LaneDetection.h">
<Filter>Pliki nagłówkowe</Filter>
</ClInclude>
<ClInclude Include="Timer.h">
<Filter>Pliki nagłówkowe</Filter>
</ClInclude>
</ItemGroup>
</Project>
35 changes: 35 additions & 0 deletions VisualStudio/LaneKeeping/Timer.h
@@ -0,0 +1,35 @@
#pragma once
#include <chrono>
#include <string_view>
#include <iostream>

class Timer {

public:
Timer(std::string_view title, long long scaler = 1) {
m_startPoint = std::chrono::high_resolution_clock::now();
m_title = title;
m_scaler = scaler;
}

~Timer() {
std::chrono::time_point<std::chrono::high_resolution_clock> endPoint = std::chrono::high_resolution_clock::now();
auto start = std::chrono::time_point_cast<std::chrono::microseconds>(m_startPoint).time_since_epoch().count();
auto end = std::chrono::time_point_cast<std::chrono::microseconds>(endPoint).time_since_epoch().count();

auto duration = end - start;
if (m_scaler != 1) {
std::cout << "On average ";
duration /= m_scaler;
}
auto ms = duration * 0.001;

std::cout << m_title << " took " << duration << " us (" << ms << " ms)\n";
}

private:
std::string_view m_title;
std::chrono::time_point<std::chrono::high_resolution_clock> m_startPoint;
long long m_scaler;
};

0 comments on commit b1bad09

Please sign in to comment.