OpenCV基本函数与原理(一)

1 加载、显示、修改、保存图像

1.1加载图像(cv::imread)

函数原型:

Mat imread(const String& filename,int flags = IMREAD_COLOR);//返回Mat对象

imread功能是加载图像文件成为一个Mat对象,其中第一个参数表示图像文件名称;
第二个参数,表示加载的图像是什么类型,支持常见的三个参数值等待: IMREAD_UNCHANGED (<0) 表示加载原图,不做任何改变;
IMREAD_GRAYSCALE ( 0)表示把原图作为灰度图像加载进来;
IMREAD_COLOR (>0) 表示把原图作为RGB图像加载进来;
注意:OpenCV支持JPG、PNG、TIFF等常见格式图像文件加载。

1.2显示图像(cv::namedWindow与cv::imshow)

函数原型为:

void nameWindow(const string& winname,int flags = WINDOW_AUTOSIZE);

namedWindow功能是创建一个OpenCV窗口,它是由OpenCV自动创建与释放,你无需取销毁它。
常见用法namedWindow(“Window Title”, WINDOW_AUTOSIZE);
WINDOW_AUTOSIZE会自动根据图像大小,显示窗口大小,不能人为改变窗口大小;
WINDOW_NORMAL,跟QT集成的时候会使用,允许修改窗口大小。

1.3修改图像(cv::cvtColor)

函数原型

void cv::cvtColor(cv::InputArray src, // 输入图
cv::OutputArray dst, // 输出图
int code, // 颜色映射类型,可以查表得到,有很多
int dstCn = 0 )// 输出的通道数 (0='automatic'),我们可以使用默认值,什么都不写。

cvtColor的功能是把图像从一个彩色空间转换到另外一个色彩空间,有三个参数,第一个参数表示源图像、第二参数表示色彩空间转换之后的图像、第三个参数表示源和目标色彩空间如:COLOR_BGR2HLS 、COLOR_BGR2GRAY 等。
例子:
cvtColor( image, gray_image, COLOR_BGR2GRAY )。

1.4保存图像(cv::imwrite)

函数原型:

CV_EXPORTS_W bool imwrite( const String& filename, InputArray img,
              const std::vector<int>& params = std::vector<int>());

功能:保存图像文件到指定目录路径;
第一个参数const String& filename表示需要写入的文件名,必须要加上后缀,比如“123.png”。
第二个参数InputArray img表示Mat类型的图像数据。
第三个参数const std::vector& params表示为特定格式保存的参数编码,它有一个默认值std::vector< int >(),所以一般情况下不用写。
注意事项:
只有8位、16位的PNG、JPG、Tiff文件格式而且是单通道或者三通道的BGR的图像才可以通过这种方式保存;
保存PNG格式的时候可以保存透明通道的图片;
可以指定压缩参数。

综合示例:

#include<opencv2\opencv.hpp>
#include<highgui.h>
using namespace cv;
int main(int argc, char** argv)
{
	// read image
	Mat image = imread("test.jpg");

	// 对图像进行所有像素用 (255- 像素值)
	Mat invertImage;
	image.copyTo(invertImage);

	// 获取图像宽、高
	int channels = image.channels();
	int rows = image.rows;
	int cols = image.cols * channels;
	if (image.isContinuous()) {
		cols *= rows;         
		rows = 1;
	}

	// 每个像素点的每个通道255取反
	uchar* p1;
	uchar* p2;
	for (int row = 0; row < rows; row++) {
		p1 = image.ptr<uchar>(row);// 获取像素指针
		p2 = invertImage.ptr<uchar>(row);
		for (int col = 0; col < cols; col++) {
			*p2 = 255 - *p1; // 取反
			p2++;
			p1++;
		}
	}

	// create windows
	namedWindow("My Test", CV_WINDOW_AUTOSIZE);
	namedWindow("My Invert Image", CV_WINDOW_AUTOSIZE);

	// display image
	imshow("My Test", image);
	imshow("My Invert Image", invertImage);

	// 关闭
	waitKey(0);
	destroyWindow("My Test");
	destroyWindow("My Invert Image");
	return 0;
}

2矩阵掩膜操作

2.1 掩膜原理

功能:通过掩膜操作实现图像对比度提高。
矩阵的掩膜操作十分简单,根据掩膜来重新计算每个像素的像素值,掩膜(mask)也被称为 kernel。
红色是中心像素,从上到下,从左到右对每个像素做同样的处理操作,得到最终结果就是对比度提高之后的输出图像Mat对象。
在这里插入图片描述
例子:使用如下矩阵对上图红色像素进行操作,可得结果:

I(i,j) = 5*I(i,j) - [I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]

在这里插入图片描述

2.2使用基本函数实现掩膜功能

1)获取图像像素指针
Mat.ptr(int i=0) 获取像素矩阵的指针,索引i表示第几行,从0开始计行数(i=0表示blue,i=1表示gree,i=2表示red )。
获得当前行指针const uchar* current= myImage.ptr(row );
获取当前像素点P(row, col)的像素值 p(row, col) =current[col]
2)像素范围处理
saturate_cast:确保像素范围为0~255
比如:
saturate_cast(-100),返回 0。
saturate_cast(288),返回255
saturate_cast(100),返回100

2.3使用库函数实现掩膜功能

1、定义掩膜:Mat kernel = (Mat_(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
表示3*3的矩阵:
在这里插入图片描述2、使用filter2D函数
函数原型:

void filter2D( InputArray src, OutputArray dst, int ddepth,
                            InputArray kernel, Point anchor=Point(-1,-1),
                            double delta=0, int borderType=BORDER_DEFAULT );

其中src与dst是Mat类型变量、src.depth表示位图深度,有32、24、8等。

综合示例:

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace cv;

int main(int argc, char** argv) {
	Mat src, dst;
	src = imread("D:/vcprojects/images/test.png");
	if (!src.data) {
		printf("could not load image...\n");
		return -1;
	}
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	imshow("input image", src);
	
	/* 自己实现掩膜操作
	int cols = (src.cols-1) * src.channels();
	int offsetx = src.channels();
	int rows = src.rows;

	dst = Mat::zeros(src.size(), src.type());
	for (int row = 1; row < (rows - 1); row++) {
		const uchar* previous = src.ptr<uchar>(row - 1);
		const uchar* current = src.ptr<uchar>(row);
		const uchar* next = src.ptr<uchar>(row + 1);
		uchar* output = dst.ptr<uchar>(row);
		for (int col = offsetx; col < cols; col++) {
			output[col] = saturate_cast<uchar>(5 * current[col] - (current[col- offsetx] + current[col+ offsetx] + previous[col] + next[col]));
		}
	}
	*/
	double t = getTickCount(); //获取时间
	Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
	filter2D(src, dst, src.depth(), kernel);
	double timeconsume = (getTickCount() - t) / getTickFrequency();
	printf("tim consume %.2f\n", timeconsume);

	namedWindow("contrast image demo", CV_WINDOW_AUTOSIZE);
	imshow("contrast image demo", dst);

	waitKey(0);
	return 0;
}

3图像混合

3.1线性混合原理

线性混合的理论公式如下:
在这里插入图片描述
其中α的取值范围为0~1之间。
从数学层面来解释,可以理解为:g(x)由f0(x)和f1(x)两个函数组成,α为f(x)的权重系数,取值范围应当0 ≤ α ≤ 1。
从图像层面来解释,可以理解为:f0(x)为图像0,f1(x)为图像1,g(x)为图像0和图像1混合后生成的混合图像,α代表着图像0和图像1在混合时的权重关系。

3.2相关API:addWeighted

函数原型:

void cv::addWeighted( const CvArr* src1, double alpha,const CvArr* src2, double beta,double gamma, CvArr* dst );

参数1:输入图像Mat – src1
参数2:输入图像src1的alpha值
参数3:输入图像Mat – src2
参数4:输入图像src2的alpha值
参数5:gamma值
参数6:输出混合图像
注意点:两张图像的大小和类型必须一致才可以

综合示例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char** argv) {
	Mat src1, src2, dst;
	src1 = imread("D:/vcprojects/images/LinuxLogo.jpg");
	src2 = imread("D:/vcprojects/images/win7logo.jpg");
	if (!src1.data) {
		cout << "could not load image Linux Logo..." << endl;
		return -1;
	}
	if (!src2.data) {
		cout << "could not load image WIN7 Logo..." << endl;
		return -1;
	}

	double alpha = 0.5;
	if (src1.rows == src2.rows && src1.cols == src2.cols && src1.type() == src2.type())//两张图片大小和类型相同才能够混合
	 {
		// addWeighted(src1, alpha, src2, (1.0 - alpha), 0.0, dst);
		// multiply(src1, src2, dst, 1.0); //混合效果不如addWeighted
		
		imshow("linuxlogo", src1);
		imshow("win7logo", src2);
		namedWindow("blend demo", CV_WINDOW_AUTOSIZE);
		imshow("blend demo", dst);
	}
	else {
		printf("could not blend images , the size of images is not same...\n");
		return -1;
	}

	waitKey(0);
	return 0;
}

4绘制形状与文字

4.1使用cv::Point与cv::Scalar

1)cv::Point:OpenCV的基本数据类型之一,表示一个坐标为整数的二维点,是一个包含integer类型成员x和y的简单结构体。
typedef struct CvPoint
{
int x; /* X坐标, 通常以0为基点 /
int y; /
y坐标, 通常以0为基点 */
}CvPoint;

Point p;
p.x = 10;
p.y = 8;
or
p = Pont(10,8);  //表示笛卡尔坐标系(10,8)

2)cv::Scalar:设置图片颜色

typedef struct CvScalar
	{
		double val[4];
	}CvScalar;

cv::Scalar的构造函数是cv::Scalar(v1, v2, v3, v4),前面的三个参数是依次设置BGR的,和RGB相反,第四个参数设置图片的透明度。
补充:当使用opencv提供的库函数imread()、imwrite()和imshow()时,cv::Scalar(v1, v2, v3, v4)的这四个参数就依次是BGRA,即蓝、绿、红和透明度。

4.2 绘制线、矩形、园、椭圆等基本几何形状

1)画线:cv::line
函数原型:

void cvLine( CvArr* img,CvPoint pt1, CvPoint pt2, CvScalar color,int thickness=1, int line_type=8, int shift=0 );

第一个参数img:要划的线所在的图像;
第二个参数pt1:直线起点;
第三个参数pt2:直线终点;
第四个参数color:直线的颜色 e.g:Scalor(0,0,255);
第五个参数thickness=1:线条粗细;
第六个参数line_type=8:
8 (or 0) - 8-connected line(8邻接)连接 线。
4 - 4-connected line(4邻接)连接线。
CV_AA - antialiased 线条。
第七个参数:坐标点的小数点位数。

2)画椭圆:cv::ellipse
函数原型:

void cvEllipse( CvArr* img, CvPoint center, CvSize axes, double angle,
                double start_angle, double end_angle, CvScalar color,
                int thickness=1, int line_type=8, int shift=0 );

第一个参数img:要划的线所在的图像;
第二个参数center:椭圆圆心坐标
第三个参数axes:轴的长度;
第四个参数angle:偏转的角度;
第五个参数start_angle:圆弧起始角的角度;
第六个参数end_angle:圆弧终结角的角度;
第七个参数color:线条的颜色;
第八个参数thickness:线条的粗细程度。
第九个参数line_type:线条的类型,见CVLINE的描述。
第十个参数shift:圆心坐标点和数轴的精度。

3)画圆:cv::Circle
函数原型

void cvCircle( CvArr* img, CvPoint center, int radius, CvScalar color,
               int thickness=1, int line_type=8, int shift=0 );

img:要划的线所在的图像;图像;
center:圆心坐标;
radius:圆形的半径;
color:线条的颜色;
thickness:如果是正数,表示组成圆的线条的粗细程度。否则,表示圆是否被填充;
line_type:线条的类型。见 cvLine 的描述;
shift:圆心坐标点和半径值的小数点位数。

4)绘制添加文字:putText
函数原型:

	void cv::putText(
		cv::Mat& img, // 待绘制的图像
		const string& text, // 待绘制的文字
		cv::Point origin, // 文本框的左下角
		int fontFace, // 字体 (如cv::FONT_HERSHEY_PLAIN)
		double fontScale, // 尺寸因子,值越大文字越大
		cv::Scalar color, // 线条的颜色(RGB)
		int thickness = 1, // 线条宽度
		int lineType = 8, // 线型(4邻域或8邻域,默认8邻域)
		bool bottomLeftOrigin = false // true='origin at lower left'
	);
	```
## 4.3随机数
生成cv::RNG
1)高斯随机数gaussian(double sigma)
2)正态分布随机数uniform(int a,int b)

**综合函数:**
```cpp
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;
Mat bgImage;
const char* drawdemo_win = "draw shapes and text demo";
void MyLines();
void MyRectangle();
void MyEllipse();
void MyCircle();
void MyPolygon();
void RandomLineDemo();
int main(int argc, char** argv) {
	bgImage = imread("D:/vcprojects/images/test1.png");
	if (!bgImage.data) {
		printf("could not load image...\n");
		return -1;
	}
	//MyLines();
	//MyRectangle();
	//MyEllipse();
	//MyCircle();
	//MyPolygon();

	//putText(bgImage, "Hello OpenCV", Point(300, 300), CV_FONT_HERSHEY_COMPLEX, 1.0, Scalar(12, 23, 200), 3, 8);
	//namedWindow(drawdemo_win, CV_WINDOW_AUTOSIZE);
	//imshow(drawdemo_win, bgImage);

	RandomLineDemo();
	waitKey(0);
	return 0;
}

void MyLines() {
	Point p1 = Point(20, 30);
	Point p2;
	p2.x = 400;
	p2.y = 400;
	Scalar color = Scalar(0, 0, 255);
	line(bgImage, p1, p2, color, 1, LINE_AA);
}

void MyRectangle() {
	Rect rect = Rect(200, 100, 300, 300);
	Scalar color = Scalar(255, 0, 0);
	rectangle(bgImage, rect, color, 2, LINE_8);
}

void MyEllipse() {
	Scalar color = Scalar(0, 255, 0);
	ellipse(bgImage, Point(bgImage.cols / 2, bgImage.rows / 2), Size(bgImage.cols / 4, bgImage.rows / 8), 90, 0, 360, color, 2, LINE_8);
}

void MyCircle() {
	Scalar color = Scalar(0, 255, 255);
	Point center = Point(bgImage.cols / 2, bgImage.rows / 2);
	circle(bgImage, center, 150, color, 2, 8);
}

void MyPolygon() {
	Point pts[1][5];
	pts[0][0] = Point(100, 100);
	pts[0][1] = Point(100, 200);
	pts[0][2] = Point(200, 200);
	pts[0][3] = Point(200, 100);
	pts[0][4] = Point(100, 100);

	const Point* ppts[] = { pts[0] };
	int npt[] = { 5 };
	Scalar color = Scalar(255, 12, 255);

	fillPoly(bgImage, ppts, npt, 1, color, 8);
}

void RandomLineDemo() {
	RNG rng(12345);
	Point pt1;
	Point pt2;
	Mat bg = Mat::zeros(bgImage.size(), bgImage.type());
	namedWindow("random line demo", CV_WINDOW_AUTOSIZE);
	for (int i = 0; i < 100000; i++) {
		pt1.x = rng.uniform(0, bgImage.cols);
		pt2.x = rng.uniform(0, bgImage.cols);
		pt1.y = rng.uniform(0, bgImage.rows);
		pt2.y = rng.uniform(0, bgImage.rows);
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		if (waitKey(50) > 0) {
			break;
		}
		line(bg, pt1, pt2, color, 1, 8);
		imshow("random line demo", bg);
	}
}

5 模糊图像

5.1 意义和作用

从主观意愿上说,我们希望看到清晰的图像,而不是模糊的图像。所以很多时候我们听说还有一种专门进行模糊图像的操作时,感觉不可思议,这有什么用呢。要知道模糊图像只是处理噪声带来的副作用,并不是我们的目的。图像没有噪声的时候,我们用平滑滤波器去模糊图像干什么呢?还真有一个重要的应用。我们可以看到,相对于原始图像,一些较小的物体已经融入背景,看不到了,有些物体即使能看到,亮度也明显降低。这样,我们用图像模糊将图像中较大的较亮的物体保留了下来,而其它的物体则消除了。我们进一步通过阈值处理对模糊后的图像进行操作,将最高亮度的25%作为阈值,低于此阈值的赋为0,高于此阈值的赋为255。
像这样利用阈值函数处理并基于物体亮度来消除某些物体的操作时很典型的。当我们只想得到感兴趣的物体时,通过图像模糊,可以将那些尺寸和亮度较小的物体过滤掉,较大的物体则易于检测。除了降低噪声,这就是图像平滑(模糊)的另一个重要应用:目标提取。

5.2模糊方法

1)均值滤波(归一化滤波)
均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(以目标像素为中心的周围8个像素,构成一个滤波模板,即去掉目标像素本身),再用模板中的全体像素的平均值来代替原来像素值。
在这里插入图片描述
用 3×3 大小模板进行均值滤波。
在这里插入图片描述
由于图像边框上的像素无法被模板覆盖,所以不做处理。
这当然造成了图像边缘的缺失。
以(2,2)像素点为例。
在这里插入图片描述
则滤波后的结果为:
在这里插入图片描述
滤波后(2,2)像素点的值由 10 变为 3。
最终结果:
在这里插入图片描述
相应的API:

void blur(Mat src,Mat dst, Size(xradius,yradius), Point(-1, -1))

src:(原始图像:channels不限,但是depth应当是CV_8U,CV_16U,CV_16S,CV_32F或CV_64F);
dst:(目标图像:size和type应与原始图像相同);
Size:(用于平滑操作的核的大小);
Point:锚点,默认值为Point(-1,-1)表示锚点在核的中心);

2)中值滤波:对椒盐噪声有很好的抑制作用
中值,中间值,将数据从小到大排序后的中间值
用 3×3 大小模板进行中值滤波。
在这里插入图片描述
以(2,2)像素点为例。
在这里插入图片描述
对模板中的 9 个数进行从小到大排序:1,1,1,2,2,5,6,6,10。中间值为 2.所有,中值滤波后(2,2)位置的值变为 2. 同理对其他像素点。
处理结果:
在这里插入图片描述
相应的API:

void medianBlur(InputArray src, OutputArray dst, int ksize)

3)高斯滤波:
高斯函数
在这里插入图片描述
高斯滤波的重要两步就是先找到高斯模板然后再进行卷积,模板(mask在查阅中有的地方也称作掩膜或者是高斯核)。所以这个时候需要知道它怎么来?又怎么用?
示例:
假定中心点的坐标是(0,0),那么取距离它最近的8个点坐标,为了计算,需要设定σ的值。假定σ=1.5,则模糊半径为1的高斯模板就算如下
在这里插入图片描述
这个时候我们我们还要确保这九个点加起来为1(这个是高斯模板的特性),这9个点的权重总和等于0.4787147,因此上面9个值还要分别除以0.4787147,得到最终的高斯模板。
在这里插入图片描述
高斯滤波计算:
有了高斯模板,那么高斯滤波的计算便顺风顺水了。
示例:
假设现有9个像素点,灰度值(0-255)的高斯滤波计算如下:
在这里插入图片描述
将这9个值加起来,就是中心点的高斯滤波的值;
对所有点重复这个过程,就得到了高斯模糊后的图像。

相应的API:

#高斯函数
void GaussianBlur(InputArray src,      //输入图像
            OutputArray dst,    //输出图像
            Size ksize,       //内核的大小
            double sigmaX,     //高斯核函数在X方向的标准偏差  
            double sigmaY=0,    //高斯核函数在Y方向的标准偏差
            intborderType=BORDER_DEFAULT )   

4)双边滤波:
均值模糊无法克服边缘像素信息丢失缺陷。原因是均值模糊是基于平均权重。
高斯模糊部分克服了该缺陷,但是无法完全避免,因为没考虑到像素值的不同。
双边滤波是保留边缘的滤波方法,避免了边缘信息的丢失,保留了图像轮廓不变。
相应的API:

void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT )

综合示例:

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

int main(int argc, char** argv) {
	Mat src, dst;
	src = imread("D:/vcprojects/images/test.png");
	if (!src.data) {
		printf("could not load image...\n");
		return -1;
	}
	char input_title[] = "input image";
	char output_title[] = "blur image";
	namedWindow(input_title, CV_WINDOW_AUTOSIZE);
	namedWindow(output_title, CV_WINDOW_AUTOSIZE);
	imshow(input_title, src);

	blur(src, dst, Size(11, 11), Point(-1, -1)); //均值滤波
	imshow(output_title, dst);

	Mat gblur;
	GaussianBlur(src, gblur, Size(11, 11), 11, 11); //高斯滤波
	imshow("gaussian blur", gblur);
    
    //medianBlur(src, dst, 3); //中值滤波
    Mat bila;
	bilateralFilter(src, bila, 15, 100, 5); //双边滤波
	namedWindow("BiBlur Filter Result", CV_WINDOW_AUTOSIZE);
	imshow("BiBlur Filter Result", bila);

	waitKey(0);
	return 0;
}
发布了29 篇原创文章 · 获赞 1 · 访问量 530

猜你喜欢

转载自blog.csdn.net/qq_45173769/article/details/104720305