提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、介绍
- 二、具体实现
- 1.quickdemo.cpp:
- 2.quickdemo.h
- 主函数main.cpp
- 总结
前言
车道检测案例,参考诸多csdn博主文章,得以写出,还有一些缺陷,希望大家可以改进并提出意见。
一、介绍
直接引入代码,部分注释在程序中解释。
二、具体实现
1.quickdemo.cpp:
#include "quickdemo.h"
#include <math.h>
#define _USE_MATH_DEFINES
#define M_PI 3.14159265358
//灰度化处理
//高斯平滑滤波降噪
void QuickDemo::gry_Demo(Mat& frame)
{
//定义图像容器
Mat gryMat;
Point left_line[2];
Point right_line[2];
//修改图片尺寸大小
double scale = 0.5;
Size ResImgSiz = Size(frame.cols * scale, frame.rows * scale);
Mat rFrame = Mat(ResImgSiz, frame.type());
resize(frame, rFrame, ResImgSiz, INTER_LINEAR);
//将原图转化为gry类型的图片
cvtColor(rFrame, gryMat, COLOR_BGR2GRAY);
imshow("gryMat", gryMat);//灰度化后的视频
//imshow("frame", rFrame);//显示原视频
Mat gauss_img;
GaussianBlur(gryMat, gauss_img, Size(3, 3), 5, 5);
//imshow("GausMat", gauss_img);//高斯模糊后的视频
// 用一阶偏导有限差分计算梯度幅值和方向
Mat gradXY, theta;
getGrandient(gauss_img, gradXY, theta);
// 局部非极大值抑制
Mat local_img;
nonLocalMaxValue(gradXY, theta, local_img);
// 用双阈值算法检测和连接边缘
Mat canny_img;
doubleThreshold(40, 80, local_img, canny_img);
//imshow("canny_img", canny_img);//显示检测的线条
Mat roi_img;
GetROI(canny_img, roi_img);
imshow("roi_img", roi_img);
Mat result = fitLines(roi_img, left_line, right_line);
imshow("result", result);//检测到的车道线
Mat result_img;
addWeighted(rFrame, 0.8, result, 0.5, 0, result_img);
imshow("lane-lines", result_img);//最终车道线拟合
}
/*
手动选择梯形rio区域
*/
void QuickDemo::GetROI(Mat src, Mat& image)
{
Mat mask = Mat::zeros(src.size(), src.type());
int width = src.cols;
int height = src.rows;
//获取车道ROI区域,只对该部分进行处理
vector<Point>pts;
Point ptA((width / 11) * 4, (height / 20) * 20);
Point ptB((width / 9) * 5, (height / 11) * 5);
Point ptC((width / 11) * 5, (height / 9) * 4);
Point ptD((width / 8) * 6, (height / 20) * 20);
pts = { ptA ,ptB,ptC,ptD };
fillPoly(mask, pts, Scalar::all(255));
src.copyTo(image, mask);
}
/*
通过计算拟合直线
*/
Mat QuickDemo::fitLines(Mat& image, Point* left_line, Point* right_line) {
int height = image.rows;
int width = image.cols;
Mat out = Mat::zeros(image.size(), CV_8UC3);
int cx = width / 2;
int cy = height / 2;
vector<Point> left_pts;
vector<Point> right_pts;
Vec4f left;
for (int i = 100; i < (cx - 10); i++)
{
for (int j = cy; j < height; j++)
{
int pv = image.at<uchar>(j, i);
if (pv == 255)
{
left_pts.push_back(Point(i, j));
}
}
}
for (int i = cx; i < (width - 20); i++)
{
for (int j = cy; j < height; j++)
{
int pv = image.at<uchar>(j, i);
if (pv == 255)
{
right_pts.push_back(Point(i, j));
}
}
}
if (left_pts.size() > 2)
{
fitLine(left_pts, left, DIST_L1, 0, 0.01, 0.01);
double k1 = left[1] / left[0];
double step = left[3] - k1 * left[2];
int x1 = int((height - step) / k1);
int y2 = int((cx - 25) * k1 + step);
Point left_spot_1 = Point(x1, height);
Point left_spot_end = Point((cx - 25), y2);
line(out, left_spot_1, left_spot_end, Scalar(0, 255, 255), 8, 8, 0);
left_line[0] = left_spot_1;
left_line[1] = left_spot_end;
}
else
{
line(out, left_line[0], left_line[1], Scalar(0, 255, 255), 8, 8, 0);
}
if (right_pts.size() > 2)
{
Point spot_1 = right_pts[0];
Point spot_end = right_pts[right_pts.size() - 1];
int x1 = spot_1.x;
int y1 = spot_1.y;
int x2 = spot_end.x;
int y2 = spot_end.y;
line(out, spot_1, spot_end, Scalar(0, 255, 255), 8, 8, 0);
right_line[0] = spot_1;
right_line[1] = spot_end;
}
else {
line(out, right_line[0], right_line[1], Scalar(0, 255, 255), 8, 8, 0);
}
return out;
}
/**
用一阶偏导有限差分计算梯度幅值和方向
img 输入原图像
gradXY 输出的梯度幅值
theta 输出的梯度方向
*/
void QuickDemo::getGrandient(Mat& img, Mat& gradXY, Mat& theta) {
gradXY = Mat::zeros(img.size(), CV_8U);
theta = Mat::zeros(img.size(), CV_8U);
for (int j = 1; j < img.rows - 1; j++) {
for (int i = 1; i < img.cols - 1; i++) {
double gradY = double(img.ptr<uchar>(j - 1)[i - 1] + 2 * img.ptr<uchar>(j - 1)[i] + img.ptr<uchar>(j - 1)[i + 1] - img.ptr<uchar>(j + 1)[i - 1] - 2 * img.ptr<uchar>(j + 1)[i] - img.ptr<uchar>(j + 1)[i + 1]);
double gradX = double(img.ptr<uchar>(j - 1)[i + 1] + 2 * img.ptr<uchar>(j)[i + 1] + img.ptr<uchar>(j + 1)[i + 1] - img.ptr<uchar>(j - 1)[i - 1] - 2 * img.ptr<uchar>(j)[i - 1] - img.ptr<uchar>(j + 1)[i - 1]);
gradXY.ptr<uchar>(j)[i] = sqrt(gradX * gradX + gradY * gradY); //计算梯度
theta.ptr<uchar>(j)[i] = atan(gradY / gradX); //计算梯度方向
}
}
}
/**
局部非极大值抑制
gradXY 输入的梯度幅值
theta 输入的梯度方向
dst 输出的经局部非极大值抑制后的图像
*/
void QuickDemo::nonLocalMaxValue(Mat& gradXY, Mat& theta, Mat& dst) {
dst = gradXY.clone();
for (int j = 1; j < gradXY.rows - 1; j++) {
for (int i = 1; i < gradXY.cols - 1; i++) {
double t = double(theta.ptr<uchar>(j)[i]);
double g = double(dst.ptr<uchar>(j)[i]);
if (g == 0.0) {
continue;
}
double g0, g1;
if ((t >= -(3 * M_PI / 8)) && (t < -(M_PI / 8))) {
g0 = double(dst.ptr<uchar>(j - 1)[i - 1]);
g1 = double(dst.ptr<uchar>(j + 1)[i + 1]);
}
else if ((t >= -(M_PI / 8)) && (t < M_PI / 8)) {
g0 = double(dst.ptr<uchar>(j)[i - 1]);
g1 = double(dst.ptr<uchar>(j)[i + 1]);
}
else if ((t >= M_PI / 8) && (t < 3 * M_PI / 8)) {
g0 = double(dst.ptr<uchar>(j - 1)[i + 1]);
g1 = double(dst.ptr<uchar>(j + 1)[i - 1]);
}
else {
g0 = double(dst.ptr<uchar>(j - 1)[i]);
g1 = double(dst.ptr<uchar>(j + 1)[i]);
}
if (g <= g0 || g <= g1) {
dst.ptr<uchar>(j)[i] = 0.0;
}
}
}
}
/**
弱边缘点补充连接强边缘点
img 弱边缘点补充连接强边缘点的输入和输出图像
*/
void QuickDemo::doubleThresholdLink(Mat& img) {
// 循环找到强边缘点,把其领域内的弱边缘点变为强边缘点
for (int j = 1; j < img.rows - 2; j++) {
for (int i = 1; i < img.cols - 2; i++) {
// 如果该点是强边缘点
if (img.ptr<uchar>(j)[i] == 255) {
// 遍历该强边缘点领域
for (int m = -1; m < 1; m++) {
for (int n = -1; n < 1; n++) {
// 该点为弱边缘点(不是强边缘点,也不是被抑制的0点)
if (img.ptr<uchar>(j + m)[i + n] != 0 && img.ptr<uchar>(j + m)[i + n] != 255) {
img.ptr<uchar>(j + m)[i + n] = 255; //该弱边缘点补充为强边缘点
}
}
}
}
}
}
for (int j = 0; j < img.rows - 1; j++) {
for (int i = 0; i < img.cols - 1; i++) {
// 如果该点依旧是弱边缘点,及此点是孤立边缘点
if (img.ptr<uchar>(j)[i] != 255 && img.ptr<uchar>(j)[i] != 255) {
img.ptr<uchar>(j)[i] = 0; //该孤立弱边缘点抑制
}
}
}
}
/**
用双阈值算法检测和连接边缘
low 输入的低阈值
high 输入的高阈值
img 输入的原图像
dst 输出的用双阈值算法检测和连接边缘后的图像
*/
void QuickDemo::doubleThreshold(double low, double high, Mat& img, Mat& dst) {
dst = img.clone();
// 区分出弱边缘点和强边缘点
for (int j = 0; j < img.rows - 1; j++) {
for (int i = 0; i < img.cols - 1; i++) {
double x = double(dst.ptr<uchar>(j)[i]);
// 像素点为强边缘点,置255
if (x > high) {
dst.ptr<uchar>(j)[i] = 255;
}
// 像素点置0,被抑制掉
else if (x < low) {
dst.ptr<uchar>(j)[i] = 0;
}
}
}
// 弱边缘点补充连接强边缘点
doubleThresholdLink(dst);
}
2.quickdemo.h
代码如下(示例):
#pragma once
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include<opencv2/dnn.hpp>
using namespace std;
using namespace cv;
class QuickDemo {
public:
void gry_Demo(Mat& frame);
void getGrandient(Mat& img, Mat& gradXY, Mat& theta);
void nonLocalMaxValue(Mat& gradXY, Mat& theta, Mat& dst);
void doubleThresholdLink(Mat& img);
void doubleThreshold(double low, double high, Mat& img, Mat& dst);
void GetROI(Mat src, Mat& image);
Mat fitLines(Mat& image, Point* left_line, Point* right_line);
};
主函数main.cpp
#include<iostream>
#include<opencv2/opencv.hpp>
#include "quickdemo.h"
using namespace std;
using namespace cv;
int main()
{
Mat frame;
QuickDemo qd;
VideoCapture capture("D:\\Opencv项目\\素材\\车道检测\\carroad2.mp4");
if (!capture.isOpened()){
cout << "Can not open video file!" << endl;
system("pause");
return -1;
}
while (true)
{
char key = waitKey(20);
if (key == 27)
{
break;
}
capture >> frame;
qd.gry_Demo(frame);
}
capture.release();
system("pause");
return 0;
}
总结
最终效果。