• 【OpenCV】红绿灯检测C++Demo实现


    很久以来一直想实现红绿灯检测,今天它来了。

    1. 原理

    OpenCV好强,能够提取红绿灯的轮廓,并根据颜色空间判断红绿,不依赖深度学习算法也能做到可用的效果/demo。

    红绿灯检测的基本步骤如下:

    1. 轮廓检测、计数
    2. red、green和light_out三种状态
    3. 提取颜色空间,红和绿
    4. 膨胀和腐蚀,去除噪点
    5. 判断3种状态

    2. 代码实现

    基于网络上的代码做复现的时候,遇到了opencv不同版本所出现的标识符未声明问题,我这里是基于opencv4.5.4实现的,4.x的应该都可以运行。

    20230617-ubuntu(opencv3)测试通过。

    创建trafficlight.h头文件,将一些引用和全局变量放进来:

    #pragma once
    
    #include "opencv2/opencv.hpp"
    #include "opencv2/imgproc.hpp"
    #include 	//opencv3-4
    #include 	//出现很多未声明标识符的问题
    //#include 
    #include 
    
    using namespace std;
    using namespace cv;
    
    // 函数声明
    int processImgR(Mat);
    int processImgG(Mat);
    bool isIntersected(Rect, Rect);
    void detect(Mat& frame);
    
    // 全局变量
    bool isFirstDetectedR = true;
    bool isFirstDetectedG = true;
    Rect* lastTrackBoxR;
    Rect* lastTrackBoxG;
    int lastTrackNumR;
    int lastTrackNumG;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    然后创建main.cpp,将主函数和功能函数加进来:

    //下一步:如何调整视频检测框,防止误检
    #include "trafficlight.h"
    
    /*
    1.轮廓检测、计数
    2.red、green和light_out三种状态
    3.提取颜色空间,红和绿
    4.膨胀和腐蚀,去除噪点
    5.判断3种状态
    */
    
    //主函数
    
    int main()
    {
    	int redCount = 0;
    	int greenCount = 0;
    
    	Mat frame;
    	Mat img;
    	Mat imgYCrCb;
    	Mat imgGreen;
    	Mat imgRed;
    
    	// 亮度参数
    	double a = 0.3;
    	double b = (1 - a) * 125;
    
    	VideoCapture capture("traffic.mkv");//导入视频的路径/摄像头 0
    	if (!capture.isOpened())
    	{
    		cout << "Start device failed!\n" << endl;//启动设备失败!
    		return -1;
    	}
    
    	// 帧处理
    	while (1)
    	{
    		capture >> frame;
    		//调整亮度
    		frame.convertTo(img, img.type(), a, b);
    
    		//转换为YCrCb颜色空间
    		cvtColor(img, imgYCrCb, CV_BGR2YCrCb);
    
    		imgRed.create(imgYCrCb.rows, imgYCrCb.cols, CV_8UC1);
    		imgGreen.create(imgYCrCb.rows, imgYCrCb.cols, CV_8UC1);
    
    		//分解YCrCb的三个成分
    		vector<Mat> planes;
    		split(imgYCrCb, planes);
    		// 遍历以根据Cr分量拆分红色和绿色
    		MatIterator_<uchar> it_Cr = planes[1].begin<uchar>(),
    			it_Cr_end = planes[1].end<uchar>();
    		MatIterator_<uchar> it_Red = imgRed.begin<uchar>();
    		MatIterator_<uchar> it_Green = imgGreen.begin<uchar>();
    
    		for (; it_Cr != it_Cr_end; ++it_Cr, ++it_Red, ++it_Green)
    		{
    			// RED, 145
    			if (*it_Cr > 145 && *it_Cr < 470)
    				*it_Red = 255;
    			else
    				*it_Red = 0;
    
    			// GREEN 95
    			if (*it_Cr > 95 && *it_Cr < 110)
    				*it_Green = 255;
    			else
    				*it_Green = 0;
    		}
    
    		//膨胀和腐蚀
    		dilate(imgRed, imgRed, Mat(15, 15, CV_8UC1), Point(-1, -1));
    		erode(imgRed, imgRed, Mat(1, 1, CV_8UC1), Point(-1, -1));
    		dilate(imgGreen, imgGreen, Mat(15, 15, CV_8UC1), Point(-1, -1));
    		erode(imgGreen, imgGreen, Mat(1, 1, CV_8UC1), Point(-1, -1));
    
    		redCount = processImgR(imgRed);
    		greenCount = processImgG(imgGreen);
    		cout << "red:" << redCount << ";  " << "green:" << greenCount << endl;
    
    		//条件判断
    		if (redCount == 0 && greenCount == 0)
    		{
    			cv::putText(frame, "lights out", Point(40, 150), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(255, 255, 255), 8, 8, 0);
    		}
    		else if (redCount > greenCount)
    		{
    			cv::putText(frame, "red light", Point(40, 150), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(0, 0, 255), 8, 8, 0);
    		}
    		else {
    			cv::putText(frame, "green light", Point(40, 150), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(0, 255, 0), 8, 8, 0);
    		}
    
    		imshow("video", frame);
    		//imshow("Red", imgRed);
    		//imshow("Green", imgGreen);
    
    		// Handle with the keyboard input
    		if (waitKey(20) == 'q')
    			break;
    	}
    
    	return 0;
    }
    
    //轮廓处理函数:红
    int processImgR(Mat src)
    {
    	Mat tmp;
    
    	vector<vector<Point>> contours;
    	vector<Vec4i> hierarchy;
    	vector<Point> hull;
    
    	CvPoint2D32f tempNode;
    	CvMemStorage* storage = cvCreateMemStorage();
    	CvSeq* pointSeq = cvCreateSeq(CV_32FC2, sizeof(CvSeq), sizeof(CvPoint2D32f), storage);
    
    	Rect* trackBox;
    	Rect* result;
    	int resultNum = 0;
    
    	int area = 0;
    	src.copyTo(tmp);
    
    	//提取轮廓
    	findContours(tmp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
    
    	if (contours.size() > 0)
    	{
    		trackBox = new Rect[contours.size()];
    		result = new Rect[contours.size()];
    
    		//确定要跟踪的区域
    		for (int i = 0; i < contours.size(); i++)
    		{
    			cvClearSeq(pointSeq);
    			// 获取凸包的点集
    			convexHull(Mat(contours[i]), hull, true);
    			int hullcount = (int)hull.size();
    			// 凸包的保存点
    			for (int j = 0; j < hullcount - 1; j++)
    			{
    				tempNode.x = hull[j].x;
    				tempNode.y = hull[j].y;
    				cvSeqPush(pointSeq, &tempNode);
    			}
    
    			trackBox[i] = cvBoundingRect(pointSeq);
    		}
    
    		if (isFirstDetectedR)
    		{
    			lastTrackBoxR = new Rect[contours.size()];
    			for (int i = 0; i < contours.size(); i++)
    				lastTrackBoxR[i] = trackBox[i];
    			lastTrackNumR = contours.size();
    			isFirstDetectedR = false;
    		}
    		else
    		{
    			for (int i = 0; i < contours.size(); i++)
    			{
    				for (int j = 0; j < lastTrackNumR; j++)
    				{
    					if (isIntersected(trackBox[i], lastTrackBoxR[j]))
    					{
    						result[resultNum] = trackBox[i];
    						break;
    					}
    				}
    				resultNum++;
    			}
    			delete[] lastTrackBoxR;
    			lastTrackBoxR = new Rect[contours.size()];
    			for (int i = 0; i < contours.size(); i++)
    			{
    				lastTrackBoxR[i] = trackBox[i];
    			}
    			lastTrackNumR = contours.size();
    		}
    
    		delete[] trackBox;
    	}
    	else
    	{
    		isFirstDetectedR = true;
    		result = NULL;
    	}
    	cvReleaseMemStorage(&storage);
    
    	if (result != NULL)
    	{
    		for (int i = 0; i < resultNum; i++)
    		{
    			area += result[i].area();
    		}
    	}
    	delete[] result;
    
    	return area;
    }
    
    //轮廓处理函数:绿
    int processImgG(Mat src)
    {
    	Mat tmp;
    
    	vector<vector<Point> > contours;
    	vector<Vec4i> hierarchy;
    	vector< Point > hull;
    
    	CvPoint2D32f tempNode;
    	CvMemStorage* storage = cvCreateMemStorage();
    	CvSeq* pointSeq = cvCreateSeq(CV_32FC2, sizeof(CvSeq), sizeof(CvPoint2D32f), storage);
    
    	Rect* trackBox;
    	Rect* result;
    	int resultNum = 0;
    
    	int area = 0;
    
    	src.copyTo(tmp);
    	//提取轮廓
    	findContours(tmp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
    
    	if (contours.size() > 0)
    	{
    		trackBox = new Rect[contours.size()];
    		result = new Rect[contours.size()];
    
    		// 确定要跟踪的区域
    		for (int i = 0; i < contours.size(); i++)
    		{
    			cvClearSeq(pointSeq);
    			// 获取凸包的点集
    			convexHull(Mat(contours[i]), hull, true);
    			int hullcount = (int)hull.size();
    			// 保存凸包的点
    			for (int j = 0; j < hullcount - 1; j++)
    			{
    				tempNode.x = hull[j].x;
    				tempNode.y = hull[j].y;
    				cvSeqPush(pointSeq, &tempNode);
    			}
    
    			trackBox[i] = cvBoundingRect(pointSeq);
    		}
    
    		if (isFirstDetectedG)
    		{
    			lastTrackBoxG = new Rect[contours.size()];
    			for (int i = 0; i < contours.size(); i++)
    				lastTrackBoxG[i] = trackBox[i];
    			lastTrackNumG = contours.size();
    			isFirstDetectedG = false;
    		}
    		else
    		{
    			for (int i = 0; i < contours.size(); i++)
    			{
    				for (int j = 0; j < lastTrackNumG; j++)
    				{
    					if (isIntersected(trackBox[i], lastTrackBoxG[j]))
    					{
    						result[resultNum] = trackBox[i];
    						break;
    					}
    				}
    				resultNum++;
    			}
    			delete[] lastTrackBoxG;
    			lastTrackBoxG = new Rect[contours.size()];
    			for (int i = 0; i < contours.size(); i++)
    			{
    				lastTrackBoxG[i] = trackBox[i];
    			}
    			lastTrackNumG = contours.size();
    		}
    
    		delete[] trackBox;
    	}
    	else
    	{
    		isFirstDetectedG = true;
    		result = NULL;
    	}
    	cvReleaseMemStorage(&storage);
    
    	if (result != NULL)
    	{
    		for (int i = 0; i < resultNum; i++)
    		{
    			area += result[i].area();
    		}
    	}
    	delete[] result;
    
    	return area;
    }
    
    //确定两个矩形区域是否相交
    bool isIntersected(Rect r1, Rect r2)
    {
    	int minX = max(r1.x, r2.x);
    	int minY = max(r1.y, r2.y);
    	int maxX = min(r1.x + r1.width, r2.x + r2.width);
    	int maxY = min(r1.y + r1.height, r2.y + r2.height);
    
    	//判断是否相交
    	if (minX < maxX && minY < maxY)
    		return true;
    	else
    		return false;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318

    运行结果如下(b站视频):

    在这里插入图片描述

    3. 打包程序为exe

    首先在VS的扩展和更新中安装Installer的扩展:

    在这里插入图片描述

    然后在解决方案下新建setup工程:

    在这里插入图片描述

    添加项目输出:

    在这里插入图片描述

    在主输出这里创建快捷方式,然后移动到User’s Desktop文件夹下:

    在这里插入图片描述

    然后添加工程所需文件,把工程所需的数据文件和依赖库都添加进来:

    在这里插入图片描述

    找依赖库的方式可以用这个命令,然后搜索并添加进来:

    在这里插入图片描述

    最后,点击生成,生成完成后,就可以安装了:

    在这里插入图片描述

    安装文件如下:

    在这里插入图片描述

    这样打包出来的安装程序就可以正常运行了。

    在这里插入图片描述

    以上。

  • 相关阅读:
    【Linux】《Linux命令行与shell脚本编程大全 (第4版) 》笔记-Chapter18-图形化桌面环境中的脚本编程
    极简idea下git操作(二)
    .NET 云原生架构师训练营(权限系统 代码实现 Identity)--学习笔记
    GaussDB CN服务异常实例分析
    深度学习(十二)——神经网络:搭建小实战和Sequential的使用
    echarts中tooltip设为渐变色与模糊背景滤镜
    【Git】Gitbash使用ssh 上传本地项目到github
    uni-app获取地理位置
    数据分析实战 | 线性回归——女性身高与体重数据分析
    【Redis笔记】主从服务器复制
  • 原文地址:https://blog.csdn.net/qq_40344790/article/details/127653557