电赛学习笔记(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 -------------- <