电赛学习笔记(6)——《基于OpenCV的数字图像处理》

源码下载:下载资源包 (bookln.cn)

常用函数库:

** 英文:**OpenCV: OpenCV modules

** 中文:**Welcome to opencv documentation! — OpenCV 2.3.2 documentation

jetson nano上的OpenCV使用:图像识别小车(jetson nano部分)——第五章

学习OpenCV的推荐网站和文章:

Getting Started with OpenCV | LearnOpenCV

spmallick/learnopencv: Learn OpenCV : C++ and Python Examples (github.com)

目录

一.图像相关知识

二.opencv简介

<1>(主体模块、环境变量配置(VS2019)、源码手动编译(CMake)、调用动/静态库、cv命名空间、OpenCV API特点、数据内部接口InputArray/OutputArray、错误处理、OpenCV头文件、HighGui模块)

<2>示例代码:

1.展示图片:

2.播放视频

3.滑动条使用:

4.鼠标事件响应:

<3>练手:

1.打开摄像头

2.视频播放器,滑动条控制进度,双击暂停/播放

<4>linux上编译运行:参考:Linux下编译、链接、加载运行C++ OpenCV的两种方式及常见问题的解决_linux下安装配置好opencv后怎么加载_Adenialzz的博客- CSDN博客

三.OpenCV基本数据结构和基本组件

<1>(基础图像容器Mat类;点Point类;颜色Scalar类;尺寸Size类;矩形Rect类;旋转矩形RotatedRect类;固定向量Vec类;复数类complexf)

<2>练手代码

1.Mat类操作

2.摄像头图像流极坐标变换

3.读取图像像素RGB值(左键显示,右键清空)

4.创建一定尺寸3通道RGB图像,并逐个访问其像素值,并绘制一绿色平面

四.数字图像灰度变换与空间滤波

<1>(灰度变换:线性、非线性;直方图:概率、累计)

<2>示例代码

1.灰度变换函数

2.直方图(概率直方图)绘制函数

七.图像分割

<1>(边缘检测:算子:一阶导数【Sobel/Prewitt/Roberts】、二阶【拉普拉斯/LOG/Canny】)

(几何形状检测【霍夫变换】:直线、圆)

(阈值分割【二值化】:全局阈值【OTSU、三角法】、自适应阈值)

​编辑 <2>示例代码

1.霍夫直线变换和霍夫圆检测

2.OTSU方法和三角法求全局阈值

<3>练手

1.边缘检测二阶导数算子使用

2.霍夫直线变换及霍夫圆的检测

3.linux(ubuntu18)上实现霍夫圆检测(可调各个参数)

4.阈值分割(OTSU/三角法/自适应阈值)

5.识别车道(二值化、边缘检测、霍夫直线变换)

九.特征提取和目标检测

[<1>(特征:HOG/LBP/HAAR- LIKE)(分类:SVM/级联分类器)](about:blank#%3C1%3E%EF%BC%88%E7%89%B9%E5%BE%81%EF%BC%9AHOG%2FLBP%2FHAAR- LIKE%EF%BC%89%EF%BC%88%E5%88%86%E7%B1%BB%EF%BC%9ASVM%2F%E7%BA%A7%E8%81%94%E5%88%86%E7%B1%BB%E5%99%A8%EF%BC%89)

<2>示例代码

1.HOG特征提取+SVM

[2.获取LBP/MB-LBP特征函数](about:blank#2.%E8%8E%B7%E5%8F%96LBP%2FMB- LBP%E7%89%B9%E5%BE%81%E5%87%BD%E6%95%B0)

3.cascade级联分类器使用


一.图像相关知识

二.opencv简介

<1>(主体模块、环境变量配置(VS2019)、源码手动编译(CMake)、调用动/静态库、cv命名空间、OpenCV

API特点、数据内部接口InputArray/OutputArray、错误处理、OpenCV头文件、HighGui模块)

<2>示例代码:
1.展示图片:


​ int main() ​ { ​ namedWindow(“lena”, WINDOW_NORMAL); ​ //setWindowProperty(“lena”, WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN); ​ setWindowTitle(“lena”, “Lena经典图像窗口”); ​ //resizeWindow(“lena”, 400, 300); ​ //moveWindow(“lena”, 0, 0); ​ createTrackbar(“t1”, “lena”, NULL, 10, NULL, NULL); ​ Mat img = imread(“lena512color.tiff”); //读取图像 ​ if (img.empty()) { ​ return -1; //如果读取图像失败,则返回 ​ } ​ imshow(“lena”, img); //显示图像 ​ waitKey(0); //等待用户输入 ​ std::cout << “Hello World!\n”; ​ }

2.播放视频


​ void PlayVideo() ​ { ​ cv::VideoCapture capture(“D:/files/picture and video/C0056.MP4”); ​ int nWidth = capture.get(CAP_PROP_FRAME_WIDTH); //视频图像宽度 ​ int nHeight = capture.get(CAP_PROP_FRAME_HEIGHT); //视频图像高度 ​ double dblFrameRate = capture.get(CAP_PROP_FPS); //视频帧率 ​ double dblFrameCnt = capture.get(CAP_PROP_FRAME_COUNT); //视频总帧数 ​ double dblStartFrames = dblFrameCnt / 2; //播放起始帧数 ​ capture.set(CAP_PROP_POS_FRAMES, dblStartFrames); //从视频中间开始播放 ​
​ cv::namedWindow(“video”,0 ); ​ resizeWindow(“video”, 800, 600); ​
​ while (capture.isOpened()) { ​ Mat frame; ​ capture >> frame; ​ if (frame.empty()) { ​ break; ​ } ​ imshow(“video”, frame); ​ waitKey(10); ​ } ​ }

3.滑动条使用:


​ 滑动条的使用实例 ​ const int g_nMaxAlphaValue = 100; //Alpha值的最大值 ​ int g_nCurAlphaValue; //当前滑动条对应的值 ​ Mat g_srcImg1; //第1张图像 ​ Mat g_srcImg2; //第2张图像 ​ Mat g_mixImg; //混合图像 ​ const char cszWindowName[] = “mix”; ​ //拖动滑动条的响应函数 ​ void on_Trackbar(int, void *) ​ { ​ //求出当前alpha值相对于最大值的比例 ​ double dblAlphaValue = double(g_nCurAlphaValue)/double(g_nMaxAlphaValue); ​ //则beta值为1减去alpha值 ​ double dblBetaValue = 1.0 - dblAlphaValue; ​ //根据alpha和beta的值,对两张图像进行线性混合 ​ addWeighted(g_srcImg1, dblAlphaValue, g_srcImg2, dblBetaValue, 0.0, g_mixImg); ​ //显示混合图像的效果 ​ imshow(cszWindowName, g_mixImg); ​ if (0 == g_nCurAlphaValue) { ​ imwrite(“mix0.jpg”, g_mixImg); ​ } ​ if (50 == g_nCurAlphaValue) { ​ imwrite(“mix50.jpg”, g_mixImg); ​ } ​ if (100 == g_nCurAlphaValue) { ​ imwrite(“mix100.jpg”, g_mixImg); ​ } ​ } ​
​ int main(int argc, char ** argv) ​ { ​ //加载图像 (两图像的尺寸需相同) ​ g_srcImg1 = imread(“lenna.bmp”, IMREAD_COLOR); ​ if (g_srcImg1.empty()) { ​ std::cout << “读取第1张图像失败” << std::endl; ​ return -1; ​ } ​ g_srcImg2 = imread(“tiffany.bmp”, IMREAD_COLOR); ​ if (g_srcImg2.empty()){ ​ std::cout << “读取第2张图像失败” << std::endl; ​ return -1; ​ } ​ // 设置滑动条的初值为70 ​ g_nCurAlphaValue = 0; ​
​ //创建窗口,自动调整大小 ​ namedWindow(“mix”, WINDOW_AUTOSIZE); ​ //在创建的窗体中创建一个滑动条控件 ​ char TrackbarName[50]; ​ sprintf_s(TrackbarName, “透明度 %d”, g_nMaxAlphaValue); ​ createTrackbar(TrackbarName, cszWindowName, &g_nCurAlphaValue, g_nMaxAlphaValue, on_Trackbar); ​ //调用一次回调函数,以显示图像 ​ on_Trackbar(g_nCurAlphaValue, 0); ​ waitKey(0); ​ return 0; ​ }

4.鼠标事件响应:


​ Rect g_rectangle; //记录要绘制的矩形位置 ​ bool g_bDrawingBox = false;//是否进行绘制 ​ RNG g_rng(12345); //随机数对象 ​ const String strWndName = “MouseWnd”; ​ void DrawRactangle(Mat & img, Rect rect) ​ { ​ //每次绘制矩形的颜色都是随机产生的 ​ rectangle(img, rect, Scalar(g_rng.uniform(0, 255),
​ g_rng.uniform(0, 255), g_rng.uniform(0, 255)), 4); ​ } ​ void onMouseCallback(int event, int x, int y, int flags, void * param) ​ { ​ //将画矩形的图像作为参数传入回调函数 ​ Mat &image = (Mat)param; ​ switch (event) ​ { ​ //鼠标移动时改变窗口的大小 ​ case EVENT_MOUSEMOVE: ​ //如果g_bDrawingBox为真,则记录矩形信息到g_rectangle中 ​ if (g_bDrawingBox) { ​ g_rectangle.width = x - g_rectangle.x; ​ g_rectangle.height = y - g_rectangle.y; ​ } ​ break; ​ //左键按下时记录窗口的起始位置 ​ case EVENT_LBUTTONDOWN: ​ g_bDrawingBox = true; ​ //记录g_rectangle的起点 ​ g_rectangle = Rect(x, y, 0, 0); ​ break; ​ //左键抬起时将当前绘制的矩形信息写入到图像中 ​ case EVENT_LBUTTONUP: ​ // 标识符为false ​ g_bDrawingBox = false; ​ //向起点左边绘制 ​ if (g_rectangle.width < 0) { ​ g_rectangle.x += g_rectangle.width; ​ g_rectangle.width *= -1; ​ } ​ //向起点上边绘制 ​ if (g_rectangle.height < 0) { ​ g_rectangle.y += g_rectangle.height; ​ g_rectangle.height *= -1; ​ } ​ //调用函数进行绘制 ​ DrawRactangle(image, g_rectangle); ​ break; ​ } ​ } ​ int main(int argc, char ** argv) ​ { ​ //准备参数 ​ g_rectangle = Rect(-1, -1, 0, 0); ​ Mat srcImage(600, 800, CV_8UC3, Scalar(255,255,255)), tempImage; ​ srcImage.copyTo(tempImage); ​ g_rectangle = Rect(-1, -1, 0, 0); ​ // 设置鼠标操作回调函数 ​ namedWindow(strWndName); ​ setMouseCallback(strWndName, onMouseCallback, (void *)&srcImage); ​ // 程序主循环,当进行绘制的标识符为真的时候进行绘制 ​ while (true) ​ { ​ //复制原图到临时变量,这样可以清除上一次的鼠标拖动结果 ​ srcImage.copyTo(tempImage); ​ if (g_bDrawingBox){ ​ //在鼠标拖动时,每次都对图像进行临时绘制 ​ Rect rectCur = g_rectangle; ​ //鼠标向上或向左移动时,需要对坐标进行处理 ​ if (rectCur.width < 0) { ​ rectCur.x += rectCur.width; ​ rectCur.width *= -1; ​ } ​ if (rectCur.height < 0) { ​ rectCur.y += rectCur.height; ​ rectCur.height *= -1; ​ } ​ DrawRactangle(tempImage, rectCur); ​ } ​ imshow(strWndName, tempImage); ​ if (waitKey(10) == 27) // 按下ESC键,程序退出 ​ break; ​ } ​
​ return 0; ​ }

<3>练手:
1.打开摄像头


​ #include “pch.h” //viscalC++预编译头文件 ​ #include //C++标准输入、输出流 ​ #include <opencv.hpp> //OpenCV头文件 ​ #include <highgui.hpp> //GUI界面头文件 ​ using namespace cv; //打开cv的名词空间 ​
​ #pragma comment(lib, “opencv_world480d.lib”)//打开动态库 ​
​ int main() ​ { ​ cv::namedWindow(“camera”, 0); //新建名为“camera”的窗口 ​ VideoCapture capture(0); //打开ID为0的摄像头 ​ Mat frame; //新建Mat变量(矩阵) ​ while (capture.isOpened()) ​ { ​ capture >> frame; //用重载运算符方式获取视频帧 ​ if (frame.empty())break; ​ imshow(“camera”, frame); //在名为“camera”的窗口显示捕获帧 ​ waitKey(10); //刷新图像,否则无法正常显示 ​ } ​ }

2.视频播放器,滑动条控制进度,双击暂停/播放


​ #include “pch.h” ​ #include ​ #include <opencv.hpp> ​ #include <highgui.hpp> ​ using namespace cv; ​ #pragma comment(lib,”opencv_world480d.lib”) ​
​ const char* trackname = “progress”; ​ const char* windowname = “videoplayer”; ​ const char* filepath = “D:/files/picture and video/C0056.MP4”; ​ cv::VideoCapture capture(filepath, CAP_ANY); ​ int nWidth = capture.get(CAP_PROP_FRAME_WIDTH); //视频图像宽度 ​ int nHeight = capture.get(CAP_PROP_FRAME_HEIGHT); //视频图像高度 ​ double dblFrameRate = capture.get(CAP_PROP_FPS); //视频帧率 ​ double dblFrameCnt = capture.get(CAP_PROP_FRAME_COUNT); //视频总帧数 ​
​ const int g_nMaxProgressValue = 100; //Alpha值的最大值 ​ int g_nCurProgressValue; //当前滑动条对应的值 ​ int Cur_Frame; //记录暂停时的帧数 ​
​ Mat frame; ​ int sign = 0; ​
​ int main() ​ { ​ void on_Trackbar(int, void*); ​ void onMouseCallback(int event, int x, int y, int flags, void* param); ​ cv::namedWindow(windowname,0); ​ setWindowProperty(windowname, WND_PROP_ASPECT_RATIO , WINDOW_FREERATIO); ​ resizeWindow(windowname, 400, 300); ​ moveWindow(windowname, 0, 0); ​
​ createTrackbar(trackname, windowname, &g_nCurProgressValue, g_nMaxProgressValue, on_Trackbar); ​ setMouseCallback(windowname, onMouseCallback, (void*)NULL); ​
while(capture.isOpened()){ Cur_Frame = capture.get(CAP_PROP_POS_FRAMES); //获取当前播放帧数 //判断是否双击,双击则暂停播放 if (sign) { capture.set(CAP_PROP_POS_FRAMES, Cur_Frame-1); }

		capture >> frame;

		//如果播放完毕,等待按键,直接退出
		if (Cur_Frame == dblFrameCnt)
		{
			waitKey(0);
			break;
		}

		imshow(windowname, frame);

		//中途按		ESC可以直接退出
		if (waitKey(1) == 27)break;
	}

}

//滑动条回调函数
void on_Trackbar(int, void*)
{
	capture.set(CAP_PROP_POS_FRAMES, g_nCurProgressValue * dblFrameCnt / g_nMaxProgressValue); //从视频中间开始播放
	capture >> frame;
}

//鼠标回调函数
void onMouseCallback(int event, int x, int y, int flags, void* param)
{
	if(event==EVENT_LBUTTONDBLCLK)sign = (sign + 1) % 2;
}

注意控制输出暂停的方法:

1.waitKey等待:键盘控制,可参考其他博主

2.一直输出上一帧:即本人使用方法


​ capture.set(CAP_PROP_POS_FRAMES, Cur_Frame-1);

3.直接用system(“pause”)

**< 4>linux上编译运行:参考:[Linux下编译、链接、加载运行C++

OpenCV的两种方式及常见问题的解决_linux下安装配置好opencv后怎么加载_Adenialzz的博客- CSDN博客](https://blog.csdn.net/weixin_44966641/article/details/120659285?spm=1001.2014.3001.5506 “Linux下编译、链接、加载运行C++ OpenCV的两种方式及常见问题的解决_linux下安装配置好opencv后怎么加载_Adenialzz的博客- CSDN博客”)**

CMakeLists.txt编辑:[CMAKE] 详解CMakeLists.txt文件 - VictoKu - 博客园 (cnblogs.com)

或参考:jetson nano上的OpenCV使用:图像识别小车(jetson nano部分)——第五章

三.OpenCV基本数据结构和基本组件

<1>(基础图像容器Mat类;点Point类;颜色Scalar类;尺寸Size类;矩形Rect类;旋转矩形RotatedRect类;固定向量Vec类;复数类complexf)

(辅助对象:迭代参数TermCriteria类;范围range类;指针Ptr类)

(工具和系统函数:数学、内存管理、性能优化、异常处理函数)

(图像绘制图形函数:线、矩形、圆、折线)

(图像保存函数;图像几何操作函数:均匀调整(尺寸)、仿射变换、对数极坐标变换)

<2>练手代码
1.Mat类操作


​ int main() ​ { ​ namedWindow(“image”, WINDOW_NORMAL); ​ setWindowTitle(“image”, “image:”); ​ Mat img(Size(200, 100), CV_8UC3, Scalar(80, 160, 240)); ​ imshow(“image”, img); ​ waitKey(0); ​ return -1; ​ }

2.摄像头图像流极坐标变换


​ const int g_nMaxValue = 100; //滑条值的最大值 ​ int g_nCurValue = 0; //当前滑动条对应的值 ​
​ int main() ​ { ​ void on_Trackbar(int, void*); ​ namedWindow(“Polor”, WINDOW_NORMAL); ​
​ Mat frame; ​ VideoCapture capture(0); ​ int nWidth = capture.get(CAP_PROP_FRAME_WIDTH); //视频图像宽度 ​ int nHeight = capture.get(CAP_PROP_FRAME_HEIGHT); //视频图像高度 ​ createTrackbar(“zoom factor”, “Polor”, &g_nCurValue, g_nMaxValue, on_Trackbar); ​ while (capture.isOpened()) ​ { ​ capture >> frame; ​ if (frame.empty())break; ​ logPolar(frame, frame, Point2i(nWidth / 2, nHeight / 2), g_nCurValue, WARP_FILL_OUTLIERS); ​ imshow(“Polor”, frame); ​ waitKey(1); ​ } ​
​ } ​
​ void on_Trackbar(int, void*) ​ { ​ ; ​ }

3.读取图像像素RGB值(左键显示,右键清空)

​ #include “opencv.hpp” ​ #include “highgui.hpp” ​ #include ​ using namespace cv; ​ #pragma comment(lib,”opencv_world480d.lib”) ​
​ const char* filepath = “/test2.png”; ​ const char* windowname = “window”; ​ const char* windowtitle = “image”; ​ const char* trackname = “fontscale”; ​ int fontscale = 1;· //字体大小及线条粗细 ​ const int max_fontscale = 100; ​ Mat img = imread(filepath, IMREAD_COLOR); ​ Mat draw_board = img.clone(); //图片拷贝以实现清除 ​ char string[5]; ​

int main()
{
	void on_Trackbar(int, void*);
	void onMouseCallback(int event, int x, int y, int flags, void* param);
	namedWindow(windowname, WINDOW_NORMAL);
	createTrackbar(trackname, windowname, &fontscale, max_fontscale, on_Trackbar);
	setMouseCallback(windowname, onMouseCallback, (void*)NULL);
	if (draw_board.empty())return -1;
	while (!draw_board.empty())
	{
		imshow(windowname, draw_board);
		if (waitKey(1) == 27)break;
	}
	return 0;
}

void on_Trackbar(int, void*)
{
	;
}

void onMouseCallback(int event, int x, int y, int flags, void* param)
{
	if (event == EVENT_LBUTTONDOWN)
	{
		//读取鼠标所指像素的值
		int rgb[3] = { img.at<cv::Vec3b>(x, y)[2],img.at<cv::Vec3b>(x, y)[1], img.at<cv::Vec3b>(x, y)[0] };
		//putText不支持\n换行,只能手动计算间隔:y + fontscale * 10 * i
		for (int i = 0; i < 3; i++) {
			sprintf_s(string, "%d", rgb[i]);
			//文字写入图像
			putText(draw_board, string, Point(x, y + fontscale * 10 * i), FONT_HERSHEY_PLAIN, fontscale, Scalar(rgb[2], rgb[1], rgb[0]), fontscale, 8, false);
		}
	}
	if (event == EVENT_RBUTTONDOWN)
	{
		//使显示图像为原始图像,即清零
		draw_board = img.clone();
	}
}

注:

①.at()函数访问多通道Mat数据元素时为只能用at()函数,且注意at<>内为 Vec3b


​ at<cv::Vec3b>(x, y)[i]

②.putText()函数无法实现换行,需手动计算

4.创建一定尺寸3通道RGB图像,并逐个访问其像素值,并绘制一绿色平面


​ #include “opencv.hpp” ​ #include “highgui.hpp” ​ #include ​ using namespace cv; ​ #pragma comment(lib,”opencv_world480d.lib”) ​
​ const char* filepath = “test2.png”; ​ const char* windowname = “window”; ​ const char* windowtitle = “image”; ​ const char* trackname = “fontscale”; ​
​ #define WIDTH 800 ​ #define HEIGHT 600 ​
​ int main() ​ { ​ namedWindow(windowname, WINDOW_NORMAL); ​ Mat img(WIDTH, HEIGHT, CV_8UC3, Scalar(0, 0, 0)); ​ for (int i = 0; i < WIDTH; i++) { ​ for (int j = 0; j < HEIGHT; j++) { ​ for (int k = 0; k < 3; k++) { ​ img.at(i, j)[k] = (i * j * k) % 256; ​ } ​ } ​ } ​ Point p1(200, 50), p2(400, 200); ​ rectangle(img,p1,p2,Scalar(0,255,0),8,8,0); ​ imshow(windowname, img); ​ //imwrite(“C:/Users/user/Desktop/1.png”, img); ​ waitKey(0); ​ ​ }

四.数字图像灰度变换与空间滤波

<1>(灰度变换:线性、非线性;直方图:概率、累计)

<2>示例代码
1.灰度变换函数

①对数变换


​ //对数变换 ​ void LogTransform(Mat srcImg, Mat &dstImg, const float c=1.0f) ​ { ​ if (srcImg.empty()){ ​ cout << “No data” << endl; ​ return; ​ } ​ //Mat dstMat = Mat::zeros(srcImg.size(), srcImg.type()); ​ add(srcImg, Scalar(1.0), srcImg); //计算 s+1 ​ srcImg.convertTo(srcImg, CV_32F); //转化为32位浮点型 ​ cv::log(srcImg, dstImg); //计算log(1+s) ​ dstImg = c*dstImg; ​ //归一化处理 ​ normalize(dstImg, dstImg, 0, 255, NORM_MINMAX); ​ //cout << dstImg << endl; ​ cout << dstImg.elemSize() << endl; ​ //将dstImg转换到CV_8U类型 ​ convertScaleAbs(dstImg, dstImg); ​ return; ​ }

②*伽马变换


​ //伽马校正 ​ void MyGammaCorrection(const Mat& src, Mat& dst, float fGamma) ​ { ​ //CV_Assert(src.data); ​ if (src.empty()){ ​ return; ​ } ​ //只处理位深度为8位的图像 ​ CV_Assert(src.depth() != sizeof(uchar)); ​ //创建查找表 ​ unsigned char lut[256]; ​ for (int i = 0; i < 256; i++){ ​ lut[i] = saturate_cast(pow((float)(i / 255.0),
​ fGamma) * 255.0f); ​ } ​
​ dst = src.clone(); ​ const int channels = dst.channels(); ​ switch (channels){ ​ case 1: ​ { ​ //MatIterator_ it; ​ //for (it = dst.begin(); it != dst.end(); it++) ​ // *it = lut[(*it)]; ​ for (int j = 0; j < dst.rows; j++){ ​ for (int i = 0; i < dst.cols; i++){ ​ unsigned char val = dst.at(j, i); ​ dst.at(j, i) = lut[val]; ​ } ​ } ​ break; ​ } ​ case 3: ​ { ​ MatIterator_ it; ​ for (it = dst.begin(); it != dst.end(); it++){ ​ (*it)[0] = lut[((*it)[0])]; ​ (*it)[1] = lut[((*it)[1])]; ​ (*it)[2] = lut[((*it)[2])]; ​ } ​ break; ​ } ​ } ​ }

注:saturate_cast<>的使用(防止颜色溢出)参考:【OpenCV】中saturate_cast的含义和用法是什么?_人工智能博士的博客- CSDN博客

2.直方图(概率直方图)绘制函数

①灰度直方图


​ //灰度直方图 ​ void DrawGrayImgHist(const Mat &srcImg) ​ { ​ if (1 != srcImg.channels()){ ​ return; ​ } ​ int channels = 0; ​ Mat dstHist; ​ int histSize[] = { 256 };
​ float midRanges[] = { 0, 256 }; ​ const float *ranges[] = { midRanges }; ​ calcHist(&srcImg, 1, &channels, Mat(), dstHist,
​ 1, histSize, ranges, true, true); ​ //最终绘制的直方图图像,大小是256×256 ​ Mat histImage = Mat::zeros(Size(256, 256), CV_8UC1); ​ double dblHistMaxValue; ​ //求得直方图的最大值 ​ minMaxLoc(dstHist, 0, &dblHistMaxValue, 0, 0); ​ //将像素的个数整合到图像的最大范围内
​ for (int i = 0; i <= 255; i++){ ​ int value = cvRound(dstHist.at(i)
​ * 255 / dblHistMaxValue); ​ line(histImage, Point(i, histImage.rows - 1),
​ Point(i, histImage.rows - 1 - value), Scalar(255)); ​ } ​ imshow(“直方图”, histImage); ​ imwrite(“desert_hist_规定化之后.bmp”, histImage); ​ }

注:minMaxLoc 寻找图像全局最大最小值

②RGB彩色直方图


​ //RGB彩色直方图 ​ void DrawRGBImgHist(const Mat &srcImg) ​ { ​ if (srcImg.empty() || srcImg.channels() != 3){ ​ return; ​ } ​ //分割成3个单通道图像 ( R, G 和 B ) ​ vector rgb_planes; ​ split(srcImg, rgb_planes); ​ // 设定bin数目 ​ int histSize = 256; ​ // 设定取值范围 ( R,G,B) ) ​ float range[] = { 0, 256 }; ​ const float* histRange = { range }; ​
​ bool uniform = true; ​ bool accumulate = true; ​
​ Mat r_hist, g_hist, b_hist; ​
​ //计算直方图: ​ calcHist(&rgb_planes[0], 1, 0, Mat(), r_hist, 1,
​ &histSize, &histRange, uniform, accumulate); ​ calcHist(&rgb_planes[1], 1, 0, Mat(), g_hist, 1,
​ &histSize, &histRange, uniform, accumulate); ​ calcHist(&rgb_planes[2], 1, 0, Mat(), b_hist, 1,
​ &histSize, &histRange, uniform, accumulate); ​
​ // 创建直方图画布 ​ int hist_w = 256; int hist_h = 200; ​ int bin_w = cvRound((double)hist_w / histSize); ​
​ Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0)); ​
// 将直方图归一化到范围 [ 0, histImage.rows ] normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX); normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX); normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX);

	// 在直方图画布上画出直方图,3个直方图叠加在一起,用不同的颜色表示
	for (int i = 1; i < histSize; i++)
	{
		line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
			Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))),
			Scalar(0, 0, 255), 2, 8, 0);
		line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
			Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))),
			Scalar(0, 255, 0), 2, 8, 0);
		line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
			Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))),
			Scalar(255, 0, 0), 2, 8, 0);
	}

	// 显示直方图
	imshow("RGB彩色图像直方图", histImage);
	imwrite("./colorhist_for_beatuty_after_qualization.bmp", histImage);
	waitKey(0);
}

③累计直方图


​ //画累积直方图 ​ void DrawAccumulateImgHist(const Mat &srcImg) ​ { ​ if (1 != srcImg.channels()){ ​ return; ​ } ​ int channels = 0; ​ Mat dstHist; ​ int histSize[] = { 256 }; ​ float midRanges[] = { 0, 256 }; ​ const float *ranges[] = { midRanges }; ​ calcHist(&srcImg, 1, &channels, Mat(), dstHist,
​ 1, histSize, ranges, true, true); ​ //对直方图进行累积 ​ for (int i = 1; i < dstHist.rows; i++){ ​ dstHist.at(i) += dstHist.at(i-1); ​ } ​ //最终绘制的直方图图像,大小是256×256 ​ Mat histImage = Mat::zeros(Size(256, 256), CV_8UC1); ​ double dblHistMaxValue; ​ //求得直方图的最大值 ​ minMaxLoc(dstHist, 0, &dblHistMaxValue, 0, 0); ​ //将像素的个数整合到图像的最大范围内
​ for (int i = 0; i < 256; i++){ ​ int value = cvRound(dstHist.at(i)
​ * 255 / dblHistMaxValue); ​ line(histImage, Point(i, histImage.rows - 1),
​ Point(i, histImage.rows - 1 - value), Scalar(255)); ​ } ​ imshow(“累积直方图”, histImage); ​ imwrite(“./accumlate.bmp”, histImage); ​ }

七.图像分割

<1>(边缘检测:算子:一阶导数【Sobel/Prewitt/Roberts】、二阶【拉普拉斯/LOG/Canny】)
(几何形状检测【霍夫变换】:直线、圆)
(阈值分割【二值化】:全局阈值【OTSU、三角法】、自适应阈值)

<2>示例代码

1.霍夫直线变换和霍夫圆检测


​ void DetectLines() ​ { ​ Mat matSrc = imread(“Hough_src_clr.png”, IMREAD_GRAYSCALE); ​ Mat matEdge; ​ //Canny算子计算图像边缘 ​ Canny(matSrc, matEdge, 250, 200, 3, false); ​ imshow(“原图像”, matSrc); ​ imshow(“Canny边缘”, matEdge); ​ imwrite(“hough_src_gray.bmp”, matSrc); ​ imwrite(“hough_src_canny.bmp”, matEdge); ​ std::vector linesSHT; ​ //标准霍夫变换检测直线,距离精度为1像素,角度精度为1度,阈值为300 ​ HoughLines(matEdge, linesSHT, 1, CV_PI / 180, 280); ​ Mat matSHT = matSrc.clone(); ​ for (size_t i = 0; i < linesSHT.size(); i++) { ​ //直线的rho和theta值 ​ float rho = linesSHT[i][0], theta = linesSHT[i][1]; ​ //pt1和pt2是直线的两个端点 ​ Point pt1, pt2; ​ double a = cos(theta), b = sin(theta); ​ double x0 = a * rho, y0 = b * rho; ​ pt1.x = cvRound(x0 + 2000 * (-b)); //把浮点数转化成整数 ​ pt1.y = cvRound(y0 + 2000 * (a)); ​ pt2.x = cvRound(x0 - 2000 * (-b)); ​ pt2.y = cvRound(y0 - 2000 * (a)); ​ line(matSHT, pt1, pt2, Scalar(255), 4); ​ } ​ imshow(“SHT直线检测结果”, matSHT); ​ imwrite(“hough_Lines_SHT.bmp”, matSHT); ​ Mat matPPHT = matSrc.clone(); ​ std::vector linesPPHT; ​ //累计概率霍夫变换检测直线,得到的是直线的起止端点 ​ HoughLinesP(matEdge, linesPPHT, 1, CV_PI / 180, 280, 100, 50); ​ for (size_t i = 0; i < linesPPHT.size(); i++) { ​ //直接绘制直线 ​ line(matPPHT, Point(linesPPHT[i][0], linesPPHT[i][1]), ​ Point(linesPPHT[i][2], linesPPHT[i][3]), Scalar(255), 4, 8); ​ } ​ imshow(“PPHT直线检测结果”, matPPHT); ​ imwrite(“Hough_lines_PPHT.bmp”, matPPHT); ​ waitKey(0); ​ }


​ void DetectCircles() ​ { ​ Mat src; ​ src = imread(“HoughCircles_src_clr.jpg”, IMREAD_GRAYSCALE); ​ //imwrite(“HoughCircles_src_gray.bmp”, src); ​ vector circles; ​ HoughCircles(src, circles, HOUGH_GRADIENT, 1, 10, 60, 40, 20, 40); ​ //在原图中画出圆心和圆
​ for (size_t i = 0; i < circles.size(); i++){ ​ //提取出圆心坐标
​ Point center(round(circles[i][0]), round(circles[i][1])); ​ //提取出圆半径
​ int radius = round(circles[i][2]); ​ //圆心
​ circle(src, center, 3, Scalar(255), -1, 4, 0); ​ //圆
​ circle(src, center, radius, Scalar(255), 3, 4, 0); ​ } ​ //imwrite(“HoughCircles_circles.bmp”, src); ​ imshow(“Circle”, src); ​ waitKey(0); ​ }

2.OTSU方法和三角法求全局阈值


​ //OTSU方法求阈值 ​ int OtsuBinary(Mat src) ​ { ​ long lPixCnt = src.rows * src.cols; ​ long histogram[256] = { 0 }; //histogram是灰度直方图 ​ for (int i = 0; i < src.rows; i++) { ​ for (int j = 0; j < src.cols; j++) { ​ unsigned char nCurVal = src.at(i, j); ​ histogram[nCurVal]++; ​ } ​ } ​ int nThreshold = 0; ​ long sum0 = 0, sum1 = 0; //存储前景的灰度总和和背景灰度总和
​ long cnt0 = 0, cnt1 = 0; //前景像素总个数和背景像素总个数
​ double w0 = 0, w1 = 0; //前景和背景所占整幅图像的比例
​ double u0 = 0, u1 = 0; //前景和背景的平均灰度
​ double variance = 0; //类间方差
​ double maxVariance = 0; //用来存储最大类间方差 ​ for (int i = 1; i < 256; i++) //遍历所有灰度级别 ​ { ​ sum0 = 0; cnt0 = 0; w0 = 0; ​ sum1 = 0; cnt1 = 0; w1 = 0; ​ for (int j = 0; j < i; j++) { ​ cnt0 += histogram[j]; //前景像素总和 ​ sum0 += j * histogram[j]; //前景灰度值总和 ​ } ​ //前景部分灰度均值 ​ u0 = cnt0 > 0 ? double(sum0) / cnt0 : 0; ​ w0 = (double)cnt0 / lPixCnt; //前景部分所占的比例 ​ for (int j = i; j <= 255; j++) { ​ cnt1 += histogram[j]; //背景像素个数 ​ sum1 += j * histogram[j]; //背景部分灰度值总和 ​ } ​ //背景部分灰度均值 ​ u1 = cnt1 > 0 ? double(sum1) / cnt1 : 0; ​ w1 = 1 - w0; //背景部分所占的比例 ​ //分割阈值为i时的类间方差 ​ variance = w0 * w1 * (u0 - u1) * (u0 - u1); ​ if (variance > maxVariance) { ​ maxVariance = variance; ​ nThreshold = i; ​ } ​ } ​
​ return nThreshold; ​ } ​
​ //三角法求阈值 ​ int TriangleBinary(Mat src) ​ { ​ long lPixCnt = src.rows * src.cols; ​ long histogram[256] = { 0 }; //histogram是灰度直方图 ​ for (int i = 0; i < src.rows; i++) { ​ for (int j = 0; j < src.cols; j++) { ​ unsigned char nCurVal = src.at(i, j); ​ histogram[nCurVal]++; ​ } ​ } ​
​ //左右边界 ​ int left_bound = 0, right_bound = 0; ​ //直方图最高峰和相应的灰度值 ​ int max_ind = 0, maxPeak = 0; ​ int temp; ​ bool isflipped = false; ​
​ // 找到最左边零的位置 ​ for (int i = 0; i < 256; i++) { ​ if (histogram[i] > 0) { ​ left_bound = i; ​ break; ​ } ​ } ​ //位置再移动一个步长,即为最左侧零位置 ​ if (left_bound > 0) ​ left_bound–; ​
​ // 找到最右边零点位置 ​ for (int i = 255; i > 0; i–) { ​ if (histogram[i] > 0) { ​ right_bound = i; ​ break; ​ } ​ } ​ // 位置再移动一个步长,即为最右侧零位置 ​ if (right_bound < 255) ​ right_bound++; ​
// 在直方图上寻找最亮的点Hmax for (int i = 0; i < 256; i++) { if (histogram[i] > maxPeak) { maxPeak = histogram[i]; max_ind = i; } }

	// 如果最大值落在靠左侧这样就无法满足三角法求阈值,
	 //所以要检测是否最大值是否靠近左侧
	// 如果靠近左侧则通过翻转到右侧位置。
	if (max_ind - left_bound < right_bound - max_ind) {
		isflipped = true;
		int i = 0;
		int j = 255;
		// 左右交换
		while (i < j) {
			temp = histogram[i]; histogram[i] = histogram[j]; histogram[j] = temp;
			i++; j--;
		}
		left_bound = 255 - right_bound;
		max_ind = 255 - max_ind;
	}

	// 计算求得阈值
	double thresh = left_bound;
	double maxDist = 0, tempDist;
	double peakIdxBound = left_bound - max_ind;
	for (int i = left_bound + 1; i <= max_ind; i++)
	{
		// 计算距离
		tempDist = maxPeak * i + peakIdxBound * histogram[i];
		if (tempDist > maxDist) {
			maxDist = tempDist;
			thresh = i;
		}
	}
	thresh--;
	if (isflipped) {
		thresh = 255 - thresh;
	}

	return thresh;
}

//手动二值化处理
Mat Binbary(Mat src, int nThreshold)
{
	//遍历每个像素,对图像进行二值化
	Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC1);
	for (int i = 0; i < src.rows; i++) {
		for (int j = 0; j < src.cols; j++) {
			if (src.at<uchar>(i, j) > nThreshold)
				dst.at<uchar>(i, j) = 255;
		}
	}
	return dst;
}

*注:

①二值化的原图都是灰度图,产生灰度图方法见后

②对于固定场景摄像头读取图像可以先调用以上函数求出全局阈值,之后使用cv::threshold()函数时直接调用该阈值,减少每帧计算阈值时间

<3>练手
1.边缘检测二阶导数算子使用


​ #include “opencv.hpp” ​ #include “highgui.hpp” ​ #include ​ using namespace cv; ​ #pragma comment(lib,”opencv_world480d.lib”) ​
​ #define IMAGE_TEST ​
​ #ifdef IMAGE_TEST ​ const char* filepath = “./Test.jpg”; ​ Mat img = imread(filepath, IMREAD_COLOR); ​ Mat draw_board = img.clone(); //图片拷贝以实现清除 ​ #endif ​ #ifdef CAMERA_TEST ​ VideoCapture capture(0); ​ Mat image; ​ #endif // CAMERA_TEST


​ int main() ​ { ​ void changing(void); ​ changing(); ​ } ​
​ //拉普拉斯高通滤波 ​ void changing(void) ​ { ​ Mat LoG_Image(const Mat & image, int kervalue = 3, double sigma = 1.0f); ​
​ #ifdef IMAGE_TEST ​ Mat image = imread(filepath, IMREAD_COLOR); ​ if (image.empty()) { ​ std::cout << “打开图片失败,请检查” << std::endl; ​ return; ​ } ​ imshow(“原图像”, image); ​ Mat matDst; ​ // Laplacian(image, matDst, image.depth(), 5); //拉普拉斯算子 ​ // matDst = LoG_Image(image, 3, 1.0f); //LOG算子 ​ Canny(image, matDst, 80, 150, 3, false); //canny算子 ​ imwrite(“changing.bmp”, matDst); ​ imshow(“变换效果”, matDst); ​ waitKey(0); ​ #endif ​ #ifdef CAMERA_TEST ​ while (capture.isOpened()) ​ { ​ capture >> image; ​ if (image.empty())break; ​ Mat matDst; ​ // Laplacian(image, matDst, image.depth(), 5); //拉普拉斯算子 ​ // matDst = LoG_Image(image, 3, 1.0f); //LOG算子 ​ Canny(image, matDst, 100, 500, 3, false); //canny算子 ​ imshow(“变换效果”, matDst); ​ if (waitKey(1) == 27)break; ​ } ​ #endif ​ } ​
​ //图像LoG算子运算 ​ Mat LoG_Image(const Mat& image, int kervalue = 3, double sigma = 1.0f) ​ { ​ //首先对图像做高斯平滑 ​ Mat matTemp; ​ GaussianBlur(image, matTemp, Size(kervalue, kervalue), sigma, sigma, BORDER_DEFAULT); ​ //通过拉普拉斯算子做边缘检测 ​ Mat laplacian = Mat::zeros(image.rows, image.cols, CV_32FC1); ​ Laplacian(matTemp, laplacian, CV_32FC1, 3); ​ //求得最大边缘值 ​ double dblMaxVal = 0; ​ minMaxLoc(laplacian, NULL, &dblMaxVal); ​ Mat dstImg; ​ convertScaleAbs(laplacian, dstImg); ​ imwrite(“edge.bmp”, dstImg); ​ Mat result = Mat::zeros(image.rows, image.cols, CV_8UC1); ​ //过零点交叉,寻找边缘像素 ​ for (int i = 1; i < result.rows - 1; i++) { ​ for (int j = 1; j < result.cols - 1; j++) { ​ if (laplacian.at(i, j) < 0.1 * dblMaxVal) { ​ continue; ​ } ​ //水平、垂直、45度方向,135度4个方向过零点判定 ​ if (laplacian.at(i - 1, j)
​ * laplacian.at(i + 1, j) < 0) ​ result.at(i, j) = 255; ​ if (laplacian.at(i, j + 1)
​ * laplacian.at(i, j - 1) < 0) ​ result.at(i, j) = 255; ​ if (laplacian.at(i + 1, j + 1)
​ * laplacian.at(i - 1, j - 1) < 0) ​ result.at(i, j) = 255; ​ if (laplacian.at(i - 1, j + 1)
​ * laplacian.at(i + 1, j - 1) < 0) ​ result.at(i, j) = 255; ​ } ​ } ​ return result; ​ } ​
​ //Canny算子计算图像的梯度和方向 ​ void CannyEdgeAndDirection(const Mat& src) ​ { ​ Mat magX = Mat(src.rows, src.cols, CV_32FC1); ​ Mat magY = Mat(src.rows, src.cols, CV_32FC1); ​ Mat slopes = Mat(src.rows, src.cols, CV_32FC1); ​ Sobel(src, magX, CV_32FC1, 1, 0, 3);//水平梯度 ​ Sobel(src, magY, CV_32FC1, 1, 0, 3);//垂直梯度 ​ //梯度方向 ​ divide(magY, magX, slopes); ​ //梯度幅值 ​ Mat magnitude; ​ sqrt(magX * magX + magY * magY, magnitude); ​ }

2.霍夫直线变换及霍夫圆的检测


​ #include “opencv.hpp” ​ #include “highgui.hpp” ​ #include ​ using namespace cv; ​ #pragma comment(lib,”opencv_world480d.lib”) ​
​ VideoCapture capture(0); ​ Mat image; ​
​ //#define SHT //SHT检测直线 ​ //#define PPHT //PPHT检测直线 ​ #define HCD //霍夫圆检测 ​
​ int main() ​ { ​ while (capture.isOpened()) ​ { ​ capture >> image; ​ if (image.empty())break; ​ Mat matCanny; ​ Mat matDst; ​ Canny(image, matCanny, 100, 300, 3, false); //canny算子 ​
​ #ifdef SHT ​ std::vector linesSHT; ​ //标准霍夫变换检测直线,距离精度为1像素,角度精度为1度,阈值为300 ​ HoughLines(matCanny, linesSHT, 1, CV_PI / 180, 280); ​ //直线在原图上绘制 ​ matDst = image.clone(); ​ for (size_t i = 0; i < linesSHT.size(); i++) { ​ //直线的rho和theta值 ​ float rho = linesSHT[i][0], theta = linesSHT[i][1]; ​ //pt1和pt2是直线的两个端点,2000是经验值(满足覆盖所有前景点像素) ​ Point pt1, pt2; ​ double a = cos(theta), b = sin(theta); ​ double x0 = a * rho, y0 = b * rho; ​ pt1.x = cvRound(x0 + 2000 * (-b)); //把浮点数转化成整数 ​ pt1.y = cvRound(y0 + 2000 * (a)); ​ pt2.x = cvRound(x0 - 2000 * (-b)); ​ pt2.y = cvRound(y0 - 2000 * (a)); ​ line(matDst, pt1, pt2, Scalar(255), 4); ​ } ​ #endif // SHT标准霍夫变换 ​
​ #ifdef PPHT ​ matDst = image.clone(); ​ std::vector linesPPHT; ​ //累计概率霍夫变换检测直线,得到的是直线的起止端点 ​ HoughLinesP(matCanny, linesPPHT, 1, CV_PI / 180, 220, 100, 50); ​ for (size_t i = 0; i < linesPPHT.size(); i++) { ​ //直接绘制直线 ​ line(matDst, Point(linesPPHT[i][0], linesPPHT[i][1]), ​ Point(linesPPHT[i][2], linesPPHT[i][3]), Scalar(255), 4, 8); ​ } ​ #endif // PPHT累计概率霍夫变换 ​
#ifdef HCD cvtColor(image, matDst, COLOR_BGR2GRAY); std::vector circles; HoughCircles(matDst, circles, HOUGH_GRADIENT, 1, 10, 60, 40, 20, 40); //在原图中画出圆心和圆
for (size_t i = 0; i < circles.size(); i++) { //提取出圆心坐标
Point center(round(circles[i][0]), round(circles[i][1])); //提取出圆半径
int radius = round(circles[i][2]); //圆心
circle(matDst, center, 3, Scalar(255), -1, 4, 0); //圆
circle(matDst, center, radius, Scalar(255), 3, 4, 0); } #endif // HCD霍夫圆检测


​ imshow(“检测结果”, matDst); ​ if (waitKey(1) == 27)break; ​ } ​ }

3.linux(ubuntu18)上实现霍夫圆检测(可调各个参数)

参数见:opencv —— HoughCircles 霍夫圆变换原理及圆检测

二值化函数threshold参数:OpenCV基础——threshold函数的使用


​ #include “opencv2/opencv.hpp” ​ #include “opencv2/highgui.hpp” ​ #include ​ using namespace cv; ​
​ VideoCapture capture(0); ​ Mat image; ​
​ const char* windowname=”win”; ​ int max_r=100; ​ int min_r=60; ​ int min_d=80; ​ int t_hold=45; ​ int param1=100; ​ int param2=10; ​ const int t_max=255; ​ const int r_max=1000; ​ const int d_max=100; ​ const int p1_max=200; ​ const int p2_max=200; ​
​ int main() ​ { ​
​ void on_Trackbar_1(int, void*); ​ void on_Trackbar_2(int, void*); ​ void on_Trackbar_3(int, void*); ​ void on_Trackbar_4(int, void*); ​ void on_Trackbar_5(int, void*); ​ void on_Trackbar_6(int, void*); ​ namedWindow(windowname,0); ​ setWindowProperty(windowname, WND_PROP_ASPECT_RATIO , WINDOW_FREERATIO); ​ resizeWindow(windowname, 400, 300); ​ moveWindow(windowname, 0, 0); ​ createTrackbar(“t_hold”,windowname, &t_hold, t_max, on_Trackbar_3); ​ createTrackbar(“max_r”,windowname, &max_r, r_max, on_Trackbar_1); ​ createTrackbar(“min_r”,windowname, &min_r, r_max, on_Trackbar_2); ​ createTrackbar(“min_d”,windowname, &min_d, d_max, on_Trackbar_6); ​ createTrackbar(“p_1”,windowname, &param1, p1_max, on_Trackbar_4); ​ createTrackbar(“p_2”,windowname, &param2, p2_max, on_Trackbar_5); ​
​ while (capture.isOpened()) ​ { ​ capture >> image; ​ if (image.empty())break; ​ Mat matCanny; ​ Mat BinImg; ​ Mat matDst; ​ cvtColor(image, matDst, COLOR_BGR2GRAY); ​ threshold(matDst, BinImg, t_hold, 255, THRESH_BINARY_INV); ​ Canny(BinImg, matCanny, 100, 300, 3, false); //canny算子 ​
std::vector circles; HoughCircles(matCanny, circles, HOUGH_GRADIENT, 1, min_d, param1, param2, min_r, max_r); //在原图中画出圆心和圆
for (size_t i = 0; i < circles.size(); i++) { //提取出圆心坐标
Point center(round(circles[i][0]), round(circles[i][1])); //提取出圆半径
int radius = round(circles[i][2]); //圆心
circle(image, center, 3, Scalar(255,0,0), -1, 4, 0); //圆
circle(image, center, radius, Scalar(255,0,0), 3, 4, 0); }

        imshow("matCanny", matCanny);
        imshow("BinImg", BinImg);
		imshow(windowname, image);
		if (waitKey(1) == 27)break;
	}
}


​ void on_Trackbar_1(int, void*) ​ { ​ ; ​ }


​ void on_Trackbar_2(int, void*) ​ { ​ ; ​ } ​
​ void on_Trackbar_3(int, void*) ​ { ​ ; ​ } ​
​ void on_Trackbar_4(int, void*) ​ { ​ ; ​ } ​
​ void on_Trackbar_5(int, void*) ​ { ​ ; ​ } ​
​ void on_Trackbar_6(int, void*) ​ { ​ ; ​ }

注:OpenCV提供的SHT输出极坐标下直线的rho和theta值,需根据经验值推算该直线上的两点以绘图;而PPHT直接返回线段两端两点坐标

*注:图像灰度化的方法:可参考OpenCV图像灰度化的六种方法_opencv灰度化_郑德帅的博客-CSDN博客

4.阈值分割(OTSU/三角法/自适应阈值)


​ #include ​ using namespace cv; ​ #pragma comment(lib,”opencv_world480d.lib”) ​
​ VideoCapture capture(0); ​ Mat image; ​
​ int main() ​ { ​ int OtsuBinary(Mat src); ​ int TriangleBinary(Mat src); ​ Mat gray, dst; ​
​ capture >> image; ​ cvtColor(image, gray, COLOR_BGR2GRAY); ​ int nThreshold = OtsuBinary(gray); ​ // int nThreshold = TriangleBinary(gray); ​ ​ while(capture.isOpened()) ​ { ​ capture >> image; ​ if (image.empty())break; ​ cvtColor(image, gray, COLOR_BGR2GRAY); ​
​ //用OTSU方法 ​ // threshold(gray, dst, nThreshold, 255, THRESH_BINARY); ​ // threshold(gray, dst, 0, 255, THRESH_BINARY | THRESH_OTSU); ​ //用三角法 ​ // threshold(gray, dst, nThreshold, 255, THRESH_BINARY); ​ // threshold(gray, dst, 0, 255, THRESH_BINARY|THRESH_TRIANGLE); ​ //自适应阈值 ​ adaptiveThreshold(gray, dst, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 7, 5); ​
imshow(“BinbaryImage”, dst); if (waitKey(1) == 27)break; } }

*注:自适应阈值保留信息更多,注意使用场合

5.识别车道(二值化、边缘检测、霍夫直线变换)


​ #include “opencv.hpp” ​ #include “highgui.hpp” ​ #include ​ using namespace cv; ​ #pragma comment(lib,”opencv_world480d.lib”) ​
​ int main() { ​ Mat image = imread(“car_track.jpg”, IMREAD_GRAYSCALE); ​ Mat BinImg; ​ threshold(image, BinImg, 150, 255, THRESH_BINARY); ​ imwrite(“BinImg.jpg”, BinImg); ​ Mat matCanny; ​ Canny(BinImg, matCanny, 100, 300, 3, false); //canny算子 ​ imwrite(“CannyImg.jpg”, matCanny); ​ Mat matDst = image.clone(); ​
​ std::vector linesSHT; ​ //标准霍夫变换检测直线,距离精度为1像素,角度精度为1度,阈值为100 ​ HoughLines(matCanny, linesSHT, 1, CV_PI / 180, 100); ​ //直线在原图上绘制 ​ matDst = image.clone(); ​ for (size_t i = 0; i < linesSHT.size(); i++) { ​ //直线的rho和theta值 ​ float rho = linesSHT[i][0], theta = linesSHT[i][1]; ​ //pt1和pt2是直线的两个端点,2000是经验值(满足覆盖所有前景点像素) ​ Point pt1, pt2; ​ double a = cos(theta), b = sin(theta); ​ double x0 = a * rho, y0 = b * rho; ​ pt1.x = cvRound(x0 + 2000 * (-b)); //把浮点数转化成整数 ​ pt1.y = cvRound(y0 + 2000 * (a)); ​ pt2.x = cvRound(x0 - 2000 * (-b)); ​ pt2.y = cvRound(y0 - 2000 * (a)); ​ line(matDst, pt1, pt2, Scalar(255), 8); ​ } ​
​ imshow(“car_track”, matDst); ​ waitKey(0); ​ imwrite(“car_track_show.jpg”, matDst); ​ }

处理结果:

九.特征提取和目标检测

<1>(特征:HOG/LBP/HAAR-LIKE)(分类:SVM/级联分类器)

<2>示例代码
1.HOG特征提取+SVM


​ //SVM参考代码 ​
​ #include “pch.h” ​ #include ​ #include ​ #include <windows.h> ​ //#include <afxwin.h> ​
​ #include ​ #include “opencv.hpp” ​ using namespace cv; ​ using namespace std; ​ using namespace cv::ml; ​
​ #ifdef _DEBUG ​ #pragma comment(lib, “opencv_world480d.lib”) ​ #else ​ #pragma comment(lib, “opencv_world480.lib”) ​ #endif ​
​ //************************************ ​ const char* file_path = “E:/测试视频数据/Video_2016_8_26__10_10_48.mp4”; ​ //************************************ ​
​ vector< float > get_svm_detector(const Ptr< SVM >& svm) ​ { ​ //得到支持向量 ​ Mat sv = svm->getSupportVectors(); ​ const int sv_total = sv.rows; ​ //得到支持向量对应的系数值 ​ Mat alpha, svidx; ​ double rho = svm->getDecisionFunction(0, alpha, svidx); ​
CV_Assert(alpha.total() == 1 && svidx.total() == 1 && sv_total == 1); CV_Assert((alpha.type() == CV_64F && alpha.at(0) == 1.) || (alpha.type() == CV_32F && alpha.at(0) == 1.f)); CV_Assert(sv.type() == CV_32F); //将支持向量的值写入一个vector返回 vector< float > hog_detector(sv.cols + 1); memcpy(&hog_detector[0], sv.ptr(), sv.cols * sizeof(hog_detector[0])); hog_detector[sv.cols] = (float)-rho; return hog_detector; }

//sampleMat是采样矩阵,labelMat是类别矩阵,nCurRows当前是矩阵的行数
//提取正样本HOG特征
void PosData(HOGDescriptor& hog, Mat& sampleMat, vector<int>& Labels, int& nRowIdx)
{
	vector<String> files; //文件名列表
	//************************************
	glob("pos_src/*.*", files); //搜索positive目录下所有文件
	//************************************
	for (size_t i = 0; i < files.size(); ++i) {
		Mat imgSrc = imread(files[i], IMREAD_GRAYSCALE); //加载图像
		if (imgSrc.empty()) {
			cout << files[i] << " is invalid!" << endl;
			continue;
		}
		Mat imgDst;
		resize(imgSrc, imgDst, hog.winSize); //将正例缩放到检测窗口大小
		vector<float> featureVec;
		hog.compute(imgDst, featureVec, Size(8, 8), Size(0, 0));
		//将特征向量加入采样矩阵
		for (int i = 0; i < featureVec.size(); i++) {
			sampleMat.at<float>(nRowIdx, i) = featureVec[i];
		}
		nRowIdx++;
		Labels.push_back(+1); //正样本类别为+1 
		cout << nRowIdx << files[i] << endl;
	}
}

//提取负样本HOG特征
void NegData(HOGDescriptor& hog, Mat& sampleMat, vector<int>& Labels, int& nRowIdx)
{
	vector<String> files; //文件名列表
	//************************************
	glob("neg_src/*.*", files); //搜索positive目录下所有文件
	//************************************
	Rect box;
	box.width = hog.winSize.width;
	box.height = hog.winSize.height;
	for (size_t i = 0; i < files.size(); ++i) {
		Mat img = imread(files[i], IMREAD_GRAYSCALE);
		if (img.empty()) {
			continue;
		}
		Mat matDst;
		if (img.cols <= hog.winSize.width + 1 || img.rows <= hog.winSize.height + 1) {
			//cout << "image too small" << endl;
			resize(img, matDst, hog.winSize);
		}
		else {
			//随机选择窗口位置
			box.x = rand() % (img.cols - box.width);
			box.y = rand() % (img.rows - box.height);
			matDst = img(box);
		}

		vector<float> featureVec;
		hog.compute(matDst, featureVec, Size(8, 8), Size(0, 0));
		//将特征向量加入采样矩阵
		for (int i = 0; i < featureVec.size(); i++) {
			sampleMat.at<float>(nRowIdx, i) = featureVec[i];
		}
		nRowIdx++;
		Labels.push_back(-1);//负样本类别为-1 
		cout << nRowIdx << files[i] << endl;
	}
}

void TrainSVMModel()
{
	//车牌检测window大小为128X48,block大小为16X16,cell大小为8X8,滑动窗口大小为8X8
	HOGDescriptor hog(cv::Size(128, 48), cv::Size(16, 16), cv::Size(8, 8), cv::Size(8, 8), 9);
	int nVecLen = hog.getDescriptorSize();
	//样本的特征向量,行数等于正负样本个数,列数等于HOG特征向量长度
	Mat sampleFeatureMat = Mat::zeros(9689, nVecLen, CV_32FC1);
	//样本的类别向量,行数等于所有样本的个数,列数等于1;1表示正样本,-1表示负样本
	vector<int> Labels;
	int nRowIdx = 0;
	PosData(hog, sampleFeatureMat, Labels, nRowIdx);
	NegData(hog, sampleFeatureMat, Labels, nRowIdx);


​ Ptr svm = SVM::create(); //创建一个SVM分类器 ​ svm->setCoef0(0.0); ​ svm->setDegree(3); ​ svm->setGamma(0); ​ svm->setKernel(SVM::LINEAR); ​ svm->setNu(0.5); ​ svm->setP(0.1); ​ svm->setC(0.01); ​ svm->setType(SVM::EPS_SVR); //分类器类型为EPS_SVR ​ //************************************ ​ //训练结束条件:要么达到1000次,要么两次误差小于1e-3 ​ svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 1e-3)); ​ //************************************ ​ svm->train(sampleFeatureMat, ROW_SAMPLE, Labels); ​
​ //svm->trainAuto(); ​ //直接将支持向量系数写入文本文件,以方便在检测器的头文件中导入 ​ vector vecHogCof = get_svm_detector(svm); ​ //************************************ ​ //支持向量结果保存 ​ ofstream file(“dector.txt”); ​ //************************************ ​ for (int i = 0; i < vecHogCof.size(); i++) { ​ file << vecHogCof[i] << “,”; ​ } ​ file.close(); ​ }


​ void TestSVMModel() ​ { ​ //************************************ ​ //SVM检测器系数向量都放在hogCof数组中(detect.txt中内容,模型建立好后可直接调用) ​ float hogCof[] = {……}; ​ //************************************ ​ ​ //创建HOG检测器,参数与训练时的参数相同 ​ //在这里特别注意将nLevels参数从默认64修改为4,可以加快检测速度 ​ HOGDescriptor hog(cv::Size(128, 48), cv::Size(16, 16), cv::Size(8, 8),
​ cv::Size(8, 8), 9, 1, -1.0, HOGDescriptor::L2Hys, 0.2, false, 4); ​ const int vecLen = sizeof(hogCof) / sizeof(float); ​ vector vecHogCof(hogCof, hogCof + vecLen); ​ hog.setSVMDetector(vecHogCof); //设置HOG检测器的系数 ​
​ //打开一个视频文件 ​ VideoCapture cap; ​ cap.open(file_path); ​ if (!cap.isOpened()) { ​ return; ​ } ​ Mat frame; ​ int nFrmIdx = 0; ​ while (true) { ​ cap >> frame; ​ if (frame.empty()) { ​ break; ​ } ​ vector detections; //检测到目标矩形位置 ​ vector foundWeights; //检测到的权重 ​ hog.detectMultiScale(frame, detections, foundWeights, 0.5, Size(8, 8), Size(0, 0), 1.1, 3.0, false); ​ for (int i = 0; i < detections.size(); i++) { ​ if (frame.rows - detections[i].y < 200) ​ continue; ​ rectangle(frame, detections[i], Scalar(0, 0, 255), 4); ​ } ​ imshow(“LP HOG Detection”, frame); ​ if (detections.size() > 0) { ​ char szFileName[100] = { 0 }; ​ sprintf_s(szFileName, “%03d.jpg”, nFrmIdx++); ​ imwrite(szFileName, frame); ​ } ​
​ waitKey(40); ​ } ​ } ​
​ int mian() ​ { ​ TestSVMModel(); ​ }

注:

①.//*…*之间的是copy代码时要修改的地方

②.void TrainSVMModel()求出支持向量系数,保存在文件中,之后可以直接复制其中数值以调用

2.获取LBP/MB-LBP特征函数


​ //使用函数模板,保证函数对所有类型图像都适用 ​ //_tp参数可以是uchar,float等 ​ template ​ //原始LBP特征 ​ void getOriginLBPFeature(InputArray _src, OutputArray _dst) ​ { ​ Mat src = _src.getMat(); ​ Mat srcExtented; ​ //对图像边界进行扩充,边界像素采用复制的形式 ​ copyMakeBorder(src, srcExtented, 1, 1, 1, 1, BORDER_REPLICATE); ​ //输出图像与原图像大小相同 ​ _dst.create(src.rows, src.cols, CV_8UC1); ​ Mat dst = _dst.getMat(); ​ dst.setTo(0); ​ for (int i = 0; i < src.rows; i++) { ​ for (int j = 0; j < src.cols; j++) { ​ //中心像素的值 ​ _tp center = srcExtented.at<_tp>(i + 1, j + 1); ​ unsigned char lbpCode = 0; //LBP编码值 ​ lbpCode |= (srcExtented.at<_tp>(i, j) > center) << 7; //左上角 ​ lbpCode |= (srcExtented.at<_tp>(i, j + 1) > center) << 6; //上边 ​ lbpCode |= (srcExtented.at<_tp>(i, j + 2) > center) << 5; //右上角 ​ lbpCode |= (srcExtented.at<_tp>(i + 1, j + 2) > center) << 4; //右边 ​ lbpCode |= (srcExtented.at<_tp>(i + 2, j + 2) > center) << 3; //右下角 ​ lbpCode |= (srcExtented.at<_tp>(i + 2, j + 1) > center) << 2; //下边 ​ lbpCode |= (srcExtented.at<_tp>(i + 2, j) > center) << 1; //左下角 ​ lbpCode |= (srcExtented.at<_tp>(i + 1, j) > center) << 0; //左边 ​ dst.at(i, j) = lbpCode; ​ } ​ } ​ } ​
​ //MB-LBP特征 ​ void getMultiScaleBlockLBPFeature(InputArray _src, OutputArray _dst, int scale) ​ { ​ Mat src = _src.getMat(); ​ int cellSize = scale / 3; ​ int offset = cellSize / 2; ​ Mat srcExtented; ​ //图像扩大一圈 ​ copyMakeBorder(src, srcExtented, offset, offset, offset, offset, BORDER_REFLECT); ​ //以当前点为中心,计算每个cell的像素均值 ​ Mat cellImage(src.rows, src.cols, CV_8UC1); ​ for (int i = 0; i < src.rows; i++) { ​ for (int j = 0; j < src.cols; j++) { ​ int temp = 0; ​ for (int m = -offset; m < offset + 1; m++) { ​ for (int n = -offset; n < offset + 1; n++) { ​ temp += srcExtented.at(i + n + offset, j + m + offset); ​ } ​ } ​ temp /= (cellSize * cellSize); ​ cellImage.at(i, j) = uchar(temp); ​ } ​ } ​ getOriginLBPFeature(cellImage, _dst); ​ }

3.cascade级联分类器使用


​ //级联分类器实现人脸检测 ​ void DetectFaces() ​ { ​ //创建一个级联分类器对象,并加载分类器文件 ​ //CascadeClassifier faceDetector(“haarcascade_frontalface_alt2.xml”); ​ CascadeClassifier faceDetector(“cascade.xml”); ​ if (faceDetector.empty()) { ​ return; ​ } ​ VideoCapture cap(0); //打开USB摄像头 ​ if (!cap.isOpened()) { ​ return; ​ } ​ Mat frame; ​ while (true) { ​ cap >> frame; //从摄像头获取一帧图像 ​ if (frame.empty()) ​ break; ​ std::vector<cv::Rect> objects; ​ //使用级联分类器检测人脸 ​ faceDetector.detectMultiScale(frame, objects); ​ //对人脸图像进行标记 ​ for (int i = 0; i < objects.size(); i++) { ​ static int nIdx = 0; ​ char szFileName[100] = { 0 }; ​ sprintf_s(szFileName, “detectedHeadShoulder/%03d.jpg”, nIdx++); ​ //sprintf_s(szFileName, “DetetecdFaces/%03d.jpg”, nIdx++); ​ cv::rectangle(frame, objects[i], Scalar(0, 0, 255), 4); ​ imwrite(szFileName, frame); ​ } ​ imshow(“人脸检测结果”, frame); //显示人脸检测结果 ​
​ if (waitKey(25) == 27) //暂停25ms,如果按ESC键则退出 ​ break; ​ } ​ cap.release(); //释放摄像头对象 ​ return; ​ }

注:分类器文件生成使用opencv_traincacade.exe;创建正样本.vec文件使用opencv_creatsamples.exe;可视化过程使用opencv_visualisation.exe

本文转自 https://blog.csdn.net/qq_32971095/article/details/131609797,如有侵权,请联系删除。

> --------------- THE END -------------- <