OpenCV3编程入门 学习笔记(总)

OpenCV3编程入门 学习笔记

2018.12.12—2018.12.29
此博客为在看过毛星云版《OpenCV3编程入门》后所总结的一本笔记,可供复习使用。

第一部分 快速上手OpenCV

1.5快速上手OpenCV图像处理

1.5.1图像显示

  #include<opencv2/opencv.hpp>
    using namespace cv;
    int main()
    {
    	Mat img = imread("E:\\imagelib\\02.jpg");
    	namedWindow("杨超越", WINDOW_NORMAL);
    //保证读取的图像显示完整
    	imshow("杨超越", img);
    	waitKey(0);
    }

1.5.2图像腐蚀

1.6 OpenCV视频操作基础

#include<opencv2/opencv.hpp>
using namespace cv;
int main()
{
	VideoCapture capture(0);//opencv调用摄像头读取视频
	while (1)
	{
		Mat frame;
		capture >> frame;
		imshow("读取视频", frame);
		if (waitKey(30) == 27)//按下Esc键退出读取视频操作
			break;
	}
	return 0;
}

Canny边缘检测

#include<opencv2/opencv.hpp>
using namespace cv;
int main()
{
	VideoCapture capture(0);//opencv调用摄像头读取视频
	Mat edges;
	while (1)
	{
		Mat frame;
		capture >> frame;
		cvtColor(frame, edges, CV_BGR2GRAY);
		blur(edges, edges, Size(7, 7));
		Canny(edges, edges, 0, 30, 3);
		namedWindow("被canny后的视频", WINDOW_NORMAL);
		imshow("被canny后的视频", edges);
		if (waitKey(30) == 27)//按下Esc键退出读取视频操作
			break;
	}
	return 0;
}

3.1 图像的载入、显示和输出到文件

图像读入
函数: Mat imread(”路径”,int flas=g);

Mat srcImage0 = imread("E:\\imagelib\\25.jpg");//默认flags 1,彩色图像
Mat srcImage1 = imread("E:\\imagelib\\25.jpg", 1);//彩色图像
Mat srcImage2 = imread("E:\\imagelib\\25.jpg", 2);//深度图像/灰度图像(无损)
Mat srcImage3 = imread("E:\\imagelib\\25.jpg", 4);//彩色(无损)

创建窗口:
函数:void namedWindow(“名称”,int flags=WINDOW_AUTOSIZE);
WINDOW_AUTOSIZE:窗口大小不可调。
WINDOW_NORMAL:窗口大小可调。
每显示一幅图片,对应新建一个窗口,可以实现多幅图片显示。

图像输出
指的是将图像从MAT这种数据结构变成文件格式:png、jpg等等
函数:
bool imwrite(“输出的图像文件名+后缀(.png)”,Mat类型的图像名称,对特定文件的参数设置)
Mat mat(480, 640, CV_8UC4);
createAlphaMat(mat);
vectorcompression_params;
compression_params.push_back(IMWRITE_PNG_COMPRESSION);
compression_params.push_back(9);
imwrite(“透明Alpha值图.png”, mat,compression_params);

3.2滑动条的创建和使用

滑动条的创建和使用
函数:int createTrackbar(轨迹条名字,依附的窗口名,int* value(滑块位置),int count,TrackbarCallback onChange=0(回调函数),void* userdata=0)
回调函数void on_Trackbar(int,void*)

3.3 鼠标操作

鼠标操作
函数:void setMouseCallback(窗口名称,MouseCallback onMouse(回调函数),void* userdata=0(用户传递到回调函数的参数))
回调函数void Foo(int event,int x,int y,int flags,void* param)

第二部分 初探core组件

4.1 基础图像容器Mat

Mat 是一个类,有两个数据部分组成,矩阵头(包含矩阵尺寸、存储方法、存储地址等信息)和一个指向所有存储像素值的矩阵。

Mat A, C
A = imread("01.jpg");
Mat B(A);//拷贝构造函数
C = A;//赋值运算符

Mat F = A.clone();
Mat G;
A.	copyTo(G);

拷贝构造函数和赋值运算符只复制信息头,与原对象共用一个矩阵,若改一个,则都受影响。
使用函数clone()或copyTo()可以复制矩阵。

使用Mat创建图像
Mat M = (2, 2, CV_8UC3, Scalar(0, 0, 255));
2,2为二维矩阵的大小
CV_[位数][带符号与否][类型前缀]C[通道数]
Scalar(a,b,c,d)四个参数,是一个类。表示颜色
在RGB中,前三个依次为B,G,R,最后一个可不写。
输出Mat矩阵
cout << “M =” << endl << " " << M << endl << endl;

4.2 常用数据结构和函数

注意数据类型,一般未提到的是int型
点的表示:Point类
Point point;
Point.x=10;
Point.y=8;
或者Point point(10,8);
颜色的表示:Scalar类
Scalar(0,0,255)
尺寸的表示:Size类
Size(5,5)
矩形的表示:Rect类
Rect类的成员变量有下x,y,width,height,分别为左上角点的坐标和矩形的宽和高。常用的成员函数有:Size()返回值为Size();area()返回矩形的面积。Constrins(Point)判断点是否在矩形内;inside(Rect)函数判断矩形是否在矩形内;tl返回左上角点的坐标;br()返回右下角点的坐标。还可对矩形进行交集、并集、平移和缩放操作。

颜色空间转换:cvtColor()函数
cvtColor(原图,目标图,标号,通道数(可不写))
绘图函数

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;

//此程序对于OpenCV3版需要额外包含头文件:
#include <opencv2/imgproc/imgproc.hpp>
//-----------------------------------【宏定义部分】-------------------------------------------- 
//		描述:定义一些辅助宏 
//------------------------------------------------------------------------------------------------ 
#define WINDOW_NAME1 "【绘制图1】"        //为窗口标题定义的宏 
#define WINDOW_NAME2 "【绘制图2】"        //为窗口标题定义的宏 
#define WINDOW_WIDTH 600//定义窗口大小的宏
//--------------------------------【全局函数声明部分】-------------------------------------
//		描述:全局函数声明
//-----------------------------------------------------------------------------------------------
void DrawEllipse( Mat img, double angle );//绘制椭圆
void DrawFilledCircle( Mat img, Point center );//绘制圆
void DrawPolygon( Mat img );//绘制多边形
void DrawLine( Mat img, Point start, Point end );//绘制线段
int main( void )
{

	// 创建空白的Mat图像
	Mat atomImage = Mat::zeros( WINDOW_WIDTH, WINDOW_WIDTH, CV_8UC3 );
	Mat rookImage = Mat::zeros( WINDOW_WIDTH, WINDOW_WIDTH, CV_8UC3 );

	ShowHelpText();
	// ---------------------<1>绘制化学中的原子示例图------------------------

	//【1.1】先绘制出椭圆
	DrawEllipse( atomImage, 90 );
	DrawEllipse( atomImage, 0 );
	DrawEllipse( atomImage, 45 );
	DrawEllipse( atomImage, -45 );

	//【1.2】再绘制圆心
	DrawFilledCircle( atomImage, Point( WINDOW_WIDTH/2, WINDOW_WIDTH/2) );

	// ----------------------------<2>绘制组合图-----------------------------
	//【2.1】先绘制出凹多边形
	DrawPolygon( rookImage );

	// 【2.2】绘制矩形
	rectangle( rookImage,
		Point( 0, 7*WINDOW_WIDTH/8 ),
		Point( WINDOW_WIDTH, WINDOW_WIDTH),
		Scalar( 0, 255, 255 ),
		-1,
		8 );

	// 【2.3】绘制一些线段
	DrawLine( rookImage, Point( 0, 15*WINDOW_WIDTH/16 ), Point( WINDOW_WIDTH, 15*WINDOW_WIDTH/16 ) );
	DrawLine( rookImage, Point( WINDOW_WIDTH/4, 7*WINDOW_WIDTH/8 ), Point( WINDOW_WIDTH/4, WINDOW_WIDTH ) );
	DrawLine( rookImage, Point( WINDOW_WIDTH/2, 7*WINDOW_WIDTH/8 ), Point( WINDOW_WIDTH/2, WINDOW_WIDTH ) );
	DrawLine( rookImage, Point( 3*WINDOW_WIDTH/4, 7*WINDOW_WIDTH/8 ), Point( 3*WINDOW_WIDTH/4, WINDOW_WIDTH ) );

	// ---------------------------<3>显示绘制出的图像------------------------
	imshow( WINDOW_NAME1, atomImage );
	moveWindow( WINDOW_NAME1, 0, 200 );
	imshow( WINDOW_NAME2, rookImage );
	moveWindow( WINDOW_NAME2, WINDOW_WIDTH, 200 );

	waitKey( 0 );
	return(0);
}



//-------------------------------【DrawEllipse( )函数】--------------------------------
//		描述:自定义的绘制函数,实现了绘制不同角度、相同尺寸的椭圆
//-----------------------------------------------------------------------------------------
void DrawEllipse( Mat img, double angle )
{
	int thickness = 2;
	int lineType = 8;

	ellipse( img,
		Point( WINDOW_WIDTH/2, WINDOW_WIDTH/2 ),
		Size( WINDOW_WIDTH/4, WINDOW_WIDTH/16 ),
		angle,
		0,
		360,
		Scalar( 255, 129, 0 ),
		thickness,
		lineType );
}


//-----------------------------------【DrawFilledCircle( )函数】---------------------------
//		描述:自定义的绘制函数,实现了实心圆的绘制
//-----------------------------------------------------------------------------------------
void DrawFilledCircle( Mat img, Point center )
{
	int thickness = -1;
	int lineType = 8;

	circle( img,
		center,
		WINDOW_WIDTH/32,
		Scalar( 0, 0, 255 ),
		thickness,
		lineType );
}


//-----------------------------------【DrawPolygon( )函数】--------------------------
//		描述:自定义的绘制函数,实现了凹多边形的绘制
//--------------------------------------------------------------------------------------
void DrawPolygon( Mat img )
{
	int lineType = 8;

	//创建一些点
	Point rookPoints[1][20];
	rookPoints[0][0]  = Point(    WINDOW_WIDTH/4,   7*WINDOW_WIDTH/8 );
	rookPoints[0][1]  = Point(  3*WINDOW_WIDTH/4,   7*WINDOW_WIDTH/8 );
	rookPoints[0][2]  = Point(  3*WINDOW_WIDTH/4,  13*WINDOW_WIDTH/16 );
	rookPoints[0][3]  = Point( 11*WINDOW_WIDTH/16, 13*WINDOW_WIDTH/16 );
	rookPoints[0][4]  = Point( 19*WINDOW_WIDTH/32,  3*WINDOW_WIDTH/8 );
	rookPoints[0][5]  = Point(  3*WINDOW_WIDTH/4,   3*WINDOW_WIDTH/8 );
	rookPoints[0][6]  = Point(  3*WINDOW_WIDTH/4,     WINDOW_WIDTH/8 );
	rookPoints[0][7]  = Point( 26*WINDOW_WIDTH/40,    WINDOW_WIDTH/8 );
	rookPoints[0][8]  = Point( 26*WINDOW_WIDTH/40,    WINDOW_WIDTH/4 );
	rookPoints[0][9]  = Point( 22*WINDOW_WIDTH/40,    WINDOW_WIDTH/4 );
	rookPoints[0][10] = Point( 22*WINDOW_WIDTH/40,    WINDOW_WIDTH/8 );
	rookPoints[0][11] = Point( 18*WINDOW_WIDTH/40,    WINDOW_WIDTH/8 );
	rookPoints[0][12] = Point( 18*WINDOW_WIDTH/40,    WINDOW_WIDTH/4 );
	rookPoints[0][13] = Point( 14*WINDOW_WIDTH/40,    WINDOW_WIDTH/4 );
	rookPoints[0][14] = Point( 14*WINDOW_WIDTH/40,    WINDOW_WIDTH/8 );
	rookPoints[0][15] = Point(    WINDOW_WIDTH/4,     WINDOW_WIDTH/8 );
	rookPoints[0][16] = Point(    WINDOW_WIDTH/4,   3*WINDOW_WIDTH/8 );
	rookPoints[0][17] = Point( 13*WINDOW_WIDTH/32,  3*WINDOW_WIDTH/8 );
	rookPoints[0][18] = Point(  5*WINDOW_WIDTH/16, 13*WINDOW_WIDTH/16 );
	rookPoints[0][19] = Point(    WINDOW_WIDTH/4,  13*WINDOW_WIDTH/16 );

	const Point* ppt[1] = { rookPoints[0] };
	int npt[] = { 20 };

	fillPoly( img,
		ppt,
		npt,
		1,
		Scalar( 255, 255, 255 ),
		lineType );
}
//-----------------------------------【DrawLine( )函数】--------------------------
//		描述:自定义的绘制函数,实现了线的绘制
//---------------------------------------------------------------------------------
void DrawLine( Mat img, Point start, Point end )
{
	int thickness = 2;
	int lineType = 8;
	line( img,
		start,
		end,
		Scalar( 0, 0, 0 ),
		thickness,
		lineType );
}

5.1 访问图像中的像素

颜色空间缩减
对每个像素实施Inew=(Iold/div)*div,可将颜色空间所见div的立方倍
函数为:colorReduce(srcImage,dstImage,div);
计时函数
double time0 = static_cast(getTickCount());

time0 = ((double)getTickCount() - time0) / getTickFrequency();//计算算法运行时间
访问图像中像素的三类方法

  1. 指针访问:C操作符[]
  2. 迭代器 iterator
  3. 动态地址计算

5.2 ROI区域图像叠加&图像混合

感兴趣区域:ROI 定义
Mat imageROI;
imageROI = image(Rect(500, 250, logo.cols, logo.rows));//使用矩形定义
imageROI = image(Range(250, 250 + logo.rows), Range(200, 200 + logo.cols));//使用Range(范围)
线性混合操作
函数:addWeighted(输入图像1,占比,输入图像2,占比,整体偏移量,输出图像,int dtype=-1);

5.3 分离颜色通道、多通道图像混合

通道分离:split(srcImage,channels)
通道合并:merge(channels,通道数(可以省略),srcImage)

5.4 图像对比度、亮度值调整

g(i,j)=a*f(I,j)+b;
a称为增益,控制对比度;
b称为偏置,控制亮度。
访问图片中每一个像素,然后对其做如上变换,进而调整对比度和亮度值。
5.5 离散傅里叶变换
函数:void dft(srcImage,dstImage,int flags=0(可选择做哪种傅里叶变换),int nonzeroRows=0(选择自己想要处理的非零行,比如输出矩阵C.rows))
其他一些可能用到的函数:
返回DFT最优尺寸大小:getOptimalDFTSize()函数
扩充图像边界:copyMakeBorder()函数
计算二维矢量的幅值:magnitude()函数 138
计算自然对数:log()函数
矩阵归一化:normalize()函数
程序实例

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


//--------------------------------------【main( )函数】-----------------------------------------
//          描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-------------------------------------------------------------------------------------------------
int main()
{

	//【1】以灰度模式读取原始图像并显示
	Mat srcImage = imread("E:\\imagelib\\41.jpg", 0);
	if (!srcImage.data) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }
	imshow("原始图像", srcImage);

	//【2】将输入图像延扩到最佳的尺寸,边界用0补充
	int m = getOptimalDFTSize(srcImage.rows);
	int n = getOptimalDFTSize(srcImage.cols);
	//将添加的像素初始化为0.
	Mat padded;
	copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));

	//【3】为傅立叶变换的结果(实部和虚部)分配存储空间。
	//将planes数组组合合并成一个多通道的数组complexI
	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(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】剪切和重分布幅度图象限
	//若有奇数行或奇数列,进行频谱裁剪      
	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之间的浮点值将矩阵变换为可视的图像格式
	//此句代码的OpenCV2版为:
	//normalize(magnitudeImage, magnitudeImage, 0, 1, CV_MINMAX); 
	//此句代码的OpenCV3版为:
	normalize(magnitudeImage, magnitudeImage, 0, 1, NORM_MINMAX);

	//【9】显示效果图
	imshow("频谱幅值", magnitudeImage);
	waitKey();

	return 0;
}

5.6 输入输出XML和YAML文件

示例程序:XML和YAML文件的写入
右击工程–>属性–>配置属性 --> C/C++ --> 命令行–>输入"/D _CRT_SECURE_NO_WARNINGS"–>“确定”,这样问题就可以解决了。注:不带引号

#include "opencv2/opencv.hpp"  
#include <time.h>  
using namespace cv;

int main()
{
	//改变console字体颜色
	system("color 5F");


	//初始化
	FileStorage fs("test.yaml", FileStorage::WRITE);

	//开始文件写入
	fs << "frameCount" << 5;
	time_t rawtime; time(&rawtime);
	fs << "calibrationDate" << asctime(localtime(&rawtime));
	Mat cameraMatrix = (Mat_<double>(3, 3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1);
	Mat distCoeffs = (Mat_<double>(5, 1) << 0.1, 0.01, -0.001, 0, 0);
	fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
	fs << "features" << "[";
	for (int i = 0; i < 3; i++)
	{
		int x = rand() % 640;
		int y = rand() % 480;
		uchar lbp = rand() % 256;

		fs << "{:" << "x" << x << "y" << y << "lbp" << "[:";
		for (int j = 0; j < 8; j++)
			fs << ((lbp >> j) & 1);
		fs << "]" << "}";
	}
	fs << "]";
	fs.release();

	printf("\n文件读写完毕,请在工程目录下查看生成的文件~");
	getchar();

	return 0;
}
示例程序:XML和YAML文件的读取
#include "opencv2/opencv.hpp"  
#include <time.h>  
using namespace cv;
using namespace std;

int main()
{
	//改变console字体颜色
	system("color 6F");

	//初始化
	FileStorage fs2("test.yaml", FileStorage::READ);

	// 第一种方法,对FileNode操作
	int frameCount = (int)fs2["frameCount"];

	std::string date;
	// 第二种方法,使用FileNode运算符> > 
	fs2["calibrationDate"] >> date;

	Mat cameraMatrix2, distCoeffs2;
	fs2["cameraMatrix"] >> cameraMatrix2;
	fs2["distCoeffs"] >> distCoeffs2;

	cout << "frameCount: " << frameCount << endl
		<< "calibration date: " << date << endl
		<< "camera matrix: " << cameraMatrix2 << endl
		<< "distortion coeffs: " << distCoeffs2 << endl;

	FileNode features = fs2["features"];
	FileNodeIterator it = features.begin(), it_end = features.end();
	int idx = 0;
	std::vector<uchar> lbpval;

	//使用FileNodeIterator遍历序列
	for (; it != it_end; ++it, idx++)
	{
		cout << "feature #" << idx << ": ";
		cout << "x=" << (int)(*it)["x"] << ", y=" << (int)(*it)["y"] << ", lbp: (";
		// 我们也可以使用使用filenode > > std::vector操作符很容易的读数值阵列
		(*it)["lbp"] >> lbpval;
		for (int i = 0; i < (int)lbpval.size(); i++)
			cout << " " << (int)lbpval[i];
		cout << ")" << endl;
	}
	fs2.release();

	//程序结束,输出一些帮助文字
	printf("\n文件读取完毕,请输入任意键结束程序~");
	getchar();

	return 0;
} 

第三部分 掌握imgproc组件

6.1 线性滤波:方框滤波、均值滤波、高斯滤波

方框滤波:邻域像素平均值
函数:boxFilter()
均值滤波:邻域像素平均值+归一化
函数:blur()
高斯滤波:邻域像素加权平均值,处理高斯噪声
函数:GaussianBlur()
6.2 非线性滤波:中值滤波、双边滤波
中值滤波:领域像素中值,去除脉冲噪声、散粒噪声、椒盐噪声
函数:medianBlur()
双边滤波:比高斯滤波多了一个高斯方差
函数:bilateralFilter()
函数实例:

#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,g_dstImage4,g_dstImage5;
int g_nBoxFilterValue=6;  //方框滤波内核值
int g_nMeanBlurValue=10;  //均值滤波内核值
int g_nGaussianBlurValue=6;  //高斯滤波内核值
int g_nMedianBlurValue=10;  //中值滤波参数值
int g_nBilateralFilterValue=10;  //双边滤波参数值


//-----------------------------------【全局函数声明部分】--------------------------------------
//		描述:全局函数声明
//-----------------------------------------------------------------------------------------------
//轨迹条回调函数
static void on_BoxFilter(int, void *);		//方框滤波
static void on_MeanBlur(int, void *);		//均值块滤波器
static void on_GaussianBlur(int, void *);			//高斯滤波器
static void on_MedianBlur(int, void *);			//中值滤波器
static void on_BilateralFilter(int, void *);			//双边滤波器
void ShowHelpText();
//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main(   )
{
	system("color 4F");  

	// 载入原图
	g_srcImage = imread( "1.jpg", 1 );
	if( !g_srcImage.data ) { printf("读取srcImage错误~! \n"); return false; }

	//克隆原图到四个Mat类型中
	g_dstImage1 = g_srcImage.clone( );
	g_dstImage2 = g_srcImage.clone( );
	g_dstImage3 = g_srcImage.clone( );
	g_dstImage4 = g_srcImage.clone( );
	g_dstImage5 = g_srcImage.clone( );

	//显示原图
	namedWindow("【<0>原图窗口】", WINDOW_NORMAL);
	imshow("【<0>原图窗口】",g_srcImage);


	//=================【<1>方框滤波】=========================
	//创建窗口
	namedWindow("【<1>方框滤波】", WINDOW_NORMAL);
	//创建轨迹条
	createTrackbar("内核值:", "【<1>方框滤波】",&g_nBoxFilterValue, 50,on_BoxFilter );
	on_BoxFilter(g_nBoxFilterValue,0);
	imshow("【<1>方框滤波】", g_dstImage1);
	//=====================================================


	//=================【<2>均值滤波】==========================
	//创建窗口
	namedWindow("【<2>均值滤波】", WINDOW_NORMAL);
	//创建轨迹条
	createTrackbar("内核值:", "【<2>均值滤波】",&g_nMeanBlurValue, 50,on_MeanBlur );
	on_MeanBlur(g_nMeanBlurValue,0);
	//======================================================


	//=================【<3>高斯滤波】===========================
	//创建窗口
	namedWindow("【<3>高斯滤波】", WINDOW_NORMAL);
	//创建轨迹条
	createTrackbar("内核值:", "【<3>高斯滤波】",&g_nGaussianBlurValue, 50,on_GaussianBlur );
	on_GaussianBlur(g_nGaussianBlurValue,0);
	//=======================================================


	//=================【<4>中值滤波】===========================
	//创建窗口
	namedWindow("【<4>中值滤波】", WINDOW_NORMAL);
	//创建轨迹条
	createTrackbar("参数值:", "【<4>中值滤波】",&g_nMedianBlurValue, 50,on_MedianBlur );
	on_MedianBlur(g_nMedianBlurValue,0);
	//=======================================================


	//=================【<5>双边滤波】===========================
	//创建窗口
	namedWindow("【<5>双边滤波】", WINDOW_NORMAL);
	//创建轨迹条
	createTrackbar("参数值:", "【<5>双边滤波】",&g_nBilateralFilterValue, 50,on_BilateralFilter);
	on_BilateralFilter(g_nBilateralFilterValue,0);
	//=======================================================
	//输出一些帮助信息
	cout<<endl<<"\t运行成功,请调整滚动条观察图像效果~\n\n"
		<<"\t按下“q”键时,程序退出。\n";
	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);

}

//-----------------------------【on_GaussianBlur( )函数】------------------------------------
//		描述:高斯滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
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);
}
//-----------------------------【on_MedianBlur( )函数】------------------------------------
//		描述:中值滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_MedianBlur(int, void *)
{
	medianBlur ( g_srcImage, g_dstImage4, g_nMedianBlurValue*2+1 );
	imshow("【<4>中值滤波】", g_dstImage4);
}


//-----------------------------【on_BilateralFilter( )函数】------------------------------------
//		描述:双边滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_BilateralFilter(int, void *)
{
	bilateralFilter ( g_srcImage, g_dstImage5, g_nBilateralFilterValue, g_nBilateralFilterValue*2, g_nBilateralFilterValue/2 );
	imshow("【<5>双边滤波】", g_dstImage5);
}

6.3 形态学滤波(1):腐蚀与膨胀

对于一幅二值化图片,认为前景是高亮部分(灰度值较高),背景是灰暗部分(灰度值较低)。
膨胀:寻找邻域内灰度最大值代替该点灰度值,相当于高亮部分增多,所以称为膨胀。
函数:dilate()
腐蚀:寻找邻域内灰度最小值代替该点灰度值,相当于灰暗部分增多,所以称为腐蚀。
函数:erode()

6.4 形态学滤波(2):开运算、闭运算、形态学梯度、顶帽、黑帽

开运算:先腐蚀,后膨胀
闭运算:先膨胀、后腐蚀
形态学梯度:膨胀图与腐蚀图之差
顶帽:原图与开运算之差
黑帽:闭运算与结果之差
上述形态学滤波函数均可由morphologyEx()函数实现。
实例:

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


//-----------------------------------【全局变量声明部分】-----------------------------------
//		描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage;//原始图和效果图
int g_nElementShape = MORPH_RECT;//元素结构的形状

//变量接收的TrackBar位置参数
int g_nMaxIterationNum = 10;
int g_nOpenCloseNum = 0;
int g_nErodeDilateNum = 0;
int g_nTopBlackHatNum = 0;
//-----------------------------------【全局函数声明部分】--------------------------------------
//		描述:全局函数声明
//-----------------------------------------------------------------------------------------------
static void on_OpenClose(int, void*);//回调函数
static void on_ErodeDilate(int, void*);//回调函数
static void on_TopBlackHat(int, void*);//回调函数
//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//改变console字体颜色
	system("color 2F");  


	//载入原图
	g_srcImage = imread("1.jpg");
	if( !g_srcImage.data ) { printf("Oh,no,读取srcImage错误~! \n"); return false; }

	//显示原始图
	namedWindow("【原始图】");
	imshow("【原始图】", g_srcImage);

	//创建三个窗口
	namedWindow("【开运算/闭运算】",1);
	namedWindow("【腐蚀/膨胀】",1);
	namedWindow("【顶帽/黑帽】",1);

	//参数赋值
	g_nOpenCloseNum=9;
	g_nErodeDilateNum=9;
	g_nTopBlackHatNum=2;

	//分别为三个窗口创建滚动条
	createTrackbar("迭代值", "【开运算/闭运算】",&g_nOpenCloseNum,g_nMaxIterationNum*2+1,on_OpenClose);
	createTrackbar("迭代值", "【腐蚀/膨胀】",&g_nErodeDilateNum,g_nMaxIterationNum*2+1,on_ErodeDilate);
	createTrackbar("迭代值", "【顶帽/黑帽】",&g_nTopBlackHatNum,g_nMaxIterationNum*2+1,on_TopBlackHat);

	//轮询获取按键信息
	while(1)
	{
		int c;

		//执行回调函数
		on_OpenClose(g_nOpenCloseNum, 0);
		on_ErodeDilate(g_nErodeDilateNum, 0);
		on_TopBlackHat(g_nTopBlackHatNum,0);

		//获取按键
		c = waitKey(0);

		//按下键盘按键Q或者ESC,程序退出
		if( (char)c == 'q'||(char)c == 27 )
			break;
		//按下键盘按键1,使用椭圆(Elliptic)结构元素结构元素MORPH_ELLIPSE
		if( (char)c == 49 )//键盘按键1的ASII码为49
			g_nElementShape = MORPH_ELLIPSE;
		//按下键盘按键2,使用矩形(Rectangle)结构元素MORPH_RECT
		else if( (char)c == 50 )//键盘按键2的ASII码为50
			g_nElementShape = MORPH_RECT;
		//按下键盘按键3,使用十字形(Cross-shaped)结构元素MORPH_CROSS
		else if( (char)c == 51 )//键盘按键3的ASII码为51
			g_nElementShape = MORPH_CROSS;
		//按下键盘按键space,在矩形、椭圆、十字形结构元素中循环
		else if( (char)c == ' ' )
			g_nElementShape = (g_nElementShape + 1) % 3;
	}

	return 0;
}
//-----------------------------------【on_OpenClose( )函数】----------------------------------
//		描述:【开运算/闭运算】窗口的回调函数
//-----------------------------------------------------------------------------------------------
static void on_OpenClose(int, void*)
{
	//偏移量的定义
	int offset = g_nOpenCloseNum - g_nMaxIterationNum;//偏移量
	int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
	//自定义核
	Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );
	//进行操作
	if( offset < 0 )
		//此句代码的OpenCV2版为:
		//morphologyEx(g_srcImage, g_dstImage, CV_MOP_OPEN, element);
		//此句代码的OpenCV3版为:
		morphologyEx(g_srcImage, g_dstImage, MORPH_OPEN, element);
	else
		//此句代码的OpenCV2版为:
		//morphologyEx(g_srcImage, g_dstImage, CV_MOP_CLOSE, element);
		//此句代码的OpenCV3版为:
		morphologyEx(g_srcImage, g_dstImage, MORPH_CLOSE, element);
		
	//显示图像
	imshow("【开运算/闭运算】",g_dstImage);
}
//-----------------------------------【on_ErodeDilate( )函数】----------------------------------
//		描述:【腐蚀/膨胀】窗口的回调函数
//-----------------------------------------------------------------------------------------------
static void on_ErodeDilate(int, void*)
{
	//偏移量的定义
	int offset = g_nErodeDilateNum - g_nMaxIterationNum;	//偏移量
	int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
	//自定义核
	Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );
	//进行操作
	if( offset < 0 )
		erode(g_srcImage, g_dstImage, element);
	else
		dilate(g_srcImage, g_dstImage, element);
	//显示图像
	imshow("【腐蚀/膨胀】",g_dstImage);
}
//-----------------------------------【on_TopBlackHat( )函数】--------------------------------
//		描述:【顶帽运算/黑帽运算】窗口的回调函数
//----------------------------------------------------------------------------------------------
static void on_TopBlackHat(int, void*)
{
	//偏移量的定义
	int offset = g_nTopBlackHatNum - g_nMaxIterationNum;//偏移量
	int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
	//自定义核
	Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );
	//进行操作
	if( offset < 0 )
		morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT , element);
	else
		morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);
	//显示图像
	imshow("【顶帽/黑帽】",g_dstImage);
}
	//输出一些帮助信息
	printf("\n\t请调整滚动条观察图像效果\n\n");
	printf( "\n\t按键操作说明: \n\n"
		"\t\t键盘按键【ESC】或者【Q】- 退出程序\n"
		"\t\t键盘按键【1】- 使用椭圆(Elliptic)结构元素\n"
		"\t\t键盘按键【2】- 使用矩形(Rectangle )结构元素\n"
		"\t\t键盘按键【3】- 使用十字型(Cross-shaped)结构元素\n"
		"\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n"	);
}

6.5 漫水填充

漫水填充是一种用特定的颜色填充连通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的办法。
函数:floodFill();
使用空范围的漫水填充
使用渐变、固定范围的漫水填充
使用渐变、浮动范围的漫水填充
操作标志符的低八位使用4位的连接模式
操作标志符的低八位使用8位的连接模式

6.6 图像金字塔与图片尺寸缩放

图像金字塔,通常指高斯采样(高斯金字塔)
从下到上,层数逐渐增加,越向上图像越小
拉格朗日金字塔:原图像利用高斯采样经先缩小再增大
向上采样:扩大尺寸
函数:pyrUp()
向下采样:减小尺寸
函数:pyrDown()

尺寸调整:resize();
可选择增大或缩小倍数,或直接给出输出图像大小。
也可选择插值方式,通常选择效率较高的线性插值。

6.7 阈值化

固定阈值操作:Threshold()函数
5种操作。0-4
0:二进制阈值
1:反二进制阈值
2:截断阈值
3:阈值化为0
4:反阈值化为0
自适应阈值操作:adaptiveThreshold()函数

7.1 基于OpenCV的边缘检测

边缘检测几种方法:
canny算子:带两个阈值,内部使用Sobel算子。
sobel算子: 分别求x,y方向的梯度,然后融合,实现边缘检测。
Laplacian 算子:使用sobel算子二阶微分。
scharr滤波器:内核为3不能变。
程序实例:

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;


//-----------------------------------【全局变量声明部分】--------------------------------------
//		描述:全局变量声明
//-----------------------------------------------------------------------------------------------
//原图,原图的灰度版,目标图
Mat g_srcImage, g_srcGrayImage,g_dstImage;

//Canny边缘检测相关变量
Mat g_cannyDetectedEdges;
int g_cannyLowThreshold=1;//TrackBar位置参数  

//Sobel边缘检测相关变量
Mat g_sobelGradient_X, g_sobelGradient_Y;
Mat g_sobelAbsGradient_X, g_sobelAbsGradient_Y;
int g_sobelKernelSize=1;//TrackBar位置参数  

//Scharr滤波器相关变量
Mat g_scharrGradient_X, g_scharrGradient_Y;
Mat g_scharrAbsGradient_X, g_scharrAbsGradient_Y;


//-----------------------------------【全局函数声明部分】--------------------------------------
//		描述:全局函数声明
//-----------------------------------------------------------------------------------------------
static void ShowHelpText( );
static void on_Canny(int, void*);//Canny边缘检测窗口滚动条的回调函数
static void on_Sobel(int, void*);//Sobel边缘检测窗口滚动条的回调函数
void Scharr( );//封装了Scharr边缘检测相关代码的函数


//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
	//改变console字体颜色
	system("color 2F");  


	//载入原图
	g_srcImage = imread("1.jpg");
	if( !g_srcImage.data ) { printf("Oh,no,读取srcImage错误~! \n"); return false; }

	//显示原始图
	namedWindow("【原始图】");
	imshow("【原始图】", g_srcImage);

	// 创建与src同类型和大小的矩阵(dst)
	g_dstImage.create( g_srcImage.size(), g_srcImage.type() );

	// 将原图像转换为灰度图像
	cvtColor( g_srcImage, g_srcGrayImage, COLOR_BGR2GRAY );

	// 创建显示窗口
	namedWindow( "【效果图】Canny边缘检测", WINDOW_AUTOSIZE );
	namedWindow( "【效果图】Sobel边缘检测", WINDOW_AUTOSIZE );

	// 创建trackbar
	createTrackbar( "参数值:", "【效果图】Canny边缘检测", &g_cannyLowThreshold, 120, on_Canny );
	createTrackbar( "参数值:", "【效果图】Sobel边缘检测", &g_sobelKernelSize, 3, on_Sobel );

	// 调用回调函数
	on_Canny(0, 0);
	on_Sobel(0, 0);

	//调用封装了Scharr边缘检测代码的函数
	Scharr( );

	//轮询获取按键信息,若按下Q,程序退出
	while((char(waitKey(1)) != 'q')) {}

	return 0;
}


//-----------------------------------【on_Canny( )函数】----------------------------------
//		描述:Canny边缘检测窗口滚动条的回调函数
//-----------------------------------------------------------------------------------------------
void on_Canny(int, void*)
{
	// 先使用 3x3内核来降噪
	blur( g_srcGrayImage, g_cannyDetectedEdges, Size(3,3) );

	// 运行我们的Canny算子
	Canny( g_cannyDetectedEdges, g_cannyDetectedEdges, g_cannyLowThreshold, g_cannyLowThreshold*3, 3 );

	//先将g_dstImage内的所有元素设置为0 
	g_dstImage = Scalar::all(0);

	//使用Canny算子输出的边缘图g_cannyDetectedEdges作为掩码,来将原图g_srcImage拷到目标图g_dstImage中
	g_srcImage.copyTo( g_dstImage, g_cannyDetectedEdges);

	//显示效果图
	imshow( "【效果图】Canny边缘检测", g_dstImage );
}



//-----------------------------------【on_Sobel( )函数】----------------------------------
//		描述:Sobel边缘检测窗口滚动条的回调函数
//-----------------------------------------------------------------------------------------
void on_Sobel(int, void*)
{
	// 求 X方向梯度
	Sobel( g_srcImage, g_sobelGradient_X, CV_16S, 1, 0, (2*g_sobelKernelSize+1), 1, 1, BORDER_DEFAULT );
	convertScaleAbs( g_sobelGradient_X, g_sobelAbsGradient_X );//计算绝对值,并将结果转换成8位

	// 求Y方向梯度
	Sobel( g_srcImage, g_sobelGradient_Y, CV_16S, 0, 1, (2*g_sobelKernelSize+1), 1, 1, BORDER_DEFAULT );
	convertScaleAbs( g_sobelGradient_Y, g_sobelAbsGradient_Y );//计算绝对值,并将结果转换成8位

	// 合并梯度
	addWeighted( g_sobelAbsGradient_X, 0.5, g_sobelAbsGradient_Y, 0.5, 0, g_dstImage );

	//显示效果图
	imshow("【效果图】Sobel边缘检测", g_dstImage); 

}
//-----------------------------------【Scharr( )函数】---------------------------------
//		描述:封装了Scharr边缘检测相关代码的函数
//-----------------------------------------------------------------------------------------
void Scharr( )
{
	// 求 X方向梯度
	Scharr( g_srcImage, g_scharrGradient_X, CV_16S, 1, 0, 1, 0, BORDER_DEFAULT );
	convertScaleAbs( g_scharrGradient_X, g_scharrAbsGradient_X );//计算绝对值,并将结果转换成8位

	// 求Y方向梯度
	Scharr( g_srcImage, g_scharrGradient_Y, CV_16S, 0, 1, 1, 0, BORDER_DEFAULT );
	convertScaleAbs( g_scharrGradient_Y, g_scharrAbsGradient_Y );//计算绝对值,并将结果转换成8位

	// 合并梯度
	addWeighted( g_scharrAbsGradient_X, 0.5, g_scharrAbsGradient_Y, 0.5, 0, g_dstImage );

	//显示效果图
	imshow("【效果图】Scharr滤波器", g_dstImage); 
}
7.2 霍夫变换
霍夫变换主要用来快速准确地检测出直线或者圆。
须先进行边缘检测:
标准霍夫变换:HoughLines()函数 
累计概率霍夫变换:HoughLinesP()函数 
霍夫圆变换:HoughCircles()函数 
程序实例:
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;


//-----------------------------------【全局变量声明部分】--------------------------------------
//		描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage,g_midImage;//原始图、中间图和效果图
vector<Vec4i> g_lines;//定义一个矢量结构g_lines用于存放得到的线段矢量集合
//变量接收的TrackBar位置参数
int g_nthreshold=100;

//-----------------------------------【全局函数声明部分】--------------------------------------
//		描述:全局函数声明
//-----------------------------------------------------------------------------------------------

static void on_HoughLines(int, void*);//回调函数
static void ShowHelpText();
//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//改变console字体颜色
	system("color 4F");  

	//载入原始图和Mat变量定义   
	Mat g_srcImage = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图

	//显示原始图  
	imshow("【原始图】", g_srcImage);  

	//创建滚动条
	namedWindow("【效果图】",1);
	createTrackbar("值", "【效果图】",&g_nthreshold,200,on_HoughLines);

	//进行边缘检测和转化为灰度图
	Canny(g_srcImage, g_midImage, 50, 200, 3);//进行一次canny边缘检测
	cvtColor(g_midImage,g_dstImage, COLOR_GRAY2BGR);//转化边缘检测后的图为灰度图

	//调用一次回调函数,调用一次HoughLinesP函数
	on_HoughLines(g_nthreshold,0);
	HoughLinesP(g_midImage, g_lines, 1, CV_PI/180, 80, 50, 10 );

	//显示效果图  
	imshow("【效果图】", g_dstImage);  


	waitKey(0);  

	return 0;  

}
//-----------------------------------【on_HoughLines( )函数】--------------------------------
//		描述:【顶帽运算/黑帽运算】窗口的回调函数
//----------------------------------------------------------------------------------------------
static void on_HoughLines(int, void*)
{
	//定义局部变量储存全局变量
	Mat dstImage=g_dstImage.clone();
	Mat midImage=g_midImage.clone();

	//调用HoughLinesP函数
	vector<Vec4i> mylines;
	HoughLinesP(midImage, mylines, 1, CV_PI/180, g_nthreshold+1, 50, 10 );

	//循环遍历绘制每一条线段
	for( size_t i = 0; i < mylines.size(); i++ )
	{
		Vec4i l = mylines[i];
		line( dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(23,180,55), 1, LINE_AA);
	}
	//显示图像
	imshow("【效果图】",dstImage);
}

7.3 重映射

实现重映射:remap()函数
重映射:就是把一幅图像中某位置的像素放置到另一个图片指定位置的过程。可进行X方向镜面,Y方向镜面,等多重映射。通过改变参数map.x以及map.y。
程序实例:

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


//-----------------------------------【宏定义部分】-------------------------------------------- 
//  描述:定义一些辅助宏 
//------------------------------------------------------------------------------------------------ 
#define WINDOW_NAME "【程序窗口】"        //为窗口标题定义的宏 


//-----------------------------------【全局变量声明部分】--------------------------------------
//          描述:全局变量的声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage;
Mat g_map_x, g_map_y;


//-----------------------------------【全局函数声明部分】--------------------------------------
//          描述:全局函数的声明
//-----------------------------------------------------------------------------------------------
int update_map( int key);
static void ShowHelpText( );//输出帮助文字

//-----------------------------------【main( )函数】--------------------------------------------
//          描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
	//改变console字体颜色
	system("color 5F"); 

	//显示帮助文字
	ShowHelpText();

	//【1】载入原始图
	g_srcImage = imread( "1.jpg", 1 );
	if(!g_srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; }  
	imshow("原始图",g_srcImage);

	//【2】创建和原始图一样的效果图,x重映射图,y重映射图
	g_dstImage.create( g_srcImage.size(), g_srcImage.type() );
	g_map_x.create( g_srcImage.size(), CV_32FC1 );
	g_map_y.create( g_srcImage.size(), CV_32FC1 );

	//【3】创建窗口并显示
	namedWindow( WINDOW_NAME, WINDOW_AUTOSIZE );
	imshow(WINDOW_NAME,g_srcImage);

	//【4】轮询按键,更新map_x和map_y的值,进行重映射操作并显示效果图
	while( 1 )
	{
		//获取键盘按键  
		int key = waitKey(0);  

		//判断ESC是否按下,若按下便退出  
		if( (key & 255) == 27 )  
		{  
			cout << "程序退出...........\n";  
			break;  
		}  

		//根据按下的键盘按键来更新 map_x & map_y的值. 然后调用remap( )进行重映射
		update_map(key);
		//此句代码的OpenCV2版为:
		//remap( g_srcImage, g_dstImage, g_map_x, g_map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) );
		//此句代码的OpenCV3版为:
		remap( g_srcImage, g_dstImage, g_map_x, g_map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) );

		//显示效果图
		imshow( WINDOW_NAME, g_dstImage );
	}
	return 0;
}

//-----------------------------------【update_map( )函数】--------------------------------
//          描述:根据按键来更新map_x与map_x的值
//----------------------------------------------------------------------------------------------
int update_map( int key )
{
	//双层循环,遍历每一个像素点
	for( int j = 0; j < g_srcImage.rows;j++)
	{ 
		for( int i = 0; i < g_srcImage.cols;i++)
		{
			switch(key)
			{
			case '1': // 键盘【1】键按下,进行第一种重映射操作
				if( i > g_srcImage.cols*0.25 && i < g_srcImage.cols*0.75 && j > g_srcImage.rows*0.25 && j < g_srcImage.rows*0.75)
				{
					g_map_x.at<float>(j,i) = static_cast<float>(2*( i - g_srcImage.cols*0.25 ) + 0.5);
					g_map_y.at<float>(j,i) = static_cast<float>(2*( j - g_srcImage.rows*0.25 ) + 0.5);
				}
				else
				{ 
					g_map_x.at<float>(j,i) = 0;
					g_map_y.at<float>(j,i) = 0;
				}
				break;
			case '2':// 键盘【2】键按下,进行第二种重映射操作
				g_map_x.at<float>(j,i) = static_cast<float>(i);
				g_map_y.at<float>(j,i) = static_cast<float>(g_srcImage.rows - j);
				break;
			case '3':// 键盘【3】键按下,进行第三种重映射操作
				g_map_x.at<float>(j,i) = static_cast<float>(g_srcImage.cols - i);
				g_map_y.at<float>(j,i) = static_cast<float>(j);
				break;
			case '4':// 键盘【4】键按下,进行第四种重映射操作
				g_map_x.at<float>(j,i) = static_cast<float>(g_srcImage.cols - i);
				g_map_y.at<float>(j,i) = static_cast<float>(g_srcImage.rows - j);
				break;
			} 
		}
	}
	return 1;
}

//-----------------------------------【ShowHelpText( )函数】----------------------------------  
//      描述:输出一些帮助信息  
//----------------------------------------------------------------------------------------------  
static void ShowHelpText()  
{  
	//输出欢迎信息和OpenCV版本
	printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n");
	printf("\n\n\t\t\t此为本书OpenCV3版的第66个配套示例程序\n");
	printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION );
	printf("\n\n  ----------------------------------------------------------------------------\n");
	//输出一些帮助信息  
	printf("\n\t欢迎来到重映射示例程序~\n\n");  
	printf( "\n\t按键操作说明: \n\n"  
		"\t\t键盘按键【ESC】- 退出程序\n"  
		"\t\t键盘按键【1】-  第一种映射方式\n"  
		"\t\t键盘按键【2】- 第二种映射方式\n"  
		"\t\t键盘按键【3】- 第三种映射方式\n"  
		"\t\t键盘按键【4】- 第四种映射方式\n" 	 );  
}  

7.4 仿射变换

对图像进行旋转、平移、缩放。
进行仿射变换:warpAffine()函数 ,需要输入仿射变换矩阵M (2*3)
getAffineTransform(),通过原图像与目标图像各三点求取仿射变换矩阵M
计算二维旋转变换矩阵:getRotationMatrix2D()函数,通过绕点,角度,缩放尺度来计算旋转矩阵M

7.5 直方图均衡化

实现直方图均衡化:equalizeHist(src,dst)函数
直方图均衡化是通过拉伸像素强度分布范围来增强图像对比度的一种方法。

8.1 查找并绘制轮廓

从二值图像中查找轮廓。
寻找轮廓:findContours()函数
绘制轮廓:drawContours()函数

8.2 寻找物体的凸包

凸包:给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形。
寻找凸包:convexHull()函数
存在hull中,可使用line函数或者drawContours()函数将其绘制出来。

8.3 使用多边形将轮廓包围

返回外部矩形边界:boundingRect()函数
寻找最小包围矩形:minAreaRect()函数 (可以是斜着的)
寻找最小包围圆形:minEnclosingCircle()函数
用椭圆拟合二维点集:fitEllipse()函数
逼近多边形曲线:approxPolyDP()函数 ,使用方法类似凸包,结果存在counters_poly中,需使用line或者drawContours()绘制出来。

8.4 图像的矩

矩用来计算形状的重心、面积,主轴和其他形状特征。
矩的计算:moments()函数
计算轮廓面积:contourArea()函数
计算轮廓长度:arcLength()函数
轮廓面积也可以矩的.m00计算,结果与contourArea()一致。

8.5 分水岭算法

实现分水岭算法:watershed()函数
图像分割的一种,分割前需要大致勾画出需要分割的区域。

8.6 图像修补

图像修复主要用来解决图象被噪声腐蚀的问题,比如镜头上的水滴、灰尘或者是旧照片的划痕。
主要是利用那些已经被破坏的区域的边缘,即边缘的颜色和结构,繁殖和混合到损坏的图像中,以达到图像修补的目的。
实现图像修补:inpaint()函数

9.1 图像直方图概述

图像直方图是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素数。

9.2 直方图的计算与绘制

计算直方图:calcHist()函数
计算出来存在MatND类型的hist中,如需绘图,还需进行归一化,绘制。
找寻最值:minMaxLoc()函数 ,可以返回最大值指针,最小值指针,最大值对应位置指针,最小值对应位置指针。

9.3 直方图对比

对比直方图:compareHist()函数
有四种方式进行对比。

9.4 反向投影

反像投影中存储的数值反应的是该像素对应亮度所在直方图中的bin区间中的值的概率。
反向投影的作用:反像投影可用于在输入图像(通常较大)中查找与特定图像(通常较小或者仅一个像素,也就是模板图像)最匹配的点或者区域,也就是定位模板图像出现在输入图像的位置。
计算反向投影:calcBackProject()函数
使用反像投影匹配:cvCalcBackProjectPatch()函数

通道复制:mixChannels()函数
可实现通道分离,合并,交换,重新分配

9.5 模板匹配

模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。
实现模板匹配:matchTemplate()函数
有六种方法进行匹配。

第四部分 深入feature2d组件

10.1 Harris角点检测

角点定义:如果某一点在任意方向的一个微小变动都会引起灰度很大的变化,那么我们就把它称之为角点。
实现Harris角点检测:cornerHarris()函数
将检测到的存入图像中,通过阈值化操作将其显示出来。

10.2 Shi-Tomasi角点检测

确定图像强角点:goodFeaturesToTrack()函数
不需要比较阈值,只需给出最大检测角点处,以及角点间允许最小距离,检测出的角点存在vector中。

10.3 亚像素级角点检测

goodFeaturesToTrack()函数只能提供像素坐标的整数值,有时需要实数坐标值,
这时需要cornerSubPix()函数。
寻找亚像素角点:cornerSubPix()函数

11.1 SURF特征点检测

SURF是尺度不变特征变换算法(SIFT)的加速版。一般来说,标准的SURF算子比SIFT算子快好几倍,并且在多幅图片下具有更好的稳定性。
SurfFeatureDetector detector
detector.detect进行检测特征点。
绘制关键点:drawKeypoints()函数
KeyPoint类用于存储特征点。

11.2 SURF特征提取

步骤:

  1. 检测特征点:SurfFeatureDetector detector -> detector.detect

  2. 计算描述子(特征向量)SurfDescriptorExtractor extractor -> extractor.compute

  3. 特征匹配:BruteForceMatcher< L2 > matcher –> matcher.match

  4. 绘制匹配点:drawMatches()函数
    程序实例:

    #include "opencv2/core/core.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include <opencv2/nonfree/nonfree.hpp>
    #include<opencv2/legacy/legacy.hpp>
    #include <iostream>
    using namespace cv;
    using namespace std;
    
    int main(  )
    {
    	//【0】改变console字体颜色
    	system("color 1F"); 
    //【1】载入素材图
    Mat srcImage1 = imread("1.jpg",1);
    Mat srcImage2 = imread("2.jpg",1);
    if( !srcImage1.data || !srcImage2.data )
    { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; }  
    
    //【2】使用SURF算子检测关键点
    int minHessian = 700;//SURF算法中的hessian阈值
    SurfFeatureDetector detector( minHessian );//定义一个SurfFeatureDetector(SURF) 特征检测类对象  
    std::vector<KeyPoint> keyPoint1, keyPoints2;//vector模板类,存放任意类型的动态数组
    
    //【3】调用detect函数检测出SURF特征关键点,保存在vector容器中
    detector.detect( srcImage1, keyPoint1 );
    detector.detect( srcImage2, keyPoints2 );
    
    //【4】计算描述符(特征向量)
    SurfDescriptorExtractor extractor;
    Mat descriptors1, descriptors2;
    extractor.compute( srcImage1, keyPoint1, descriptors1 );
    extractor.compute( srcImage2, keyPoints2, descriptors2 );
    //【5】使用BruteForce进行匹配
    // 实例化一个匹配器
    BruteForceMatcher< L2<float> > matcher;
    std::vector< DMatch > matches;
    //匹配两幅图中的描述子(descriptors)
    matcher.match( descriptors1, descriptors2, matches );
    //【6】绘制从两个图像中匹配出的关键点
    Mat imgMatches;
    drawMatches( srcImage1, keyPoint1, srcImage2, keyPoints2, matches, imgMatches );//进行绘制
    //【7】显示效果图
    imshow("匹配图", imgMatches );
    waitKey(0);
    return 0;
    

    }

11.3 使用FLANN进行特征点匹配

另一种特征匹配方法,可实现快速高效匹配。
FlannBasedMatcher类
DescriptorMatcher::match

11.4 寻找已知物体

在FLANN特征匹配基础上,还可以进一步利用Homography映射找出已知物体。
寻找透视变换:findHomography()函数
进行透视矩阵变换:perspectiveTransform()函数
步骤:

  1. 使用findHomography()函数寻找匹配上的关键点的变换。
  2. 使用perspectiveTransform()函数来映射点。

11.5 ORB特征提取

ORB算法是brief算法的改进版。
ORB算法概述算法比sift算法效率高两个数量级,而在计算速度上,ORB是sift的100倍,是surf的10倍。ORB算法综合性能在各种测评里相较于其他提取算法是最好的。
BRIEF的优点在于速度,缺点也很明显。
 不具备旋转不变性。
 对噪声敏感。
 不具备尺度不变性。
ORB算法解决了上述缺点的1.2,但未解决3.但后续如果用在视频处理,可通过跟踪等策略解决。
OrbFeatureDetector featureDetector 特征检测
OrbDescriptorExtractor featureExtractor; 计算描述子
基于FLANN的描述符对象匹配
程序实例:

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

int main(	) 
{
	//【0】改变console字体颜色
	system("color 2F"); 

	//【0】显示帮助文字


	//【0】载入源图,显示并转化为灰度图
	Mat srcImage = imread("E:\\imagelib\\78.jpg");
	imshow("原始图",srcImage);
	Mat grayImage;
	cvtColor(srcImage, grayImage, CV_BGR2GRAY);

	//------------------检测SIFT特征点并在图像中提取物体的描述符----------------------

	//【1】参数定义
	OrbFeatureDetector featureDetector;
	vector<KeyPoint> keyPoints;
	Mat descriptors;

	//【2】调用detect函数检测出特征关键点,保存在vector容器中
	featureDetector.detect(grayImage, keyPoints);

	//【3】计算描述符(特征向量)
	OrbDescriptorExtractor featureExtractor;
	featureExtractor.compute(grayImage, keyPoints, descriptors);

	//【4】基于FLANN的描述符对象匹配
	flann::Index flannIndex(descriptors, flann::LshIndexParams(12, 20, 2), cvflann::FLANN_DIST_HAMMING);

	//【5】初始化视频采集对象
	VideoCapture cap(0);

	unsigned int frameCount = 0;//帧数

	//【6】轮询,直到按下ESC键退出循环
	

		Mat captureImage = imread("E:\\imagelib\\79.jpg");
		imshow("原始图2", captureImage);
		Mat captureImage_gray;//定义两个Mat变量,用于视频采集
		cvtColor(captureImage, captureImage_gray, CV_BGR2GRAY);
		
		vector<KeyPoint> captureKeyPoints;
		Mat captureDescription;

		//【8】调用detect函数检测出特征关键点,保存在vector容器中
		featureDetector.detect(captureImage_gray, captureKeyPoints);

		//【9】计算描述符
		featureExtractor.compute(captureImage_gray, captureKeyPoints, captureDescription);

		//【10】匹配和测试描述符,获取两个最邻近的描述符
		Mat matchIndex(captureDescription.rows, 2, CV_32SC1), matchDistance(captureDescription.rows, 2, CV_32FC1);
		flannIndex.knnSearch(captureDescription, matchIndex, matchDistance, 2, flann::SearchParams());//调用K邻近算法

		//【11】根据劳氏算法(Lowe's algorithm)选出优秀的匹配
		vector<DMatch> goodMatches;
		for(int i = 0; i < matchDistance.rows; i++) 
		{
			if(matchDistance.at<float>(i, 0) < 0.6 * matchDistance.at<float>(i, 1)) 
			{
				DMatch dmatches(i, matchIndex.at<int>(i, 0), matchDistance.at<float>(i, 0));
				goodMatches.push_back(dmatches);
			}
		}

		//【12】绘制并显示匹配窗口
		Mat resultImage;
		drawMatches( captureImage, captureKeyPoints, srcImage, keyPoints, goodMatches, resultImage);
		imshow("匹配窗口", resultImage);


	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_36163358/article/details/86484958