2019-07-24 23:33:23 ltshan139 阅读数 202
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    361 人正在学习 去看看 刘山

前言

本课所讲的车道线检测来自于一个实际的项目,因为场景比较单一,而且硬件比较受限制,所以采用传统的图像处理方法来识别它们。

源图像

需求:要求识别出 黄色线道里外两条线, 一共有两根黄色车道线。

挑战性:黄色车道线有些模糊不清,一定程度上和其周围背景区分不是很明显;由于摄像头安装位置不是正中间,所以拍出来的黄色车道线有些扭曲畸变,不是严格的直线,而是有些向左弯曲的直线。

识别过程

一)由于场景比较单一,所以先做一个roi,来尽量排除背景的干扰。同时在roi各取4个点(蓝色圆圈和红色圆圈表示),并求得蓝色点变换到红色点的投影矩阵。

 二)根据投影矩阵,将roi图像做一个透视变换,可理解为将倾斜视角拍的图片转换为鸟瞰图。鸟瞰图上黄色线道近似垂直线,从而可以大大提供识别准确度和鲁棒性,而且背景干扰部分进一步排除掉。

三) 综合采用前面所学到的图像处理知识:高斯模糊,自己构造卷积模板来求黄色线道边缘;根据颜色特征区分开黄色车道和背景;最后采用腐蚀形态学方法去噪点得到比较理想的边缘点。这部分的具体内容会在视频课程中详细讲解。

四)采用HoughLinesp来找直线,并且要求斜率近似于垂直。将多根挨在一起的直线上点进行直线拟合,得到四条最终的直线方程,并画在鸟瞰图上。

五)最后一步就是将上面的图进行透视逆变换,并作平移就能在原始图上画出黄色车道线了。

结论

本课所涉及新的理论知识点比较少,侧重于实践,主要是综合应用前面所讲的图像处理方法来进行车道线识别。当然,如果场景比较复杂 比如光线忽亮忽暗等,还可以考虑语义分割模型,比如mask-rcnn deeplabv3等来进行识别。在识别推理前,需要收集大量的样本数据,并进行标注和训练才能得到算法模型。此外跑这种复杂的模型,对硬件要求比较高,一般需要GPU才能达到推理时间在毫秒级。

2018-09-17 17:42:45 xiao__run 阅读数 9435
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    361 人正在学习 去看看 刘山

先上图再干活
1、
这里写图片描述

2、
这里写图片描述
(一)目前国内外广泛使用的车道线检测方法主要分为两大类:
(1) 基于道路特征的车道线检测;
(2) 基于道路模型的车道线检测。
基于道路特征的车道线检测作为主流检测方法之一,主要是利用车道线与道路环境的物理特征差异进行后续图像的分割与处理,从而突出车道线特征,以实现车道线的检测。该方法复杂度较低,实时性较高,但容易受到道路环境干扰。
基于道路模型的车道线检测主要是基于不同的二维或三维道路图像模型(如直线型、抛物线型、样条曲线型、组合模型等) ,采用相应方法确定各模型参数,然后进行车道线拟合。该方法对特定道路的检测具有较高的准确度,但局限性强、运算量大、实时性较差。
(二)在这我介绍一种车道线检测方法,效果在高速上还可以,对于破损道路,光照变化太大等道路效果不佳,后续继续改进(直方图均衡以及多特征融合等等),这里有个基础版本的接口,大致步骤如下
(1)图像灰度化
(2)图像高斯滤波
(3)边缘检测
(4)获取掩膜,获取感兴趣区域
(5)霍夫变换检测直线
(6)将检测到的车道线分类,设置阈值,以图像中线分为左右两边的车道线,存入一个vector
(7)回归两条直线,即左右分别两个点,且求出斜率方程
(8)确定车道线的转弯与否
下面我贴出代码
(1)头文件(LaneDetector.h)

class LaneDetector 
{
private:
	double img_size;
	double img_center;
	bool left_flag = false;  // Tells us if there's left boundary of lane detected
	bool right_flag = false;  // Tells us if there's right boundary of lane detected
	cv::Point right_b;  // Members of both line equations of the lane boundaries:
	double right_m;  // y = m*x + b
	cv::Point left_b;  //
	double left_m;  //

public:
	cv::Mat deNoise(cv::Mat inputImage);  // Apply Gaussian blurring to the input Image
	cv::Mat edgeDetector(cv::Mat img_noise);  // Filter the image to obtain only edges
	cv::Mat mask(cv::Mat img_edges);  // Mask the edges image to only care about ROI
	std::vector<cv::Vec4i> houghLines(cv::Mat img_mask);  // Detect Hough lines in masked edges image
	std::vector<std::vector<cv::Vec4i> > lineSeparation(std::vector<cv::Vec4i> lines, cv::Mat img_edges);  // Sprt detected lines by their slope into right and left lines
	std::vector<cv::Point> regression(std::vector<std::vector<cv::Vec4i> > left_right_lines, cv::Mat inputImage);  // Get only one line for each side of the lane
	std::string predictTurn();  // Determine if the lane is turning or not by calculating the position of the vanishing point
	int plotLane(cv::Mat inputImage, std::vector<cv::Point> lane, std::string turn);  // Plot the resultant lane and turn prediction in the frame.
};

源文件LaneDetector.cpp

*@file LaneDetector.cpp
*@author Miguel Maestre Trueba
*@brief Definition of all the function that form part of the LaneDetector class.
*@brief The class will take RGB images as inputs and will output the same RGB image but
*@brief with the plot of the detected lanes and the turn prediction.
*/
#include <string>
#include <vector>
#include <opencv2/opencv.hpp>
#include "LaneDetector.h"

// IMAGE BLURRING
/**
*@brief Apply gaussian filter to the input image to denoise it
*@param inputImage is the frame of a video in which the
*@param lane is going to be detected
*@return Blurred and denoised image
*/
cv::Mat LaneDetector::deNoise(cv::Mat inputImage) {
	cv::Mat output;

	cv::GaussianBlur(inputImage, output, cv::Size(3, 3), 0, 0);

	return output;
}

// EDGE DETECTION
/**
*@brief Detect all the edges in the blurred frame by filtering the image
*@param img_noise is the previously blurred frame
*@return Binary image with only the edges represented in white
*/
cv::Mat LaneDetector::edgeDetector(cv::Mat img_noise) {
	cv::Mat output;
	cv::Mat kernel;
	cv::Point anchor;

	// Convert image from RGB to gray
	cv::cvtColor(img_noise, output, cv::COLOR_RGB2GRAY);
	// Binarize gray image
	cv::threshold(output, output, 140, 255, cv::THRESH_BINARY);

	// Create the kernel [-1 0 1]
	// This kernel is based on the one found in the
	// Lane Departure Warning System by Mathworks
	anchor = cv::Point(-1, -1);
	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
	cv::filter2D(output, output, -1, kernel, anchor, 0, cv::BORDER_DEFAULT);
	cv::imshow("output", output);
	return output;
}

// MASK THE EDGE IMAGE
/**
*@brief Mask the image so that only the edges that form part of the lane are detected
*@param img_edges is the edges image from the previous function
*@return Binary image with only the desired edges being represented
*/
cv::Mat LaneDetector::mask(cv::Mat img_edges) {
	cv::Mat output;
	cv::Mat mask = cv::Mat::zeros(img_edges.size(), img_edges.type());
	cv::Point pts[4] = {
		cv::Point(210, 720),
		cv::Point(550, 450),
		cv::Point(717, 450),
		cv::Point(1280, 720)
	};

	// Create a binary polygon mask
	cv::fillConvexPoly(mask, pts, 4, cv::Scalar(255, 0, 0));
	// Multiply the edges image and the mask to get the output
	cv::bitwise_and(img_edges, mask, output);

	return output;
}

// HOUGH LINES
/**
*@brief Obtain all the line segments in the masked images which are going to be part of the lane boundaries
*@param img_mask is the masked binary image from the previous function
*@return Vector that contains all the detected lines in the image
*/
std::vector<cv::Vec4i> LaneDetector::houghLines(cv::Mat img_mask) {
	std::vector<cv::Vec4i> line;

	// rho and theta are selected by trial and error
	HoughLinesP(img_mask, line, 1, CV_PI / 180, 20, 20, 30);

	return line;
}

// SORT RIGHT AND LEFT LINES
/**
*@brief Sort all the detected Hough lines by slope.
*@brief The lines are classified into right or left depending
*@brief on the sign of their slope and their approximate location
*@param lines is the vector that contains all the detected lines
*@param img_edges is used for determining the image center
*@return The output is a vector(2) that contains all the classified lines
*/
std::vector<std::vector<cv::Vec4i> > LaneDetector::lineSeparation(std::vector<cv::Vec4i> lines, cv::Mat img_edges) {
	std::vector<std::vector<cv::Vec4i> > output(2);
	size_t j = 0;
	cv::Point ini;
	cv::Point fini;
	double slope_thresh = 0.3;
	std::vector<double> slopes;
	std::vector<cv::Vec4i> selected_lines;
	std::vector<cv::Vec4i> right_lines, left_lines;

	// Calculate the slope of all the detected lines
	for (auto i : lines) {
		ini = cv::Point(i[0], i[1]);
		fini = cv::Point(i[2], i[3]);

		// Basic algebra: slope = (y1 - y0)/(x1 - x0)
		double slope = (static_cast<double>(fini.y) - static_cast<double>(ini.y)) / (static_cast<double>(fini.x) - static_cast<double>(ini.x) + 0.00001);

		// If the slope is too horizontal, discard the line
		// If not, save them  and their respective slope
		if (std::abs(slope) > slope_thresh) {
			slopes.push_back(slope);
			selected_lines.push_back(i);
		}
	}

	// Split the lines into right and left lines
	img_center = static_cast<double>((img_edges.cols / 2));
	while (j < selected_lines.size()) {
		ini = cv::Point(selected_lines[j][0], selected_lines[j][1]);
		fini = cv::Point(selected_lines[j][2], selected_lines[j][3]);

		// Condition to classify line as left side or right side
		if (slopes[j] > 0 && fini.x > img_center && ini.x > img_center) {
			right_lines.push_back(selected_lines[j]);
			right_flag = true;
		}
		else if (slopes[j] < 0 && fini.x < img_center && ini.x < img_center) {
			left_lines.push_back(selected_lines[j]);
			left_flag = true;
		}
		j++;
	}

	output[0] = right_lines;
	output[1] = left_lines;

	return output;
}

// REGRESSION FOR LEFT AND RIGHT LINES
/**
*@brief Regression takes all the classified line segments initial and final points and fits a new lines out of them using the method of least squares.
*@brief This is done for both sides, left and right.
*@param left_right_lines is the output of the lineSeparation function
*@param inputImage is used to select where do the lines will end
*@return output contains the initial and final points of both lane boundary lines
*/
std::vector<cv::Point> LaneDetector::regression(std::vector<std::vector<cv::Vec4i> > left_right_lines, cv::Mat inputImage) {
	std::vector<cv::Point> output(4);
	cv::Point ini;
	cv::Point fini;
	cv::Point ini2;
	cv::Point fini2;
	cv::Vec4d right_line;
	cv::Vec4d left_line;
	std::vector<cv::Point> right_pts;
	std::vector<cv::Point> left_pts;

	// If right lines are being detected, fit a line using all the init and final points of the lines
	if (right_flag == true) {
		for (auto i : left_right_lines[0]) {
			ini = cv::Point(i[0], i[1]);
			fini = cv::Point(i[2], i[3]);

			right_pts.push_back(ini);
			right_pts.push_back(fini);
		}

		if (right_pts.size() > 0) {
			// The right line is formed here
			cv::fitLine(right_pts, right_line, CV_DIST_L2, 0, 0.01, 0.01);
			right_m = right_line[1] / right_line[0];
			right_b = cv::Point(right_line[2], right_line[3]);
		}
	}

	// If left lines are being detected, fit a line using all the init and final points of the lines
	if (left_flag == true) {
		for (auto j : left_right_lines[1]) {
			ini2 = cv::Point(j[0], j[1]);
			fini2 = cv::Point(j[2], j[3]);

			left_pts.push_back(ini2);
			left_pts.push_back(fini2);
		}

		if (left_pts.size() > 0) {
			// The left line is formed here
			cv::fitLine(left_pts, left_line, CV_DIST_L2, 0, 0.01, 0.01);
			left_m = left_line[1] / left_line[0];
			left_b = cv::Point(left_line[2], left_line[3]);
		}
	}

	// One the slope and offset points have been obtained, apply the line equation to obtain the line points
	int ini_y = inputImage.rows;
	int fin_y = 470;

	double right_ini_x = ((ini_y - right_b.y) / right_m) + right_b.x;
	double right_fin_x = ((fin_y - right_b.y) / right_m) + right_b.x;

	double left_ini_x = ((ini_y - left_b.y) / left_m) + left_b.x;
	double left_fin_x = ((fin_y - left_b.y) / left_m) + left_b.x;

	output[0] = cv::Point(right_ini_x, ini_y);
	output[1] = cv::Point(right_fin_x, fin_y);
	output[2] = cv::Point(left_ini_x, ini_y);
	output[3] = cv::Point(left_fin_x, fin_y);

	return output;
}

// TURN PREDICTION
/**
*@brief Predict if the lane is turning left, right or if it is going straight
*@brief It is done by seeing where the vanishing point is with respect to the center of the image
*@return String that says if there is left or right turn or if the road is straight
*/
std::string LaneDetector::predictTurn() {
	std::string output;
	double vanish_x;
	double thr_vp = 10;

	// The vanishing point is the point where both lane boundary lines intersect
	vanish_x = static_cast<double>(((right_m*right_b.x) - (left_m*left_b.x) - right_b.y + left_b.y) / (right_m - left_m));

	// The vanishing points location determines where is the road turning
	if (vanish_x < (img_center - thr_vp))
		output = "Left Turn";
	else if (vanish_x >(img_center + thr_vp))
		output = "Right Turn";
	else if (vanish_x >= (img_center - thr_vp) && vanish_x <= (img_center + thr_vp))
		output = "Straight";

	return output;
}

// PLOT RESULTS
/**
*@brief This function plots both sides of the lane, the turn prediction message and a transparent polygon that covers the area inside the lane boundaries
*@param inputImage is the original captured frame
*@param lane is the vector containing the information of both lines
*@param turn is the output string containing the turn information
*@return The function returns a 0
*/
int LaneDetector::plotLane(cv::Mat inputImage, std::vector<cv::Point> lane, std::string turn) {
	std::vector<cv::Point> poly_points;
	cv::Mat output;

	// Create the transparent polygon for a better visualization of the lane
	inputImage.copyTo(output);
	poly_points.push_back(lane[2]);
	poly_points.push_back(lane[0]);
	poly_points.push_back(lane[1]);
	poly_points.push_back(lane[3]);
	cv::fillConvexPoly(output, poly_points, cv::Scalar(0, 0, 255), CV_AA, 0);
	cv::addWeighted(output, 0.3, inputImage, 1.0 - 0.3, 0, inputImage);

	// Plot both lines of the lane boundary
	cv::line(inputImage, lane[0], lane[1], cv::Scalar(0, 255, 255), 5, CV_AA);
	cv::line(inputImage, lane[2], lane[3], cv::Scalar(0, 255, 255), 5, CV_AA);

	// Plot the turn message
	cv::putText(inputImage, turn, cv::Point(50, 90), cv::FONT_HERSHEY_COMPLEX_SMALL, 3, cvScalar(0, 255, 0), 1, CV_AA);

	// Show the final output image
	cv::namedWindow("Lane", CV_WINDOW_AUTOSIZE);
	cv::imshow("Lane", inputImage);
	return 0;
}

main函数

#include <iostream>
#include <string>
#include <vector>
#include <opencv2/opencv.hpp>
#include "LaneDetector.h"
//#include "LaneDetector.cpp"

/**
*@brief Function main that runs the main algorithm of the lane detection.
*@brief It will read a video of a car in the highway and it will output the
*@brief same video but with the plotted detected lane
*@param argv[] is a string to the full path of the demo video
*@return flag_plot tells if the demo has sucessfully finished
*/
int main() {
	
	// The input argument is the location of the video
	cv::VideoCapture cap("challenge_video.mp4");
	if (!cap.isOpened())
		return -1;

	LaneDetector lanedetector;  // Create the class object
	cv::Mat frame;
	cv::Mat img_denoise;
	cv::Mat img_edges;
	cv::Mat img_mask;
	cv::Mat img_lines;
	std::vector<cv::Vec4i> lines;
	std::vector<std::vector<cv::Vec4i> > left_right_lines;
	std::vector<cv::Point> lane;
	std::string turn;
	int flag_plot = -1;
	int i = 0;

	// Main algorithm starts. Iterate through every frame of the video
	while (i < 540) {
		// Capture frame
		if (!cap.read(frame))
			break;

		// Denoise the image using a Gaussian filter
		img_denoise = lanedetector.deNoise(frame);

		// Detect edges in the image
		img_edges = lanedetector.edgeDetector(img_denoise);

		// Mask the image so that we only get the ROI
		img_mask = lanedetector.mask(img_edges);

		// Obtain Hough lines in the cropped image
		lines = lanedetector.houghLines(img_mask);

		if (!lines.empty())
		{
			// Separate lines into left and right lines
			left_right_lines = lanedetector.lineSeparation(lines, img_edges);

			// Apply regression to obtain only one line for each side of the lane
			lane = lanedetector.regression(left_right_lines, frame);

			// Predict the turn by determining the vanishing point of the the lines
			turn = lanedetector.predictTurn();

			// Plot lane detection
			flag_plot = lanedetector.plotLane(frame, lane, turn);

			i += 1;
			cv::waitKey(25);
		}
		else {
			flag_plot = -1;
		}
	}
	return flag_plot;
}

最后再晒一张结果图吧
这里写图片描述

2016-09-21 19:54:45 BBBBB54321 阅读数 4948
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    361 人正在学习 去看看 刘山

实现功能:

视频图像采集

图像预处理

车道线检测与识别



实现的效果:





实现思路:

视频采集部分:摄像头采集分为单目采集与多目(双目),双目采集采用立体视觉方法,虽然对于恢复物体的深度信息比较容易,但是图像共轭像点的匹配问题,却在很大程度上限制了它的应用。本系统采用单目视觉方法,即通过一个摄像机来采集图像。虽然,这种方法得到的图像不包含物体的深度信息,但是,如果把时间看作第三轴线,我们便可以从连续的视频图像中得到准三维图像

图像预处理部分:包含图像灰度化,增强图像对比度,图像中值滤波,图像边缘增强,边缘检测信息与区域增长信息融合几部分。

车道线检测识别:有多种方法,如最小二乘法,Hough变换,Radon变换等,本系统使用opencv自带直线检测库霍夫变换。



实现代码:


读取视频检测其中车道线部分核心代码;

用到的结构体声明以及变量初始化:

        IplImage* pCutFrame = NULL;
        IplImage* pCutFrImg = NULL;
        IplImage* pCutBkImg = NULL;


        CvMat* pCutFrameMat = NULL;
        CvMat* pCutFrMat = NULL;
        CvMat* pCutBkMat = NULL;


      //  CvCapture* pCapture = NULL;

        CvMemStorage* storage = cvCreateMemStorage();
        CvSeq* lines = NULL;
<span style="white-space:pre">	</span> int nFrmNum = 0;
        int CutHeight = 250;


打开已有视频(或摄像头),按帧读取视频

if (!(pCapture = cvCaptureFromFile("//home//wengkl//Downloads//car//3.mov"))){
        fprintf(stderr, "Can not open video file\n");
        return -2;
  	  }
    //每次读取一桢的视频
    while (pFrame = cvQueryFrame(pCapture)){
<span style="white-space:pre">		</span>...
<span style="white-space:pre">	</span>}

设置感兴趣区域:

cvSetImageROI(pFrame, cvRect(0, CutHeight, pFrame->width, pFrame->height - CutHeight));

如果是第一帧,分配内存空间

  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);
        }

如果不是第一帧,进行各类检测如下:

获得剪切图

  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);

            cvConvert(pCutBkMat, pCutBkImg);
canny变换

            cvCanny(pCutFrImg, pCutFrImg, 50, 100);
霍夫变换直线检测,并在图像上标记出来,打印出当前帧数与直线斜率

            lines = cvHoughLines2(pCutFrImg, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 100, 30, 15);
            printf("line = %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);
                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);
                    cvLine(pCutFrame, line[0], line[1], CV_RGB(255, 0, 0), 6, CV_AA);
                }
            }

释放内存

    cvReleaseImage(&pCutFrImg);
    cvReleaseImage(&pCutBkImg);
    cvReleaseImage(&pCutFrame);
    cvReleaseMat(&pCutFrameMat);
    cvReleaseMat(&pCutFrMat);
    cvReleaseMat(&pCutBkMat);



由于本系统是移植到FriendlyARM上使用的,并没有移植GUI的库,不能直接用

  1.   CvCapture* pCapture = cvCreateCameraCapture(-1);  

所以附上部分V4L2的代码
int grappicture()
{
    int grab;
    BITMAPFILEHEADER   bf;
    BITMAPINFOHEADER   bi;


    FILE * fp1,* fp2;


    fp1 = fopen(BMP, "wb");
    fp2 = fopen(YUV, "wb");
    if(!fp1)
    {
        printf("open "BMP"error\n");
        return(FALSE);
    }




    if(!fp2)
    {
        printf("open "YUV"error\n");
        return(FALSE);
    }




    //Set BITMAPINFOHEADER
    bi.biSize = 40;
    bi.biWidth = IMAGEWIDTH;
    bi.biHeight = IMAGEHEIGHT;
    bi.biPlanes = 1;
    bi.biBitCount = 24;
    bi.biCompression = 0;
    bi.biSizeImage = IMAGEWIDTH*IMAGEHEIGHT*3;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;




    //Set BITMAPFILEHEADER
    bf.bfType = 0x4d42;
    bf.bfSize = 54 + bi.biSizeImage;
    bf.bfReserved = 0;
    bf.bfOffBits = 54;


    grab=v4l2_grab();
//    printf("GRAB is %d\n",grab);
    printf("grab ok\n");
    fwrite(buffers[0].start, IMAGEHEIGHT*IMAGEWIDTH*2,1, fp2);
    printf("save "YUV"OK\n");


    yuyv_2_rgb888();
    fwrite(&bf, 14, 1, fp1);
    fwrite(&bi, 40, 1, fp1);
    fwrite(frame_buffer, bi.biSizeImage, 1, fp1);
    printf("save "BMP"OK\n");
    close_v4l2();


    return(TRUE);
}



然后只需将源码程序修改如下部分:
在函数开头初始化摄像头
        if(!initcamera())
         {
             printf("init failure");
         }

pFrame = cvQueryFrame(pCapture)
修改为
int grapp=grappicture();
if(grapp==0)

{
cout<<"grapcamera failue !"<<endl;
}
 IplImage*  pFrame= cvLoadImage("image_bmp.bmp",-1);//读取原彩色图


移植问题与总结:

在PC下跑这部分代码是很流畅的,但是一旦移植到友善之臂下,摄像头每次保存图片并解析的速度就会降低;加上摄像头的分辨率比较低,导致在ARM上车道线的识别会有误差

另外,目前代码检测的车道线均为直线或类似直线,当车速过快或者转弯角度过大时,检测出的斜率会混乱。

2019-01-28 14:44:42 lovely_yoshino 阅读数 291
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    361 人正在学习 去看看 刘山

车道识别

1.打开摄像头

2.透视转换

​2.1 首先对图像二值化

- 2.1.1利用Sobel梯度阈值对于x、y获取二值化图像

​2.2 选择感兴趣区域,并对感兴趣区域进行透视变换获取透视变换后的值

3.提取的左右车道上拟合多项式

​ 3.1 找到左右车道的质心

- 3.1.1选取图像的1/4进行处理

- 3.1.2通过获取左右最大最小像素点,并选取最优点

​3.2 绘制mask覆盖

3.3 检测是否存在预测线条以及车道

​3.4 创建填充图像,实现车道覆盖

​3.5 将车道绘制到空白图像上

​3.6 利用逆透视矩阵(MIV)将空白返回到原始图像空间
在这里插入图片描述

2019-07-07 17:57:41 weixin_40693859 阅读数 1019
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    361 人正在学习 去看看 刘山

车道线检测算法的设计与实现

一、问题定义

车道线检测,就是在汽车行驶时通过汽车前方的摄像头捕捉到地面的图像(视频),通过Opencv对图片(视频)进行一系列处理后,可以在图片(视频)中标注出车道线,然后以此作为汽车自动驾驶时的安全行驶范围。由此可见车道线检测是无人驾驶中最为重要且基础的一环。在无人车自动行驶的过程中,实时获取路面视频流,然后对视频进行处理,标注出可行驶的路线。

 

二、算法设计

关于车道线检测,本次实验只是对图片进行处理,因此省去了不少步骤,本次实验主要参考了优达学院的无人驾驶工程师课程,这一系列课程简述了如何将图片的车道线进行检测并标注出来,之后调用pymovie将视频中的每一个目标视频,分割为一帧一帧的图片,然后针对每一个图片进行车道线的识别,最终将每一帧再合成为一个输出视频,整个课程不止针对简单的车道线,还考虑了在阴影条件下,在车道线有弯道时,以及车道线并不完整有部分车道线消失时依旧能够完成车道线的检测。本次实验就是在学习并研究以上课程中完成(感觉就是免费学习了这个课程)。

    大体上整个识别的过程可以分为以下几个步骤:

  1. 加载图片,将其变为由RGB表示的数组
  2. 对图片进行灰度处理,并去除噪点
  3. 通过opencv自带函数实现边缘检测(sobel算子,canny算子,prewitt算子)
  4. 对检测后的图像裁剪出车道线
  5. 通过霍夫变换将车道线画出
  6. 将检测到车道线加载到原图像中

 

2.1数据预处理

首先加载原图片采取cv2.imread加载方式这里也可以采用 mpimg.read 的加载方式,但是跟前面相比颜色的排序由GBR变为RGB。之后应用时注意顺序的变化。

对于输入的图像,我们通过灰度处理将其从一个三通道的图像转换为单通道图像。具体过程就是RGB对应的不同权重的求和:

我们直接调用cvtColor()函数完成这个过程,之后我们就要消除这个图像部分噪点,这样就可以避免在接下来的边缘检测时,在车道线附近出现某个噪点,而使最终的检测结果与真实值相比出现较大误差。

常用的去噪方法有均值模糊、中值模糊和高斯模糊我们在这里使用的是用高斯模糊并使用5*5的矩阵作为核心来对图片进行卷积运算。

其中kernel size越大,对图片进行卷积运算的模板矩阵也就越大最后计算出来的结果也就越模糊,去掉的噪点也就越多。这样在之后的边缘检测中获得最终的图案越好,然而这对车道线检测时也会有少量的损失,但是这在之后的霍夫变换中可以对其进行弥补,但是如果车道线在边缘检测中损失过多,可能会影响霍夫变换最终生成的直线。

但目前先不考虑这些,在经过预处理之后,就可以通过高通滤波进行边缘检测了。

                                                                                                 原始图像

                                                                            使用cv2加载图片并进行灰度处理

 

                                                                                 使用高斯模糊去除部分噪点(5*5)

 

                                                                              高斯模糊去除大部分噪点(15*15)

2.2通过opencv中的各种滤波器进行边缘检测

边缘检测也就是对图像求导数的过程,图像的边缘也就是一阶导数出现极值点,或者二阶导数为零的情况。在opencv中有3种边缘检测算子,一阶导数Roberts、Sobel、Prewitt。二阶导数Laplacian、Log/Marr、(Kirsch、Nevitia),非微分边缘检测算子:Canny。

通过以上算子,来对整个图像求解梯度,我们可以看出在Sobel图像中可以提取出明显的车道线边缘,以及汽车边缘,周围较为明显景物的边缘。Sobel算子对于灰度渐变和噪声较多的图像有着较好的处理效果,Sobel算子对于边缘定位比较准确。相比之下Reberts,Prewitt、Laplacian都是跟Sobel算子不同,针对的图像也不同,导致最后得到的效果不同,而Canny()是内部使用了Sobel算子,对每个像素点计算出梯度强度和方向之后,通过应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。

                                                                                  使用高通滤波对图像进行处理

                                                                 Canny(low_threshold=50  high_threshold=200)

                                                                   Canny(low_threshold=100 high_threshold=200)

Canny滤波器根据选择不同的low_threshold,最终获取的图像也是不同,这种方法可以把图片中距离较远,树木车俩等较为模糊的物体,最后的由于阈值取的较高而被抑制,最终图像也就不再包含其边缘。

2.3对边缘检测后的图像进行截取(获取到车道线的边缘即可)

通过上面的几个操作,我们可以看到车道线已经能够检测出来,然而仅靠Canny函数的阈值来调整图像,是无法获得单独的车道线的,当阈值调整至135,左侧车道线就会少一部分,但是左侧的边缘仍然无法消除。

这时我们可以考虑对该边缘线进行筛选,假设无人车上的 摄像头角度不变,那么在保证车辆行驶在规定车道线内的前提下,相对于车来说,前方的车道线的位置是固定的(直线)。左右两条车道线以相对的斜率,在前方的某点相交。(通过基于透视的图像矫正方法,可以将我们所看到的两条斜直线,转为两条相交直线)。 因此车道线多在图片的下面的三角区域之中,根据本图像的大小为(540,960),而我们的图像是一个二维数组,通过标定一个区域

x=[0,0,460,500,960,960]

y=[540,500,320,320,500,540]

将所有该区域内的车道线边缘检测结果保留,其他的令其像素点值为0。整个过程为将标定的范围标为mask(初始为与原图像shape相同的全0数组),将该区域内标为255,然后将mask与原图像进行与操作,这样就可以只保留下区域内的结果。其他区域像素点值变为0,也就变成纯黑色。

                                                                        在matplotlib中可以标注出规定区域范围

                                                                              实际图像中只保留下了最后的车道线

2.4通过霍夫变换车道线进行补全并绘制至原图像中

在将图像进行截取之后,据我们想要的最终结果已经很接近了,然而由于有的车道线是虚线形式的,我们所获得的图像也就会成为类似矩形的分段式的图像。在opencv中最常用的就是霍夫变换(Hough Lines())中最常用的就要将其转化为左右两条直线。

霍夫变换的原理:

对于一条直线,我们可以将直线上的点用极坐标来表示出来

因此一条直线也就被转换为θ角度内(r,θ)的集合,也被称为霍夫空间。

整条直线也就被映射为以θ为横坐标,r为纵坐标的曲线,同一条直线上的点就会表现为多个曲线的交点。霍夫变换,就是根据相交曲线的数量,来判断是否是直线,假如只有3条曲线相交,它可能是图像恰好不同位置的交点而已(其实也不一定,因为在进行canny的边缘检测后,我们又对区域进行截取,已经能保证区域中95%以上都是车道线内容,当可能水平直线中,左右车道线中有部分点处于同一直线,就会导致结果中出现垂直的变换结果)因此对于参数threshold我们还是设置的多一点比较好。(也不能太多,不然,对于虚线情况下的车道线说不定数量也不够,这样就少线了,可能导致之后的拟合出现误差)

 

                                                                                     裁剪后的车道线

经过霍夫变换,会返回一个(21,1,4)的数组,里面存储的都是笛卡尔坐标系中的xy值,之后就要将每个点进行分类,根据k值的正负号来判断这个点属于左车道线还是右车道线,将其分为right_line和left_line后,我们将它们进行最小二乘的拟合,这样就可以保证虚线形式下的车道线检测也是一条直线,根据所有线中的最大值来固定能拟合出的车道线的长度。

                                                                                      画出对应的车道线

 

做到这,基础的对图片进行车道线检测就算是做到尾声了,接下来就是只要将该车道线绘制到原图像上即可,最终的效果图如下,我们可以清楚的看到我们找到车道线与实际车道线的重合。

                                                                                    将车道线加载原图像中

 

三、实验总结

在本次实验中,主要通过高斯滤波,Canny边缘检测,霍夫变换完成大部分内容,本次实验也只是对单一图片进行识别,同时道路还是直线,就更为的简单,相比于视频流的输入,面对不同路况以及弯曲的车道线,以及将车道线进行透明转换,该实验也只是入门中的入门。通过这次动手也加深了对高斯模糊,高通滤波,Sobel算子,Canny中阈值的变换,霍夫变换笛卡尔坐标与极坐标的转换这些理论进行了各种实践与尝试,上面提到的拓展内容,会在以后继续深入实现(不过这些都在Github上有相应的开源代码

还有一个问题就是,对于左右两侧的车道线的识别,当无人驾驶的汽车进行变道时,只靠一个摄像头捕捉肯定是不够的,这时候单用这种算法一定会出现错误,应该只能在两侧都捕捉路面信息,与本次实验不同,只是在截取车道线时,不是再选一个正下方类似三角形的形状,二是左(右)侧类似平行四边形的形状。这样就能保证获取周围车道线的信息,为变更车道做准备。

 

图像处理的本质

阅读数 1356

没有更多推荐了,返回首页