OpenCV 笔记

TIP

注意所有 C++ 源文件使用 Visual Studio 2019 编辑器打开否则会出现中文注释乱码

环境搭建

环境介绍

下载后安装,安装完成共有两个文件夹,一个是编译后的一个是源码

环境配置

D:\localProgram\opencv\opencv\build\x64\vc15\bin 安装目录文件路径,配置到电脑环境变量,然后重启项目

使用 vs 创建一个空项目和项目配置

创建完成项目如果编译器是 x86 改成如下

然后为项目添加目录和 opencv 库做为依赖,点击项目,选择属性

分别添加 添加生成目录,添加库目录 如下:

  • 添加生成目录 D:\localProgram\opencv\opencv\build\include

  • 添加库目录 D:\localProgram\opencv\opencv\build\x64\vc15\lib

配置附加依赖项:

D:\localProgram\opencv\opencv\build\x64\vc15\lib 下的文件夹中 opencv_world454d.lib文件用于调试。 opencv_world454.lib文件用于正式发布,在属性如图中进行配置。 注:两个文件的区别在于是否带 d,带 d 的用于调试环境,不带 d 用于发布环境。vs 编辑器 2017 以前使用 vc14。vs2017 以后版本编辑器使用 vc15

opencv_contrib 模块

为人脸模型训练模块在 opencv_contrib 模块中,因此想要训练人脸模型必须使用 opencv_contrib 模块,opencv_contrib 模块版本必须与 opencv 库版本相对应 人脸模型训练模块 https://github.com/opencv/opencv_contrib/tagsopen in new window

Cmaker 编译工具 https://cmake.org/download/open in new window

Cmaker 编译工具 下载后解压找到目录 D:\download\tool\cmake-3.22.1-windows-x86_64\bin 运行 .exe 程序选择 opencv 源码目录,点击 configure 按钮

编译报错,无法从远程下载文件,,重新点击 confifure 按钮 在 CMake 中,Where is the source code 添加 opencv 文件夹,即/Opencv/opencv-4.5.4(注意:如果/Opencv/opencv-4.5.4 下还有一层文件夹 opencv-4.5.4,请用/Opencv/opencv-4.5.4/opencv-4.5.4);

在 CMake 中,Where to build the binaries 添加 build 文件夹,即/Opencv/build

点击 configure,选择你使用的 Visual Studio 版本,选择平台(一般是 x64)

注意:如果这里版本选错,点击 File > DeleteCache。再点击 Configure 重新选择。

点击 finish;

再次点击 configure,等待(过程中有文件下载,请先搭好梯子)

完成后,在编译选项中进行勾选。BUILD_CUDA_STUBS、OPENCV_DNN_CUDA、WITH_CUDA、OPENCV_ENABLE_NONFREE、build_opencv_world 打勾

找到编译选项 OPENCV_EXTRA_MODULES_PATH ,将 Value 设置为/Opencv/opencv_contrib-4.5.4/modules(即/Opencv/opencv_contrib-4.5.4 中的 modules 目录,注意中间有没有多一层文件夹);

点击 configure,等待至出现 configuring done;

注:若报错,先查报错并修改后重试;若多次重试,仍报同一个错误,建议新建 build1。修改 Where to build the binaries 为/Opencv/build1;点击 File > DeleteCache。再点击 Configure 重新开始。

点击 generate,等待至出现 generating done;

点击 open project。

对照报错和日志文件可以看出,日志中提示我们手动下载缺失文件

手动下载后修改文件命名,MD5 值+'-'+下载的文件名。比如 opencv_videoio_ffmpeg_64.dll 这个文件下载后凡在.cache 下的 ffmpeg 下,然后改名为 b8120c07962d591e2e9071a1bf566fd0-opencv_videoio_ffmpeg_64.dll 替换原来 0KB 的那个文件

ffmpeg_version.cmake 文件访问后,另存为 txt 文本,然后修改文件名称和后缀。

下载失败文件搞定后,重新点击 configure,等待至出现 configuring done

程序和测试

测试项目配置

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

//测试opencv 项目配置
using namespace cv;
using namespace std;

int main() {
	string path = "Resources/test.png";
	Mat img = imread(path);
	imshow("Image", img);
	waitKey(0);
	return 0;
};

图像、视频、网络摄像头

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;


// 一、阅读图像、视频和网络摄像头
// 1.加载图形

void main() {
	string path = "Resources/test.png";
	// Mat 基于 opencv 引用的矩阵数据类型
	Mat img = imread(path);


	// imshow 用于显示图像
	imshow("Image", img);
	// 由于显示图像后会立刻关闭,所以waitKey(0) 表示无穷大 这样打开了不会关闭
	waitKey(0);
};



// 2.加载视频:视频播放完成后,会出现一个错误,错误原因是没有可播放的图片了

void main() {
	string path = "Resources/test_video.mp4";
	// 视频捕获
	VideoCapture cap(path);
	Mat img;
	// 由于视频由一系列的图片组成,所以使用循环
	while (true) {
		// 视频每一帧图片的渲染
		cap.read(img);
		// imshow 用于显示图像
		imshow("Image", img);
		// 由于显示图像后会立刻关闭,所以waitKey(0) 表示无穷大 这样打开了不会关闭
		// waitKey() 数值越大播放越慢
		waitKey(20);
	}

};


// 3.加载网络摄像头

void mainVoid() {
	// cap(1) 标识摄像头id
	VideoCapture cap(0);
	Mat img;
	由于视频由一系列的图片组成,所以使用循环
	while (true) {
		// 视频每一帧图片的渲染
		cap.read(img);
		// imshow 用于显示图像
		imshow("Image", img);
		// waitKey() 数值越大播放越慢
		waitKey(1);
	}
};

图像基本功能

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

// 转换图像颜色、高斯模糊、检测图像边缘、扩大图像、侵蚀图像

void main() {

	string path = "Resources/test.png";
	Mat img = imread(path);
	Mat imgGray, imgBlur, imgCanny, imgDia, imgErode;
	// 转换图像颜色
	cvtColor(img, imgGray, COLOR_BGR2GRAY);
	// 添加图像模糊-高斯模糊
	GaussianBlur(imgGray, imgBlur, Size(7, 7), 5, 0);
	// canny 图像边缘检测器
	Canny(img, imgCanny, 50, 150);

	// 扩大图像(加粗图像检查测边缘线条)
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
	dilate(imgCanny, imgDia, kernel);

	// 侵蚀
	erode(imgDia, imgErode, kernel);


	// 显示图像为正常颜色
	imshow("Image", img);
	// 显示图像为灰色
	imshow("Image Gray", imgGray);
	// 显示高斯模糊图像
	imshow("Image Blur", imgBlur);
	// 显示检测图像边缘
	imshow("Image Canny", imgCanny);
	// 扩大图像(加粗图像检查测边缘线条)
	imshow("Image Dilataion", imgDia);
	// 侵蚀图像
	imshow("Image Erode", imgErode);

	waitKey(0);
};

调整大小并裁剪图像大小

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void main() {
	string path = "Resources/test.png";
	Mat img = imread(path);
	Mat imgResize, imgCrop;

	// 打印图像大小
	cout << img.size() << endl;

	// 1.调整图像大小 -- 指定大小缩放
	// resize(img, imgResize, Size(301,306));
	// 调整图像大小 -- 按照百分比缩放,原图像为1
	 resize(img, imgResize, Size(), 0.5,0.5);

	 //2.裁剪图像
	 Rect roi(100,100,300,250);
	 imgCrop = img(roi);


	// 原图像大小
	imshow("Image", img);
	// 调整后的图像大小
	imshow("Image Resize", imgResize);
	// 裁剪图像
	imshow("Image Crop", imgCrop);
	waitKey(0);

};

图形形状和文本

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void main() {

	// 创建空白图片
	Mat img(521,521, CV_8UC3, Scalar(255,255,255));
	// 创建一个圆-边框厚度为10
	//circle(img, Point(256, 256), 155, Scalar(255, 98, 0), 10);


	// 创建一个圆-完全填充颜色
	circle(img, Point(256, 256), 155, Scalar(255, 98, 0), FILLED);
	// 长方形
	rectangle(img, Point(130,226), Point(283,286),Scalar(255,255,255), FILLED);
	// 创建一根线
	line(img, Point(130, 296), Point(382, 296), Scalar(255, 255, 255), 2);
	// 存放文本
	putText(img, "workShopText", Point(137, 262), FONT_HERSHEY_DUPLEX, 3,Scalar(255, 255, 255), 2);

	imshow("Image", img);

	waitKey(0);

};

扭曲和透视

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

float w = 250, h = 350;//图片大小
Mat matrix, imgWarp;

void main() {
	//图片用画图打开,在屏幕左下角会显示点的坐标
	string path = "Resources/cards.jpg";
	Mat img=imread(path);//matrix data type 由opencv引入来处理图像
	Point2f src[4] = { {529,142},{771,190},{405,395},{674,457} };//Point2f表示浮点数
	Point2f dst[4] = { {0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h} };//Point2f表示浮点数

	matrix = getPerspectiveTransform(src, dst);
	warpPerspective(img, imgWarp, matrix, Point(w,h));

	//确定src坐标是否正确
	for (int i = 0; i < 4; i++) {
		circle(img, src[i], 10, Scalar(0, 0, 255), FILLED);
	}

	imshow("Image", img);
	imshow("Image Warp", imgWarp);
	waitKey(0);//增加延时,0表示无穷
}

颜色检测

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

Mat imgHSV, mask;
int hmin = 0, smin = 110, vmin = 153;
int hmax = 19, smax = 240, vmax = 255; // 如何确定这6个值,每次都更改所有这些再次运行很痛苦 -->创建跟踪栏(使我们可以实时更改这些值)

int main() {
	string path = "Resources/shapes.png";
	Mat img = imread(path);
	// 转换图像到HSV空间,在其中查找颜色更加容易
	cvtColor(img, imgHSV, COLOR_BGR2HSV);

	// 创建控制窗口
	namedWindow("Trackbars", (640, 200));

	// 控制图现象显示 运行时,把3个min的都移到最小值,把3个max的都移到最大值,然后移动使其保持为白色
	createTrackbar("Hue Min", "Trackbars", &hmin, 179); // 对于hue色相饱和度最大180,对于另外两个色相饱和度最大255
	createTrackbar("Hue Max", "Trackbars", &hmax, 179);
	createTrackbar("Sat Min", "Trackbars", &smin, 255);
	createTrackbar("Sat Max", "Trackbars", &smax, 255);
	createTrackbar("Val Min", "Trackbars", &vmin, 255);
	createTrackbar("Val Max", "Trackbars", &vmax, 255);

	while (true)
	{
		//检查数组元素是否位于其他两个数组的元素之间。
		Scalar lower(hmin, smin, vmin);
		Scalar upper(hmax, smax, vmax);
		// imgHSV为输入图像,mask为输出图像
		inRange(imgHSV, lower, upper, mask);
		imshow("Image", img);
		imshow("Image HSV", imgHSV);
		imshow("Image HSV", imgHSV);
		imshow("Image Mask", mask);
		waitKey(1);
	}

	return 0;
};

形状轮廓检测

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

// 接收图像 Mat 数据类型在 opencv 中是矩阵数据
Mat imgGray, imgBlur, imgCanny, imgDil, imgErode;


//imgDil是传入的扩张边缘的图像用来查找轮廓,img是要在其上绘制轮廓的图像
void getContours(Mat imgDil, Mat img) {

	/*{
		{Point(20,30),Point(50,60)},
		{},
		{}
	}*/
	// 轮廓检测到的轮廓。每个轮廓线存储为一个点的向量
	vector<vector<Point>> contours;
	// 包含关于映像拓扑的信息  typedef Vec<int, 4> Vec4i;具有4个整数值
	vector<Vec4i> hierarchy;
	// 在二值图像中查找轮廓。该函数利用该算法从二值图像中提取轮廓
	findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	// img:要绘制轮廓在什么图片上,contours:要绘制的轮廓,-1定义要绘制的轮廓号(-1表示所有轮廓),Saclar表示轮廓颜色,2表示厚度
	// drawContours(img, contours, -1, Scalar(255, 0, 255), 4);

	vector<vector<Point>> conPoly(contours.size());//conploy的数量应小于contours
	vector<Rect> boundRect(contours.size());
	//过滤器:通过轮廓面积来过滤噪声
	for (int i = 0; i < contours.size(); i++) {//遍历检测到的轮廓
		int area = contourArea(contours[i]);

		cout << "area:" << area << endl;

		string objectType;
		// 轮廓面积>1000才绘制
		if (area > 1000) {
			//计算轮廓周长或凹坑长度。该函数计算了曲线长度和封闭的周长。
			float peri = arcLength(contours[i], true);//计算封闭轮廓周长
			// 以指定的精度近似多边形曲线。第二个参数conPloy[i]存储近似的结果,是输出。

			/*
				approxPolyDP()函数是opencv中对指定的点集进行多边形逼近的函数,其逼近的精度可通过参数设置。
				第三个参数double epsilon:指定的精度,也即是原始曲线与近似曲线之间的最大距离。
				第四个参数bool closed:若为true,则说明近似曲线是闭合的;反之,若为false,则断开
			*/
			approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);
			//计算边界矩形
			boundRect[i] = boundingRect(conPoly[i]);
			//找近似多边形的角点,三角形有3个角点,矩形/正方形有4个角点,圆形>4个角点
			int objCor = (int)conPoly[i].size();

			cout << "图形存在几个顶点:" << objCor << endl;
			// 为不同顶点的图形添加类型
			if (objCor == 3) { objectType = "Tri"; }
			else if (objCor == 4) {
				//宽高比
				float aspRatio = (float)boundRect[i].width / (float)boundRect[i].height;
				//矩形的宽高比不会正好等于1
				if (aspRatio > 0.95 && aspRatio < 1.05) {
					objectType = "Square";
				}else objectType = "Rect";
			}
			else if (objCor > 4) { objectType = "Circle"; }

			drawContours(img, conPoly, i, Scalar(255, 0, 255), 2);
			rectangle/*绘制边界矩形*/(img, boundRect[i].tl()/*tl():topleft矩形左上角坐标*/, boundRect[i].br()/*br():bottom right矩形右下角坐标*/, Scalar(0, 255, 0), 5);
			putText(img, objectType, { boundRect[i].x,boundRect[i].y - 5 }/*文字坐标*/, FONT_HERSHEY_PLAIN, 1, Scalar(0, 69, 255), 2);
		}
	}
}

void main() {
	string path = "Resources/shapes.png";
	Mat img = imread(path);
	/* 图像经过处理称为:预处理 */
	// 转换图像颜色
	cvtColor(img, imgGray, COLOR_BGR2GRAY);
	// 添加图像模糊-高斯模糊
	GaussianBlur(imgGray, imgBlur, Size(7, 7), 5, 0);
	// canny 图像边缘检测器
	Canny(img, imgCanny, 50, 150);
	// 扩大图像(加粗图像检查测边缘线条)创建一个核,增加Size(只能是奇数)会扩张/侵蚀更多
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
	dilate(imgCanny, imgDil, kernel);

	//img是在其上绘轮廓的图片
	getContours(imgDil, img);


	imshow("Image", img);
	//imshow("Image Gray", imgGray);
	//imshow("Image Blur", imgBlur);
	//imshow("Image Canny", imgCanny);
	//imshow("Image Dil", imgDil);
	waitKey(0);
};

人脸检测

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void main() {
	string path = "Resources/test.png";
	Mat img = imread(path);

	// 加载人脸检测训练模型
	CascadeClassifier faceCascade;
	faceCascade.load("Resources/haarcascade_frontalface_default.xml");
	// 判断是否为空
	if (faceCascade.empty()){cout << "XML文件未加载" << endl;}
	// 创建矩形变量,用于标识人脸
	vector<Rect> faces;
	// 使用人脸级连点检测多尺度方法
	faceCascade.detectMultiScale(img, faces, 1.1, 10);
	// 遍历人脸
	for (int i = 0; i < faces.size(); i++) {
		/*
		rectangle 绘制边界矩形
			tl():topleft矩形左上角坐标
			br():bottom right矩形右下角坐标
		*/
		rectangle(img, faces[i].tl(), faces[i].br(), Scalar(0, 255, 0), 5);
	}
	imshow("Image", img);
	waitKey(0);
};

检测物体轮廓

#include<opencv2/imgcodecs.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>

using namespace std;
using namespace cv;


/// <summary>
/// Project 1
/// 使用HSV空间检测颜色、找到轮廓所在位置、取轮廓的位置然后创建一个圆
/// </summary>

Mat img;
vector<vector<int>> newPoints;

vector<vector<int>> myColors{ {98,109,54,127,255,255},//蓝色(hmin smin vmin hmax smax vmax)
								{35,0,0,77,245,255} };//绿色(hmin smin vmin hmax smax vmax)

vector<Scalar> myColorValues{ {255,0,255},//蓝色
								{0,255,0} };//绿色

//! 获取轮廓
Point getContours(Mat imgDil) {//imgDil是传入的扩张边缘的图像用来查找轮廓,img是要在其上绘制轮廓的图像
	vector<vector<Point>> contours;//轮廓检测到的轮廓。每个轮廓线存储为一个点的向量

	vector<Vec4i> hierarchy;//包含关于映像拓扑的信息  typedef Vec<int, 4> Vec4i;具有4个整数值

	//在二值图像中查找轮廓。该函数利用该算法从二值图像中提取轮廓
	findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	//drawContours(img, contours, -1, Scalar(255, 0, 255), 2);//img:要绘制轮廓在什么图片上,contours:要绘制的轮廓,-1定义要绘制的轮廓号(-1表示所有轮廓),Saclar表示轮廓颜色,2表示厚度

	vector<vector<Point>> conPoly(contours.size());//conploy的数量应小于contours
	vector<Rect> boundRect(contours.size());

	Point myPoint(0, 0);

	//过滤器:通过轮廓面积来过滤噪声
	for (int i = 0; i < contours.size(); i++) {//遍历检测到的轮廓
		int area = contourArea(contours[i]);

		//cout << area << endl;

		string objectType;
		if (area > 1000) {//轮廓面积>1000才绘制
			//计算轮廓周长或凹坑长度。该函数计算了曲线长度和封闭的周长。
			float peri = arcLength(contours[i], true);//计算封闭轮廓周长
			approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);//以指定的精度近似多边形曲线。第二个参数conPloy[i]存储近似的结果,是输出。

			boundRect[i] = boundingRect(conPoly[i]);//计算边界矩形

			myPoint.x = boundRect[i].x + boundRect[i].width / 2;
			myPoint.y = boundRect[i].y;

			rectangle/*绘制边界矩形*/(img, boundRect[i].tl()/*tl():topleft矩形左上角坐标*/, boundRect[i].br()/*br():bottom right矩形右下角坐标*/, Scalar(0, 255, 0), 5);
			drawContours(img, conPoly, i, Scalar(255, 0, 255), 2);

		}
	}
	return myPoint;
}


vector<vector<int>> findColor(Mat img) {
	Mat imgHSV;
	cvtColor(img, imgHSV, COLOR_BGR2HSV);//转换图像到HSV空间,在其中查找颜色更加容易

	for (int i = 0; i < myColors.size(); i++)
	{
		Scalar lower(myColors[i][0], myColors[i][1], myColors[i][2]);
		Scalar upper(myColors[i][3], myColors[i][4], myColors[i][5]);
		Mat mask;
		inRange(imgHSV, lower, upper, mask);//定义颜色下限和上限,因为由于照明和不同的阴影,颜色的值将不完全相同,会是一个值的范围
		//imshow(to_string(i), mask);
		Point myPoint=getContours(mask);
		if (myPoint.x != 0 && myPoint.y != 0) {//没检测到东西的时候就不加入新点
			newPoints.push_back({ myPoint.x,myPoint.y,i });//i为颜色索引
		}
	}
	return newPoints;

}

void drawOnCanvas(vector<vector<int>> newPoints, vector<Scalar> myColorValues) {
	for (int i = 0; i < newPoints.size(); i++) {
		circle(img, Point(newPoints[i][0], newPoints[i][1]),6,myColorValues[newPoints[i][2]],FILLED);
	}
}


// 捕捉图像边框
void main() {
	VideoCapture cap(0);//相机id=0


	while (true) {
		cap.read(img);

		newPoints=findColor(img);
		drawOnCanvas(newPoints,myColorValues);
		imshow("Image", img);
		waitKey(1);//增加延时 1ms,以免太慢
	}

}

文件扫描仪

#include<opencv2/imgcodecs.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>

using namespace std;
using namespace cv;

/// <summary>
/// Project 2 – Document Scanner
/// 图像预处理:转换为灰度、添加模糊、使用Canny边缘检测器找到边缘(知道纸张在哪里)、基于纸张的坐标提取四个角得到顶视图
/// </summary>

Mat imgOriginal, imgGray, imgBlur, imgCanny, imgDil, imgErode, imgThre, imgWarp, imgCrop;
vector<Point> initialPoints,docPoints;
float w = 420, h = 596;
Mat preProcessing(Mat img) {
	cvtColor(img, imgGray, COLOR_BGR2GRAY);//cvt是convert的缩写,将图像从一种颜色空间转换为另一种颜色空间。
	GaussianBlur(imgGray, imgBlur,Size(7,7),5,0);//使用高斯滤波器模糊图像。该函数将源图像与指定的高斯核进行卷积,Size(7,7)是核大小,数字越大越模糊
	Canny(imgBlur, imgCanny, 25, 75);//边缘检测,阈值1,2可调,目的:显示更多的边缘

	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));//创建一个核,增加Size(只能是奇数)会扩张/侵蚀更多
	dilate(imgCanny, imgDil, kernel);//扩张边缘(增加边缘厚度)
	//erode(imgDil, imgErode, kernel);//侵蚀边缘(减小边缘厚度)
	return imgDil;
}

vector<Point>/*返回纸张的4个角点*/ getContours(Mat imgDil) {//imgDil是传入的扩张边缘的图像用来查找轮廓,img是要在其上绘制轮廓的图像
	vector<vector<Point>> contours;//轮廓检测到的轮廓。每个轮廓线存储为一个点的向量

	vector<Vec4i> hierarchy;//包含关于映像拓扑的信息  typedef Vec<int, 4> Vec4i;具有4个整数值

	//在二值图像中查找轮廓。该函数利用该算法从二值图像中提取轮廓
	findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	//drawContours(img, contours, -1, Scalar(255, 0, 255), 2);//img:要绘制轮廓在什么图片上,contours:要绘制的轮廓,-1定义要绘制的轮廓号(-1表示所有轮廓),Saclar表示轮廓颜色,2表示厚度

	vector<vector<Point>> conPoly(contours.size());//conploy的数量应小于contours
	vector<Rect> boundRect(contours.size());

	vector<Point> biggest;
	int maxArea = 0;

	//过滤器:通过轮廓面积来过滤噪声
	for (int i = 0; i < contours.size(); i++) {//遍历检测到的轮廓
		int area = contourArea(contours[i]);

		//cout << area << endl;

		string objectType;
		if (area > 1000) {//轮廓面积>1000才绘制
			//计算轮廓周长或凹坑长度。该函数计算了曲线长度和封闭的周长。
			float peri = arcLength(contours[i], true);//计算封闭轮廓周长
			approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);//以指定的精度近似多边形曲线。第二个参数conPloy[i]存储近似的结果,是输出。

			if (area > maxArea&&conPoly[i].size()==4) {
				biggest = { conPoly[i][0],conPoly[i][1],conPoly[i][2],conPoly[i][3] };
				maxArea = area;
			}

			//rectangle/*绘制边界矩形*/(imgOriginal, boundRect[i].tl()/*tl():topleft矩形左上角坐标*/, boundRect[i].br()/*br():bottom right矩形右下角坐标*/, Scalar(0, 255, 0), 5);
			//drawContours(imgOriginal, conPoly, i, Scalar(255, 0, 255), 2);

		}
	}
	return biggest;
}

void drawPoints(vector<Point> points, Scalar color) {
	for (int i = 0; i < points.size(); i++) {
		circle(imgOriginal, points[i], 10, color, FILLED);
		putText(imgOriginal, to_string(i), points[i], FONT_HERSHEY_PLAIN, 4, color, 4);
	}
}

vector<Point> reorder(vector<Point> points) {//标记点的顺序会变,要确定一个顺序 0 1
											//								 2 3
	vector<Point> newPoints;
	vector<int>  sumPoints, subPoints;
	for (int i = 0; i < 4; i++) {
		sumPoints.push_back(points[i].x + points[i].y);
		subPoints.push_back(points[i].x - points[i].y);
	}

	newPoints.push_back(points[min_element/* find smallest element*/(sumPoints.begin(), sumPoints.end())-sumPoints.begin()]);
	newPoints.push_back(points[max_element/* find largest element*/(subPoints.begin(), subPoints.end()) - subPoints.begin()]);
	newPoints.push_back(points[min_element/* find smallest element*/(subPoints.begin(), subPoints.end()) - subPoints.begin()]);
	newPoints.push_back(points[max_element/* find largest element*/(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]);

	return newPoints;
}

Mat getWarp(Mat img,vector<Point> points,float w,float h) {
	Point2f src[4] = { points[0],points[1],points[2],points[3] };//Point2f表示浮点数
	Point2f dst[4] = { {0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h} };//Point2f表示浮点数

	Mat matrix = getPerspectiveTransform(src, dst);
	warpPerspective(img, imgWarp, matrix, Point(w,h));

	return imgWarp;
}

void main() {
	string path = "Resources/paper.jpg";
	imgOriginal=imread(path);

	//resize(imgOriginal/*source*/, imgOriginal/*destination*/, Size()/*不定义尺寸*/, 0.5/*定义比例*/, 0.5/*定义比例*/);

	//预处理
	imgThre = preProcessing(imgOriginal);
	//获得轮廓--获得最大矩形
	initialPoints=getContours(imgThre);
	//drawPoints(initialPoints, Scalar(0, 0, 255));
	docPoints = reorder(initialPoints);
	//drawPoints(docPoints, Scalar(0, 255, 0));
	//扭曲
	imgWarp = getWarp(imgOriginal, docPoints, w, h);
	//裁剪多余的边--通过创建一个矩形
	int cropValue = 5;
	Rect roi(cropValue/*每条边要减去的像素*/, cropValue, w - 2 * cropValue/*宽度*/, h - 2 * cropValue/*高度*/);
	imgCrop = imgWarp(roi);
	imshow("Image", imgOriginal);
	imshow("Image Dilation", imgThre);
	imshow("Image Warp", imgWarp);
	imshow("Image Crop", imgCrop);
	waitKey(0);
}

车牌检测器

#include<opencv2/imgcodecs.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<opencv2/objdetect.hpp>//对象检测头文件
#include<iostream>

using namespace std;
using namespace cv;
/// <summary>
/// Project 3 – License Plate(车牌) Detector 学习如何检测车牌和如何裁剪并保存这些区域
/// </summary>

void main() {
	VideoCapture cap(0);//相机id=0
	Mat img;

	//加载模型
	CascadeClassifier plateCascade;/*用于对象检测的级联分类器类*/
	plateCascade.load("Resources/haarcascade_russian_plate_number.xml");//从文件加载分类器(已经训练好的模型)
	if (plateCascade.empty()) { cout << "XML file not loaded" << endl; }//检测文件是否加载成功

	vector<Rect> plates;

	while (true) {
		cap.read(img);
		//可以更改比例因子和最小邻居来调整检测成功率
		plateCascade.detectMultiScale(img/*输入*/, plates/*输出*/, 1.1/*比例因子*/, 10/*最小邻居*/);//在输入图像中检测不同大小的对象。检测到的对象将以矩形列表的形式返回。
		for (int i = 0; i < plates.size(); i++) {
			Mat imgCrop = img(plates[i]);//plates是矩形列表,plates[i]是矩形
			//imshow(to_string(i), imgCrop);
		imwrite("Resources/Plates/" + to_string(i) + ".png", imgCrop);
			rectangle(img, plates[i].tl(), plates[i].br(), Scalar(255, 0, 255), 3);//绘制矩形
		}

		imshow("Image", img);
		waitKey(1);//增加延时,0表示无穷
	}
}

口罩识别

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void main() {
	// 创建摄像头连接 如果是只连接一个摄像头则默认0(包括笔记本电脑自带的摄像头),其余的外接可能需要更换数字
	VideoCapture cap(0);
	Mat frame; // 摄像头对象读取数据;

	CascadeClassifier face_cascade, nose_cascade, mask_detector;
	// 加载人脸检测训练模型
	face_cascade.load("Resources/haarcascade_frontalface_alt2.xml");
	// 加载人脸鼻子模型
	nose_cascade.load("Resources/haarcascade_mcs_nose.xml");
	// 加载口罩模型
	mask_detector.load("Resources/mask.xml");
	// 判断是否为空
	if (face_cascade.empty()){cout << "人脸模型XML文件未加载" << endl;}
	if (nose_cascade.empty()){cout << "人脸鼻子模型XML文件未加载" << endl;}
	if (mask_detector.empty()){cout << "口罩模型XML文件未加载" << endl;}

	/*
	detectMultiScale函数。它可以检测出图片中所有的人脸,并将人脸用vector保存各个人脸的坐标、大小(用矩形表示),函数由分类器对象调用:
	void detectMultiScale(
		const Mat& image,
		CV_OUT vector<Rect>& objects,
		double scaleFactor = 1.1,
		int minNeighbors = 3,
		int flags = 0,
		Size minSize = Size(),
		Size maxSize = Size()
	);
	函数介绍:
	参数1:image--待检测图片,一般为灰度图像加快检测速度;
	参数2:objects--被检测物体的矩形框向量组;
	参数3:scaleFactor--表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10%;
	参数4:minNeighbors--表示构成检测目标的相邻矩形的最小个数(默认为3个)。
			如果组成检测目标的小矩形的个数和小于 min_neighbors - 1 都会被排除。
			如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,
			这种设定值一般用在用户自定义对检测结果的组合程序上;
	参数5:flags--要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为
			CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域,
			因此这些区域通常不会是人脸所在区域;
	参数6、7:minSize和maxSize用来限制得到的目标区域的范围。
	*/
	// 创建矩形并用于标识人脸
	vector<Rect> faces, masks, noses;

	while (true) {
		// 获取图片每一帧的数据
		cap.read(frame);
		// 转换图像颜色
		/*cvtColor(img, frame, COLOR_BGR2GRAY);*/
		// 检测人脸,并转换为人脸参数
		face_cascade.detectMultiScale(frame, faces, 1.2, 10);
		mask_detector.detectMultiScale(frame, masks, 1.03, 5);
		nose_cascade.detectMultiScale(frame, noses, 1.2, 10);
		// 搜索人脸
		for (int i = 0; i < faces.size(); i++)
		{
			/*
			rectangle 绘制边界矩形
				tl():topleft矩形左上角坐标
				br():bottom right矩形右下角坐标
			*/
			rectangle(frame, faces[i].tl(), faces[i].br(), Scalar(0, 0, 255), 2);
			// 存放文本 TODO:Point() 坐标应该修改为动态
			putText(frame, "no_mask", Point(137, 262), FONT_HERSHEY_DUPLEX, 3, Scalar(0, 0, 255), 2);
		}

		// 搜索人脸鼻子
		for (int i = 0; i < noses.size(); i++)
		{
			rectangle(frame, noses[i].tl(), noses[i].br(), Scalar(10, 10, 255), 2);
			putText(frame, "no_mask", Point(137, 262), FONT_HERSHEY_DUPLEX, 3, Scalar(0, 0, 255), 2);
		}
		// 搜索口罩
		for (int i = 0; i < masks.size(); i++)
		{
			rectangle(frame, masks[i].tl(), masks[i].br(), Scalar(0, 255, 0), 2);
			putText(frame, "have_mask", Point(137, 262), FONT_HERSHEY_DUPLEX, 3, Scalar(0, 255, 0), 2);
		}

		// imshow 用于显示图像
		imshow("Image", frame);
		// waitKey() 数值越大播放越慢
		int c = waitKey(5);
	}
}

去除票据印章及通道分离

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
	//定义相关变量
	Mat srcImage, newImage;
	Mat srcImage_B, srcImage_G, srcImage_R;
	//存放Mat的数组vector
	vector<Mat> channels_BGR;
	//读取原始图像并检查图像是否读取成功
	string path = "Resources/voucher.jfif";
	srcImage = imread(path);
	if (srcImage.empty())
	{
		cout << "读取图像有误,请重新输入正确路径!\n";
		getchar();
		return -1;
	}
	imshow("src原图像", srcImage);  //在窗口显示原图像
   //对加载的原图像进行通道分离,即把一个3通道图像转换成为3个单通道图像
	split(srcImage, channels_BGR);
	//从数组中取出3种颜色,0通道为B分量,1通道为G分量,2通道为R分量。
	srcImage_B = channels_BGR.at(0);
	srcImage_G = channels_BGR.at(1);
	srcImage_R = channels_BGR.at(2);
	imshow("srcImage_B通道", srcImage_B); //分别显示R,G,B各个通道图像
	imshow("srcImage_G通道", srcImage_G);
	imshow("srcImage_R通道", srcImage_R);
	// 全局二值化
	Mat gray;
	cvtColor(srcImage, gray, COLOR_BGR2GRAY);
	int th = 170; //阈值要根据实际情况调整
	Mat binary;
	//CV_THRESH_BINARY代表阈值其中一种模式,下期会讲起阈值
	threshold(gray, binary, th, 255, THRESH_BINARY);
	Mat red_binary;
	threshold(srcImage_R, red_binary, th, 255, THRESH_BINARY);
	imshow("灰色图 + 阈值处理 ", binary);
	imshow("R通道+阈值处理", red_binary);
	// 将3个单通道重新合并成一个三通道图像
	merge(channels_BGR, newImage);
	imshow("将R,G,B通道合并后的图像", newImage);
	waitKey(0);

	return 0;
};

图像增强与图像去噪,图像分割之边缘检测

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;

int main()
{
    //【1】以灰度模式读取原始图像并显示
    string path = "Resources/test.png";
    Mat srcImage = imread(path, 0);
    if (!srcImage.data) {
        cout << "读取图片错误,请确定目录下是否有imread函数指定图片存在~!" << endl;
        getchar();
        return false;
    }
    imshow("原始图像", srcImage);
    //【2】将输入图像延扩到最佳的尺寸,边界用0补充
    //离散傅里叶变换的运行速度与图片的尺寸有很大关系。
    //当图像尺寸是2,3,5整数倍时,计算速度最快。getOptimalDFTSize就是获取最佳尺寸
    int m = getOptimalDFTSize(srcImage.rows);
    int n = getOptimalDFTSize(srcImage.cols);
    //将添加的像素初始化为0.
    Mat padded;
    //copyMakeBorder()函数的作用是扩充图像边界
    copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));
    //【3】为傅立叶变换的结果(复数)(实部和虚部)分配存储空间。
    //将planes数组组合合并成一个多通道的数组complexI
    //1.傅里叶结果是复数,这就是说对于每个原图像值,结果会有两个图像值
    //2.频域值范围远远超过空间值范围,因此要将频域存储再float格式中。并多加一个通道储存复数部分
    //3.什么是通道?请翻开第四篇文章谢谢,关注一下小嗷的公众号:aoxiaoji
    Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
    Mat complexI;
    merge(planes, 2, complexI);
    //【4】进行就地离散傅里叶变换
    dft(complexI, complexI);
    //【5】将复数转换为幅值,即=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
    //请翻开第四篇文章有讲通道分离split。谢谢,关注一下小嗷的公众号:aoxiaoji
    split(complexI, planes); // 将多通道数组complexI分离成几个单通道数组,planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
    //计算二维矢量的幅值
    magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
    Mat magnitudeImage = planes[0];
    //【6】进行对数尺度(logarithmic scale)缩放
    //将复数转换为幅值范围太大。高值显示为白点,而低值为黑点,高低值的变化无法有效分辨。为了在屏幕上凹显出高低变化的连续性,用对数尺度替代线性尺度
    magnitudeImage += Scalar::all(1);
    log(magnitudeImage, magnitudeImage);//求自然对数
    //【7】剪切和重分布幅度图象限
    //若有奇数行或奇数列,进行频谱裁剪
    //第二步,为了提高处理速度延扩了图像,现在是剔除添加的像素。
    //为了方便显示,也可以重新分布幅度图像象限位置,
    //(注:将第五步得到的幅度图从中间划开,得到4张1/4子图象,将每张子图堪称幅度图的一个象限,重新分布,即将4个角点重叠到图片中心)
    //这样的话原点(0,0)就位移到图像中心了
    magnitudeImage = magnitudeImage(Rect(0, 0, magnitudeImage.cols & -2, magnitudeImage.rows & -2));
    //重新排列傅立叶图像中的象限,使得原点位于图像中心
    int cx = magnitudeImage.cols / 2;
    int cy = magnitudeImage.rows / 2;
    Mat q0(magnitudeImage, Rect(0, 0, cx, cy));   // ROI区域的左上
    Mat q1(magnitudeImage, Rect(cx, 0, cx, cy));  // ROI区域的右上
    Mat q2(magnitudeImage, Rect(0, cy, cx, cy));  // ROI区域的左下
    Mat q3(magnitudeImage, Rect(cx, cy, cx, cy)); // ROI区域的右下
                                                  //交换象限(左上与右下进行交换)
    Mat tmp;
    q0.copyTo(tmp);
    q3.copyTo(q0);
    tmp.copyTo(q3);
    //交换象限(右上与左下进行交换)
    q1.copyTo(tmp);
    q2.copyTo(q1);
    tmp.copyTo(q2);
    //【8】归一化,用0到1之间的浮点值将矩阵变换为可视的图像格式(部分幅度值仍然超过可显示范围[0,1].幅度归一化到可显示范围)
    //此句代码的OpenCV2版为:
    //normalize(magnitudeImage, magnitudeImage, 0, 1, CV_MINMAX);
    //此句代码的OpenCV3版为:
    normalize(magnitudeImage, magnitudeImage, 0, 1, NORM_MINMAX);
    //【9】显示效果图
    imshow("频谱幅值", magnitudeImage);
    waitKey();
    return 0;
}

图像噪声

#include <cstdlib>
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
Mat addSaltNoise(const Mat srcImage, int n);
int main()
{
    string path = "Resources/test.png";
    Mat srcImage = imread(path);
    if (!srcImage.data)
    {
        cout << "读入图像有误!" << endl;
        system("pause");
        return -1;
    }
    imshow("原图像", srcImage);
    Mat dstImage = addSaltNoise(srcImage, 3000);
    imshow("添加椒盐噪声的图像", dstImage);
    //存储图像
    //imwrite("salt_pepper_Image.jpg", dstImage);
    waitKey();
    return 0;
}
Mat addSaltNoise(const Mat srcImage, int n)
{
    //克隆一张一摸一样的图
    Mat dstImage = srcImage.clone();
    for (int k = 0; k < n; k++)
    {
        //随机取值行
        //rand()是伪随机数生成函数,%是模运算,这个表达式的作用是随机生成012三个数字中的一个。
        int i = rand() % dstImage.rows;
        int j = rand() % dstImage.cols;
        //图像通道判定
        if (dstImage.channels() == 1)
        {
            // at类中的at方法对于获取图像矩阵某点的RGB值或者改变某点的值很方便
            //对于单通道的图像,则可以使用:
            //    如:XXX.at<uchar>(i, j) = 255(因为单通道(灰度图),由黑到白)
            //    (坐标(i, j)上,像素点的值为白色)
            //    对于多通道的图像(BGR),如

            //    img.at<Vec3b>(14, 25)[0] = 25;//B
            //img.at< Vec3b >(14, 25)[1] = 25;//G
            //img.at< Vec3b >(14, 25[2] = 25;//R
            dstImage.at<uchar>(i, j) = 255;       //盐噪声
        }
        else
        {
            dstImage.at<Vec3b>(i, j)[0] = 255;
            dstImage.at<Vec3b>(i, j)[1] = 255;
            dstImage.at<Vec3b>(i, j)[2] = 255;
        }
    }
    for (int k = 0; k < n; k++)
    {
        //随机取值行列
        int i = rand() % dstImage.rows;
        int j = rand() % dstImage.cols;
        //图像通道判定
        if (dstImage.channels() == 1)
        {
            dstImage.at<uchar>(i, j) = 0;     //椒噪声
        }
        else
        {
            dstImage.at<Vec3b>(i, j)[0] = 0;
            dstImage.at<Vec3b>(i, j)[1] = 0;
            dstImage.at<Vec3b>(i, j)[2] = 0;
        }
    }
    return dstImage;
}

视频运动目标捕捉

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace std;

Mat moveCheck(Mat& frontFrame, Mat& afterFrame)
{
    Mat frontGray, afterGray, diff;
    Mat resFrame = afterFrame.clone();
    //灰度处理,节省运算时间
    cvtColor(frontFrame, frontGray, COLOR_BGR2GRAY);
    cvtColor(afterFrame, afterGray, COLOR_BGR2GRAY);
    //帧差处理  找到两帧之间运动物体差异
    //缺点:会捕捉所有运动的物体,没办法专门捕捉某个目标
    absdiff(frontGray, afterGray, diff);

    //二值化: 使其变得更加黑白分明,便于计算,会产生噪点
    threshold(diff, diff, 25, 255, THRESH_BINARY);

    //腐蚀处理:去除大部分的白色噪点
    Mat element = cv::getStructuringElement(MORPH_RECT, Size(4, 4));//小于4*4方块的白色噪点都会被腐蚀
    erode(diff, diff, element);

    //膨胀处理:将白色区域变"胖",便于识别
    Mat element2 = cv::getStructuringElement(MORPH_RECT, Size(30, 30));
    dilate(diff, diff, element2);
    //动态物体标记
    vector<vector<Point>>contours;//用于保存关键点
    //findContours 第四个参数 CHAIN_APPROX_SIMPLE  用于压缩水平、垂直和对角线段,只留下它们的端点。例如,一个直立的矩形轮廓用 4 个点进行编码。
    findContours(diff, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
    //提取关键点
    vector<vector<Point>>contours_poly(contours.size());
    vector<Rect>boundRect(contours.size());
    int x, y, w, h;
    int num = contours.size();
    for (int i = 0; i < num; i++)
    {
        approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);
        boundRect[i] = boundingRect(Mat(contours_poly[i]));
        x = boundRect[i].x;
        y = boundRect[i].y;
        w = boundRect[i].width;
        h = boundRect[i].height;
        //绘制
        rectangle(resFrame, Point(x, y), Point(x + w, y + h), Scalar(0, 255, 0), 2);
    }
    return resFrame;
}

int main(int argc, char* argv[])
{
    Mat frame;

    Mat temp;
    Mat res;
    int num = 0;
    VideoCapture cap("Resources/test_video.mp4");
    while (cap.read(frame))
    {
        num++;
        if (num == 1)
        {//如果为第一帧则把当前帧传入(即不产生效果)

            res = moveCheck(frame, frame);
        }
        else
        {//从第二帧开始才有差帧
            res = moveCheck(temp, frame);

        }
        temp = frame.clone();//此处注意要调用.clone深拷贝,否则会出现两个画面一样的情况
        imshow("frame", frame);
        imshow("res", res);
        waitKey(25);
    }
    return 0;
}

马赛克原理之简单提高图像算法性能

//      程序描述:来自一本国外OpenCV2书籍的示例-遍历图像像素的14种方法
//------------------------------------------------------------------------------------------------

/*------------------------------------------------------------------------------------------*\
This file contains material supporting chapter 2 of the cookbook:
Computer Vision Programming using the OpenCV Library.
by Robert Laganiere, Packt Publishing, 2011.

This program is free software; permission is hereby granted to use, copy, modify,
and distribute this source code, or portions thereof, for any purpose, without fee,
subject to the restriction that the copyright notice may not be removed
or altered from any source or altered source distribution.
The software is released on an as-is basis and without any warranties of any kind.
In particular, the software is not guaranteed to be fault-tolerant or free from failure.
The author disclaims all warranties with regard to this software, any use,
and any consequent failure, is purely the responsibility of the user.

Copyright (C) 2010-2011 Robert Laganiere, www.laganiere.name
\*------------------------------------------------------------------------------------------*/

//---------------------------------【头文件、命名空间包含部分】-----------------------------
//      描述:包含程序所使用的头文件和命名空间
//-------------------------------------------------------------------------------------------------
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;

//---------------------------------【宏定义部分】---------------------------------------------
//      描述:包含程序所使用宏定义
//-------------------------------------------------------------------------------------------------
#define NTESTS 14
#define NITERATIONS 20

//----------------------------------------- 【方法一】-------------------------------------------
//      说明:利用.ptr 和 []
//-------------------------------------------------------------------------------------------------
void colorReduce0(Mat& image, int div = 64) {

    int nl = image.rows; //行数
    int nc = image.cols * image.channels(); //每行元素的总元素数量

    for (int j = 0; j < nl; j++)
    {

        uchar* data = image.ptr<uchar>(j);

        for (int i = 0; i < nc; i++)
        {

            //-------------开始处理每个像素-------------------

            data[i] = data[i] / div * div + div / 2;

            //-------------结束像素处理------------------------

        } //单行处理结束
    }
}

//-----------------------------------【方法二】-------------------------------------------------
//      说明:利用 .ptr 和 * ++
//-------------------------------------------------------------------------------------------------
void colorReduce1(Mat& image, int div = 64) {

    int nl = image.rows; //行数
    int nc = image.cols * image.channels(); //每行元素的总元素数量

    for (int j = 0; j < nl; j++)
    {

        uchar* data = image.ptr<uchar>(j);

        for (int i = 0; i < nc; i++)
        {

            //-------------开始处理每个像素-------------------

            *data++ = *data / div * div + div / 2;

            //-------------结束像素处理------------------------

        } //单行处理结束
    }
}

//-----------------------------------------【方法三】-------------------------------------------
//      说明:利用.ptr 和 * ++ 以及模操作
//-------------------------------------------------------------------------------------------------
void colorReduce2(Mat& image, int div = 64) {

    int nl = image.rows; //行数
    int nc = image.cols * image.channels(); //每行元素的总元素数量

    for (int j = 0; j < nl; j++)
    {

        uchar* data = image.ptr<uchar>(j);

        for (int i = 0; i < nc; i++)
        {

            //-------------开始处理每个像素-------------------

            int v = *data;
            *data++ = v - v % div + div / 2;

            //-------------结束像素处理------------------------

        } //单行处理结束
    }
}

//----------------------------------------【方法四】---------------------------------------------
//      说明:利用.ptr 和 * ++ 以及位操作
//----------------------------------------------------------------------------------------------------
void colorReduce3(Mat& image, int div = 64) {

    int nl = image.rows; //行数
    int nc = image.cols * image.channels(); //每行元素的总元素数量
    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    //掩码值
    uchar mask = 0xFF << n; // e.g. 对于 div=16, mask= 0xF0

    for (int j = 0; j < nl; j++) {

        uchar* data = image.ptr<uchar>(j);

        for (int i = 0; i < nc; i++) {

            //------------开始处理每个像素-------------------

            *data++ = *data & mask + div / 2;

            //-------------结束像素处理------------------------
        }  //单行处理结束
    }
}

//----------------------------------------【方法五】----------------------------------------------
//      说明:利用指针算术运算
//---------------------------------------------------------------------------------------------------
void colorReduce4(Mat& image, int div = 64) {

    int nl = image.rows; //行数
    int nc = image.cols * image.channels(); //每行元素的总元素数量
    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    int step = image.step; //有效宽度
                           //掩码值
    uchar mask = 0xFF << n; // e.g. 对于 div=16, mask= 0xF0

                            //获取指向图像缓冲区的指针
    uchar* data = image.data;

    for (int j = 0; j < nl; j++)
    {

        for (int i = 0; i < nc; i++)
        {

            //-------------开始处理每个像素-------------------

            *(data + i) = *data & mask + div / 2;

            //-------------结束像素处理------------------------

        } //单行处理结束

        data += step;  // next line
    }
}

//---------------------------------------【方法六】----------------------------------------------
//      说明:利用 .ptr 和 * ++以及位运算、image.cols * image.channels()
//-------------------------------------------------------------------------------------------------
void colorReduce5(Mat& image, int div = 64) {

    int nl = image.rows; //行数
    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    //掩码值
    uchar mask = 0xFF << n; // e.g. 例如div=16, mask= 0xF0

    for (int j = 0; j < nl; j++)
    {

        uchar* data = image.ptr<uchar>(j);

        for (int i = 0; i < image.cols * image.channels(); i++)
        {

            //-------------开始处理每个像素-------------------

            *data++ = *data & mask + div / 2;

            //-------------结束像素处理------------------------

        } //单行处理结束
    }
}

// -------------------------------------【方法七】----------------------------------------------
//      说明:利用.ptr 和 * ++ 以及位运算(continuous)
//-------------------------------------------------------------------------------------------------
void colorReduce6(Mat& image, int div = 64) {

    int nl = image.rows; //行数
    int nc = image.cols * image.channels(); //每行元素的总元素数量

    if (image.isContinuous())
    {
        //无填充像素
        nc = nc * nl;
        nl = 1;  // 为一维数列
    }

    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    //掩码值
    uchar mask = 0xFF << n; // e.g. 比如div=16, mask= 0xF0

    for (int j = 0; j < nl; j++) {

        uchar* data = image.ptr<uchar>(j);

        for (int i = 0; i < nc; i++) {

            //-------------开始处理每个像素-------------------

            *data++ = *data & mask + div / 2;

            //-------------结束像素处理------------------------

        } //单行处理结束
    }
}

//------------------------------------【方法八】------------------------------------------------
//      说明:利用 .ptr 和 * ++ 以及位运算 (continuous+channels)
//-------------------------------------------------------------------------------------------------
void colorReduce7(Mat& image, int div = 64) {

    int nl = image.rows; //行数
    int nc = image.cols; //列数

    if (image.isContinuous())
    {
        //无填充像素
        nc = nc * nl;
        nl = 1;  // 为一维数组
    }

    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    //掩码值
    uchar mask = 0xFF << n; // e.g. 比如div=16, mask= 0xF0

    for (int j = 0; j < nl; j++) {

        uchar* data = image.ptr<uchar>(j);

        for (int i = 0; i < nc; i++) {

            //-------------开始处理每个像素-------------------

            *data++ = *data & mask + div / 2;
            *data++ = *data & mask + div / 2;
            *data++ = *data & mask + div / 2;

            //-------------结束像素处理------------------------

        } //单行处理结束
    }
}

// -----------------------------------【方法九】 ------------------------------------------------
//      说明:利用Mat_ iterator
//-------------------------------------------------------------------------------------------------
void colorReduce8(Mat& image, int div = 64) {

    //获取迭代器
    Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
    Mat_<Vec3b>::iterator itend = image.end<Vec3b>();

    for (; it != itend; ++it) {

        //-------------开始处理每个像素-------------------

        (*it)[0] = (*it)[0] / div * div + div / 2;
        (*it)[1] = (*it)[1] / div * div + div / 2;
        (*it)[2] = (*it)[2] / div * div + div / 2;

        //-------------结束像素处理------------------------
    }//单行处理结束
}

//-------------------------------------【方法十】-----------------------------------------------
//      说明:利用Mat_ iterator以及位运算
//-------------------------------------------------------------------------------------------------
void colorReduce9(Mat& image, int div = 64) {

    // div必须是2的幂
    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    //掩码值
    uchar mask = 0xFF << n; // e.g. 比如 div=16, mask= 0xF0

                            // 获取迭代器
    Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
    Mat_<Vec3b>::iterator itend = image.end<Vec3b>();

    //扫描所有元素
    for (; it != itend; ++it)
    {

        //-------------开始处理每个像素-------------------

        (*it)[0] = (*it)[0] & mask + div / 2;
        (*it)[1] = (*it)[1] & mask + div / 2;
        (*it)[2] = (*it)[2] & mask + div / 2;

        //-------------结束像素处理------------------------
    }//单行处理结束
}

//------------------------------------【方法十一】---------------------------------------------
//      说明:利用Mat Iterator_
//-------------------------------------------------------------------------------------------------
void colorReduce10(Mat& image, int div = 64) {

    //获取迭代器
    Mat_<Vec3b> cimage = image;
    Mat_<Vec3b>::iterator it = cimage.begin();
    Mat_<Vec3b>::iterator itend = cimage.end();

    for (; it != itend; it++) {

        //-------------开始处理每个像素-------------------

        (*it)[0] = (*it)[0] / div * div + div / 2;
        (*it)[1] = (*it)[1] / div * div + div / 2;
        (*it)[2] = (*it)[2] / div * div + div / 2;

        //-------------结束像素处理------------------------
    }
}

//--------------------------------------【方法十二】--------------------------------------------
//      说明:利用动态地址计算配合at
//-------------------------------------------------------------------------------------------------
void colorReduce11(Mat& image, int div = 64) {

    int nl = image.rows; //行数
    int nc = image.cols; //列数

    for (int j = 0; j < nl; j++)
    {
        for (int i = 0; i < nc; i++)
        {

            //-------------开始处理每个像素-------------------

            image.at<Vec3b>(j, i)[0] = image.at<Vec3b>(j, i)[0] / div * div + div / 2;
            image.at<Vec3b>(j, i)[1] = image.at<Vec3b>(j, i)[1] / div * div + div / 2;
            image.at<Vec3b>(j, i)[2] = image.at<Vec3b>(j, i)[2] / div * div + div / 2;

            //-------------结束像素处理------------------------

        } //单行处理结束
    }
}

//----------------------------------【方法十三】-----------------------------------------------
//      说明:利用图像的输入与输出
//-------------------------------------------------------------------------------------------------
void colorReduce12(const Mat& image, //输入图像
    Mat& result,      // 输出图像
    int div = 64) {

    int nl = image.rows; //行数
    int nc = image.cols; //列数

                         //准备好初始化后的Mat给输出图像
    result.create(image.rows, image.cols, image.type());

    //创建无像素填充的图像
    nc = nc * nl;
    nl = 1;  //单维数组

    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    //掩码值
    uchar mask = 0xFF << n; // e.g.比如div=16, mask= 0xF0

    for (int j = 0; j < nl; j++) {

        uchar* data = result.ptr<uchar>(j);
        const uchar* idata = image.ptr<uchar>(j);

        for (int i = 0; i < nc; i++) {

            //-------------开始处理每个像素-------------------

            *data++ = (*idata++) & mask + div / 2;
            *data++ = (*idata++) & mask + div / 2;
            *data++ = (*idata++) & mask + div / 2;

            //-------------结束像素处理------------------------

        } //单行处理结束
    }
}

//--------------------------------------【方法十四】-------------------------------------------
//      说明:利用操作符重载
//-------------------------------------------------------------------------------------------------
void colorReduce13(Mat& image, int div = 64) {

    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    //掩码值
    uchar mask = 0xFF << n; // e.g. 比如div=16, mask= 0xF0

                            //进行色彩还原
    image = (image & Scalar(mask, mask, mask)) + Scalar(div / 2, div / 2, div / 2);
}

// 马赛克原理之简单提高图像算法性能(颜色空间缩减和计时函数)

//-----------------------------------【main( )函数】--------------------------------------------
//      描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main()
{
    int64 t[NTESTS], tinit;
    Mat image0;
    Mat image1;
    Mat image2;

    system("color 4F");


    string path =  "Resources/test.png";

    image0 = imread(path);
    if (!image0.data) {
        cout << " could not image " << endl;
        getchar();
        return -1;
    }

    //时间值设为0
    for (int i = 0; i < NTESTS; i++)
        t[i] = 0;

    // 多次重复测试
    int n = NITERATIONS;
    for (int k = 0; k < n; k++)
    {
        cout << k << " of " << n << endl;

        image1 = imread(path);
        //【方法一】利用.ptr 和 []
        tinit = getTickCount();
        colorReduce0(image1);
        t[0] += getTickCount() - tinit;

        //【方法二】利用 .ptr 和 * ++
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce1(image1);
        t[1] += getTickCount() - tinit;

        //【方法三】利用.ptr 和 * ++ 以及模操作
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce2(image1);
        t[2] += getTickCount() - tinit;

        //【方法四】 利用.ptr 和 * ++ 以及位操作
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce3(image1);
        t[3] += getTickCount() - tinit;

        //【方法五】 利用指针的算术运算
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce4(image1);
        t[4] += getTickCount() - tinit;

        //【方法六】利用 .ptr 和 * ++以及位运算、image.cols * image.channels()
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce5(image1);
        t[5] += getTickCount() - tinit;

        //【方法七】利用.ptr 和 * ++ 以及位运算(continuous)
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce6(image1);
        t[6] += getTickCount() - tinit;

        //【方法八】利用 .ptr 和 * ++ 以及位运算 (continuous+channels)
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce7(image1);
        t[7] += getTickCount() - tinit;

        //【方法九】 利用Mat_ iterator
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce8(image1);
        t[8] += getTickCount() - tinit;

        //【方法十】 利用Mat_ iterator以及位运算
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce9(image1);
        t[9] += getTickCount() - tinit;

        //【方法十一】利用Mat Iterator_
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce10(image1);
        t[10] += getTickCount() - tinit;

        //【方法十二】 利用动态地址计算配合at
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce11(image1);
        t[11] += getTickCount() - tinit;

        //【方法十三】 利用图像的输入与输出
        image1 = imread(path);
        tinit = getTickCount();
        Mat result;
        colorReduce12(image1, result);
        t[12] += getTickCount() - tinit;
        image2 = result;

        //【方法十四】 利用操作符重载
        image1 = imread(path);
        tinit = getTickCount();
        colorReduce13(image1);
        t[13] += getTickCount() - tinit;

        //------------------------------
    }
    //输出图像
    imshow("原始图像", image0);
    imshow("结果", image2);
    imshow("图像结果", image1);

    // 输出平均执行时间
    cout << endl << "-------------------------------------------" << endl << endl;
    cout << "\n【方法一】利用.ptr 和 []的方法所用时间为 " << 1000. * t[0] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法二】利用 .ptr 和 * ++ 的方法所用时间为" << 1000. * t[1] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法三】利用.ptr 和 * ++ 以及模操作的方法所用时间为" << 1000. * t[2] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法四】利用.ptr 和 * ++ 以及位操作的方法所用时间为" << 1000. * t[3] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法五】利用指针算术运算的方法所用时间为" << 1000. * t[4] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法六】利用 .ptr 和 * ++以及位运算、channels()的方法所用时间为" << 1000. * t[5] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法七】利用.ptr 和 * ++ 以及位运算(continuous)的方法所用时间为" << 1000. * t[6] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法八】利用 .ptr 和 * ++ 以及位运算 (continuous+channels)的方法所用时间为" << 1000. * t[7] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法九】利用Mat_ iterator 的方法所用时间为" << 1000. * t[8] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法十】利用Mat_ iterator以及位运算的方法所用时间为" << 1000. * t[9] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法十一】利用Mat Iterator_的方法所用时间为" << 1000. * t[10] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法十二】利用动态地址计算配合at 的方法所用时间为" << 1000. * t[11] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法十三】利用图像的输入与输出的方法所用时间为" << 1000. * t[12] / getTickFrequency() / n << "ms" << endl;
    cout << "\n【方法十四】利用操作符重载的方法所用时间为" << 1000. * t[13] / getTickFrequency() / n << "ms" << endl;

    waitKey(0);
    return 0;
}

图像处理之线性滤波

//---------------------------------【头文件、命名空间包含部分】-------------------------------
//      描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//-----------------------------------【全局变量声明部分】--------------------------------------
//  描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage1, g_dstImage2, g_dstImage3;//存储图片的Mat类型
int g_nBoxFilterValue = 3;  //方框滤波参数值
int g_nMeanBlurValue = 3;  //均值滤波参数值
int g_nGaussianBlurValue = 3;  //高斯滤波参数值
                               //-----------------------------------【全局函数声明部分】--------------------------------------
                               //   描述:全局函数声明
                               //-----------------------------------------------------------------------------------------------
                               //四个轨迹条的回调函数
static void on_BoxFilter(int, void*);      //均值滤波
static void on_MeanBlur(int, void*);       //均值滤波
static void on_GaussianBlur(int, void*);           //高斯滤波
//-----------------------------------【main( )函数】--------------------------------------------
//  描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main()
{
    //改变console字体颜色
    system("color 5F");
    // 载入原图
    string path = "Resources/test.png";
    g_srcImage = imread(path, 1);
    if (!g_srcImage.data) { printf("文件读取错误 \n"); return false; }
    //克隆原图到三个Mat类型中
    g_dstImage1 = g_srcImage.clone();
    g_dstImage2 = g_srcImage.clone();
    g_dstImage3 = g_srcImage.clone();
    //显示原图
    namedWindow("【<0>原图窗口】", 1);
    imshow("【<0>原图窗口】", g_srcImage);
    //=================【<1>方框滤波】==================
    //创建窗口
    namedWindow("【<1>方框滤波】", 1);
    //创建轨迹条
    createTrackbar("内核值:", "【<1>方框滤波】", &g_nBoxFilterValue, 40, on_BoxFilter);
    on_BoxFilter(g_nBoxFilterValue, 0);
    //================================================
    //=================【<2>均值滤波】==================
    //创建窗口
    namedWindow("【<2>均值滤波】", 1);
    //创建轨迹条
    createTrackbar("内核值:", "【<2>均值滤波】", &g_nMeanBlurValue, 40, on_MeanBlur);
    on_MeanBlur(g_nMeanBlurValue, 0);
    //================================================
    //=================【<3>高斯滤波】=====================
    //创建窗口
    namedWindow("【<3>高斯滤波】", 1);
    //创建轨迹条
    createTrackbar("内核值:", "【<3>高斯滤波】", &g_nGaussianBlurValue, 40, on_GaussianBlur);
    on_GaussianBlur(g_nGaussianBlurValue, 0);
    //================================================
    //输出一些帮助信息
    cout << endl << "\t运行成功,请调整滚动条观察图像效果~\n\n"
        << "\t按下“q”键时,程序退出。\n";
    //按下“q”键时,程序退出
    while (char(waitKey(1)) != 'q') {}
    return 0;
}
//-----------------------------【on_BoxFilter( )函数】------------------------------------
//  描述:方框滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_BoxFilter(int, void*)
{
    //方框滤波操作
    boxFilter(g_srcImage, g_dstImage1, -1, Size(g_nBoxFilterValue + 1, g_nBoxFilterValue + 1));
    //显示窗口
    imshow("【<1>方框滤波】", g_dstImage1);
}
//-----------------------------【on_MeanBlur( )函数】------------------------------------
//  描述:均值滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_MeanBlur(int, void*)
{
    //均值滤波操作
    blur(g_srcImage, g_dstImage2, Size(g_nMeanBlurValue + 1, g_nMeanBlurValue + 1), Point(-1, -1));
    //显示窗口
    imshow("【<2>均值滤波】", g_dstImage2);
}
//-----------------------------【ContrastAndBright( )函数】------------------------------------
//  描述:高斯滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_GaussianBlur(int, void*)
{
    //高斯滤波操作
    GaussianBlur(g_srcImage, g_dstImage3, Size(g_nGaussianBlurValue * 2 + 1, g_nGaussianBlurValue * 2 + 1), 0, 0);
    //显示窗口
    imshow("【<3>高斯滤波】", g_dstImage3);
}

opencv 学习 Demo 代码下载 Demo 代码下载open in new window