OpenCV学习笔记——《基于OpenCV的数字图像处理》

2023-11-07

源码下载:下载资源包 (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/级联分类器)

<2>示例代码

1.HOG特征提取+SVM

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

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 <iostream>             //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 <iostream>
#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博客

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 <iostream>
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 <iostream>
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<Vec3b>(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<uchar>(pow((float)(i / 255.0),\
			fGamma) * 255.0f);
	}

	dst = src.clone();
	const int channels = dst.channels();
	switch (channels){
	case 1:
	{
		//MatIterator_<uchar> it;
		//for (it = dst.begin<uchar>(); it != dst.end<uchar>(); 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<uchar>(j, i);
				dst.at<uchar>(j, i) = lut[val];
			}
		}
		break;
	}
	case 3:
	{
		MatIterator_<Vec3b> it;
		for (it = dst.begin<Vec3b>(); it != dst.end<Vec3b>(); it++){
			(*it)[0] = lut[((*it)[0])];
			(*it)[1] = lut[((*it)[1])];
			(*it)[2] = lut[((*it)[2])];
		}
		break;
	}
	}
}

注:saturate_cast<>的使用(防止颜色溢出)参考:【OpenCV】中saturate_cast<uchar>的含义和用法是什么?_人工智能博士的博客-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<float>(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<Mat> 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<float>(i) += dstHist.at<float>(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<float>(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<Vec2f> 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<Vec4i> 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<Vec3f> 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<uchar>(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<uchar>(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 <iostream>
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<float>(i, j) < 0.1 * dblMaxVal) {
				continue;
			}
			//水平、垂直、45度方向,135度4个方向过零点判定
			if (laplacian.at<float>(i - 1, j) \
				* laplacian.at<float>(i + 1, j) < 0)
				result.at<uchar>(i, j) = 255;
			if (laplacian.at<float>(i, j + 1) \
				* laplacian.at<float>(i, j - 1) < 0)
				result.at<uchar>(i, j) = 255;
			if (laplacian.at<float>(i + 1, j + 1) \
				* laplacian.at<float>(i - 1, j - 1) < 0)
				result.at<uchar>(i, j) = 255;
			if (laplacian.at<float>(i - 1, j + 1) \
				* laplacian.at<float>(i + 1, j - 1) < 0)
				result.at<uchar>(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 <iostream>
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<Vec2f> 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<Vec4i> 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<Vec3f> 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 <iostream>
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<Vec3f> 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 <iostream>
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 <iostream>
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<Vec2f> 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 <iostream>	
#include <fstream>
#include <windows.h>
//#include <afxwin.h>

#include <stack>
#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<double>(0) == 1.) ||
		(alpha.type() == CV_32F && alpha.at<float>(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 = 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<float> 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<float> 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<Rect> detections; //检测到目标矩形位置
		vector<double> 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 <typename _tp>
//原始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<uchar>(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<uchar>(i + n + offset, j + m + offset);
				}
			}
			temp /= (cellSize * cellSize);
			cellImage.at<uchar>(i, j) = uchar(temp);
		}
	}
	getOriginLBPFeature<uchar>(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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

OpenCV学习笔记——《基于OpenCV的数字图像处理》 的相关文章

随机推荐

  • 查询数据库所占空间大小

    目录 统计数据库整体所占大小 统计数据库中各表所占大小 统计数据库整体所占大小 select table schema as 数据库 sum table rows as 记录数 sum truncate data length 1024 1
  • 华为19级专家10年心血终成百页负载均衡高并发网关设计实战文档

    负载均衡 LoadBalance 的字面意思是将工作负载分担到多个工作单元上进行执行 它建立在现有网络结构之上 是构建分布式服务 大型网络应用的关键组件 近十几年来 负载均衡技术层出不穷 令人眼花缭乱 如果问身边的技术人员什么是负载均衡 我
  • Vjava学习笔记之(VirtualMachine 内存(总容量和已使用))

    源代码 package com vmware client import com vmware util Session import com vmware vim25 HostListSummary import com vmware v
  • 11G RAC 中 OCR 及Voting Disk 相关操作

    一 启动oracle clusterware 先决条件 Oracle High Availability Services daemon OHASD 运行在所有集群节点上 1 启动整个Oracle Clusterware stack crs
  • windows下git

    下载gitGit for Windows Windows安装git图文教程 喵代王 香菜的博客 CSDN博客 windows安装git 创建文件夹 右键 git bash here 同mac使用
  • 基于Spring Boot的ERP仓储管理信息系统设计与实现毕业设计源码150958

    基于Spring Boot的ERP仓储管理信息系统设计与实现 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化 电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用 信息时代的到来已成为不可阻挡的时尚潮流 人类发展的
  • 如何实现一个IO口读取多个设备信息

    前言 1 今天遇到一个有意思的问题一个IO口如何读取多个电机的堵转问题 之后他就发了一张图片 2 看到这个问题 之前先说一个简单的 我们如何实现一个IO读取多个按键 了解了这个之后 对于多个电机堵转就很好理解了 如何实现一个IO对多个按键读
  • 直方图均衡化原理

    原文 http www cnblogs com tianyalu p 5687782 html 直方图均衡化原理 直方图均衡化的作用是图像增强 有两个问题比较难懂 一是为什么要选用累积分布函数 二是为什么使用累积分布函数处理后像素值会均匀分
  • 从零开始的Java开发 笔记目录(跑路了)

    写在前面 不全 学习资料来源于网络 已经跑路了 文章目录 阶段1 Java零基础入门 第1周 环境搭建与语法入门 第2周 Java语法之循环 数组与方法 第3周 面向对象之封装与继承 第4周 面向对象之单例模式与多态 第5周 常用工具类 上
  • linux c++遍历文件夹下所有文件,C++ 遍历目录下文件

    function 遍历目录下所有文件 返回文件总数 子文件夹总数 修改一下可以获得全部文件名等 include stdlib h include direct h include string h include io h include
  • 对OOD/OOP有较深的理解

    最近 经常有很多人在求职的时候遇到这样一个问题 对OOD OOP有较深的理解 那OOD OOP又是什么 那今天就来讲讲它们都是些什么 又如何去回答 1 OOA Object oriented analysis 面向对象分析 面向对象分析方法
  • 一款带ai基因的向导般生成ppt的神奇网站

    只要按要求填写每一页的内容 即可生成一套像模像样的ppt 无需排版 模板众多 以后ppt不需要人写了 哈哈 1 登录 https app slidebean com 2 注册 3 新建 4 模板选择 5 填写 以airbnb为例 6 结果
  • 【微信读书每日一答辅助小程序】使用python对每日一答问题进行识别,并将结果保存到剪贴板以便搜索。

    目录标题 1 环境准备 2 获取屏幕位置 3 指定区域屏幕截图 4 文字识别 5 按键识别并保存到剪贴板 在腾讯收购阅文之后 微信读书的无限卡已经不能免费看书了 这时白嫖微信读书每日一答的书币成了不错的选择 严重偏科又手速垃圾的我在等级升高
  • Win10 解决docker一直docker desktop starting进不去的问题

    这里写自定义目录标题 为什么出现这个问题 方法1 方法2 方法3 解决我的问题 后续计划 为什么出现这个问题 似乎是因为上次没有完全关闭 而是直接关闭电脑导致的 目前有三种方法 后续应该有更多 我这边方法1 2都没有解决我的问题 方法3解决
  • Rxjs 操作符实践指南

    操作符实战 1 工具方法型 count 统计总数 import range from rxjs import count from rxjs operators const numbers range 1 7 const result nu
  • python中16mod7_mod_python模块安装

    两 mod python 1 性能 使用mod python的主要优势在于比传统CGI更高的性能 一个測试 使用在Pentium 1 2GHz的机器上执行Red Hat Linux 7 3 使用4种类型的脚本 基于标准的CGI导入模块 以典
  • Android Glide加载图片圆角效果与ImageView的ScaleType冲突问题

    在imageVIew显示图片的时候一般是使用 android scaleType centerCrop 来让图片不被变形显示 但是如果现在用Glide来加载图片并给它转化出一个圆角 transform new GlideRoundTrans
  • 【导航】ESP32-C3 入门教程目录 【快速跳转】

    本文是 矜辰所致 的ESP32 C3 专栏的内容导航 结合自己的学习应用过程的总结记录 ESP32 C3入门教程 前言 一 环境篇 二 硬件篇 三 基础篇 四 Wi Fi篇 五 蓝牙篇 六 应用篇 前言 本系列教程以实际应用为目的 能够使得
  • 代码随想录 - Day37 - 贪心算法

    代码随想录 Day37 贪心算法 376 摆动序列 排除只有一个数的情况 把差值全部求出来放到dif里 在此过程中顺便去掉差值为0的情况 如果dif为空 说明里面所有差值为0 那么最长摆动序列只能是1 直接返回 如果dif不为空 把dif
  • OpenCV学习笔记——《基于OpenCV的数字图像处理》

    源码下载 下载资源包 bookln cn 常用函数库 英文 OpenCV OpenCV modules 中文 Welcome to opencv documentation OpenCV 2 3 2 documentation jetson