OpenCV基础知识介绍

一、图像与矩阵

  通常来讲,图像是一个标准的矩形,有着宽度(width)和高度(height)。而矩阵有着行(row)和列(column),矩阵的操做在数学和计算机中的处理都很常见且成熟,因而很天然的就把图像做为一个矩阵,把对图像的操做转换成对矩阵的操做,实际上全部的图像处理工具都是这么作的。计算机视觉中的图像是数字设备捕获到物理世界的表象。图像只是存储在矩阵格式中的数字序列。每一个数字是一个考虑的波长(例如RGB图像中的红、绿、蓝)或波长范围(对全色设备而言,如红外光谱仪)的光强衡量。图像中的每一个点称为像素,每一个像素能够存储一个或多个值。这取决与它的灰度。这些值存储只有一个值,例如0或者1.灰度级尺寸能够存储一个值,彩色图像能够存储3个值。php

 MatBasicImageForComputer.jpg

  例如,在上图中,您能够看到汽车的镜子只不过是一个包含像素点的全部强度值的矩阵。咱们如何得到和存储像素值可能根据咱们的须要而变化,但最终计算机世界中的全部图像能够简化为数值矩阵和描述矩阵自己的其余信息。OpenCV是一个计算机视觉库,其主要重点是处理和操做这些信息。所以,您须要熟悉的第一件事是OpenCV如何存储和处理图像。html

二、opencv的mat类

  opencv最初是Intel在俄罗斯的团队实现的,而在后期Intel对opencv的支持力度慢慢变小。在08年,美国一家机器人公司Willow Garage开始大力支持opencv,在获得支持后opencv更新速度明显加快,加入了不少新特性。在opencv1.x时代,数据类型为IplImage,在使用这种数据类型时,考虑内存管理称为众多开发者的噩梦。在进入到opencv2.x时代,一种新的数据类型Mat被定义,将开发者极大的解脱出来。因此在接下来的教程中,都会使用Mat类,而在看到IplImage类数据时也不要感到奇怪。 
Mat类有两种基本的数据结构组成,一种是矩阵头(包括矩阵尺寸、存储方法、存储路径等信息),另外一个是指向包含像素值的矩阵的指针(矩阵维度取决于其存储方法)。矩阵头的尺寸是个常数,可是矩阵自身的尺寸根据图像不一样而不一样。Mat类的定义有不少行,下面列出来一些关键属性以下所示:python

class CV_EXPORTS Mat
{
public:
//......不少函数定义,在此省略
...
/*flag参数包含许多关于矩阵的信息,如:
Mat的标识
数据是否连续
深度
通道数目
*/
int flags;
int dims;   //矩阵的维数,取值应该大于或等于2
int rows,cols;  //矩阵的行列数
uchar* data;    //指向数据的指针
int* refcount;  //指向引用计数的指针,若是数据由用户分配则为NULL

//......其余的一些函数
};

  

能够把Mat看做是一个通用的矩阵类,能够经过Mat中诸多的函数来建立和操做多维矩阵。有不少种方法能够建立一个Mat对象。linux

Mat类提供了一系列的构造函数,能够根据需求很方便的建立Mat对象,其部分构造方法以下:ios

Mat::Mat()      //无参数构造方法

/*建立行数为rows,列数为cols,类型为type的图像*/
Mat::Mat(int rows, int cols, int type)  

/*建立大小为size,类型为type的图像*/
Mat::Mat(Size size, int type)

/*建立行数为rows,列数为cols,类型为type的图像
并将全部元素初始化为s*/
Mat::Mat(int rows, int cols, int type, const Scalar& s)
ex:Mat(3,2,CV_8UC1, Scalar(0))  //三行两列全部元素为0的一个矩阵

/*建立大小为size,类型为type,初始元素为s*/
Mat::Mat(Size size, int type, const Scalar& s)

/*将m赋值给新建立的对象*/
Mat::Mat(const Mat& m)  //此处不会发生数据赋值,而是两个对象共用数据

/*建立行数为rows,列数为cols,类型为type的图像
此构造函数不建立图像数据所需内存而是直接使用data所指内存
图像的步长由step指定*/
Mat::Mat(int rows, int cols, int type, void* data, size_t step = AUTO_STEP)

Mat::Mat(Size size, int type, void* data, size_t step = AUTO_STEP)  //同上

/*建立新的图像为m数据的一部分,其具体的范围由rowRange和colRange指定
此构造函数也不进行图像数据的复制操做,与m共用数据*/
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)

/*建立新的矩阵为m的一部分,具体的范围由roi指定
此构造函数一样不进行数据的复制操做与m共用数据*/
Mat::Mat(const Mat& m, const Rect& roi)

  在构造函数中不少都涉及到type,type能够是CV_8UC1, CV_8UC3, …,CV_64FC4等。这些type中的8U表示8位无符号整数(unsigned int), 16S表示16位有符号整数,64F表示64位浮点数即double类型,C表示channel表示图像通道,C后面的数字表示通道数。如C1表示单通道图像,C4表示4通道图像,以此类推。若是须要更多的通道数,须要使用宏CV_8UC(n)重定义,其中n是须要的通道数。如程序员

Mat M(3, 2, CV_8UC(5));     //建立3行2列通道为5的图像

  下面经过一个实例进行理解:算法

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;

int main()
{

	Mat M1(3, 2, CV_8UC3, Scalar(0, 0, 255));
	cout << "M1 = " << endl << " " << M1 << endl;

	Mat M2(Size(3, 2), CV_8UC3, Scalar(1, 2, 3));
	cout << "M2 = " << endl << " " << M2 << endl;

	Mat M3(M2);
	cout << "M3 = " << endl << " " << M3 << endl;

	Mat M4(M2, Range(1, 2), Range(1, 2));
	cout << "M4 = " << endl << " " << M4 << endl;

	waitKey(0);


	return 0;
}

  运行结果以下:编程

也可使用create()函数建立对象。若是create()函数指定的参数与图像以前的参数相同,则不进行实质的内存申请操做,若是参数不一样,则减小原始数据内存的索引并从新申请内存。使用方法以下所示:数组

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat M1;
	M1.create(4, 4, CV_8UC(2));
	cout << "M1 = " << endl << " " << M1 << endl << endl;

	waitKey(0);

	return 0;
}

 运行结果以下:安全

**值得注意的是使用create()函数没法初始化Mat类。

opencv也可使用Matlab的风格建立函数如:zeros(),ones()和eyes()。这些方法使得代码很是简洁,使用也很是方便。在使用这些函数时须要指定图像的大小和类型。

 

在已有Mat类的基础上建立一个Mat类,即新建立的类是已有Mat类的某一行或某一列,可使用clone()或copyTo(),这样的构造方式不是以数据共享方式存在。能够利用setTo()函数更改矩阵的值进行验证,方法以下:

 程序中M4.row(0)就是指的M4的第一行,其它相似。必须值得注意的是:在本篇介绍中工较少了clone()、copyTo()、和”=”三种实现矩阵赋值的方式。其中”=”是使用重载的方式将矩阵值赋值给新的矩阵,而这种方式下,被赋值的矩阵和赋值矩阵之间共享空间,改变任何一个矩阵的值会影响到另一个矩阵。而clone()和copyTo()两种方法在赋值后,两个矩阵的存储空间是独立的,不存在共享空间的状况。 
运行结果以下

opencv中还支持其余的格式化输入与输出

 

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;

int main()
{
	//使用函数randu()生成随机数,随机数范围为0-255
	Mat R = Mat(3, 2, CV_8UC3);
	randu(R, Scalar::all(0), Scalar::all(255));

	//以默认格式输出
	cout << "R (default) = " << endl << R << endl << endl;

	//以Python格式输出
	cout << "R (python)  = " << endl << format(R, Formatter::FMT_PYTHON) << endl << endl;

	//以CSV格式输出
	cout << "R (csv)     = " << endl << format(R, Formatter::FMT_CSV) << endl << endl;

	//以Numpy格式输出
	cout << "R (numpy)   = " << endl << format(R, Formatter::FMT_NUMPY) << endl << endl;

	//以C语言的格式输出
	cout << "R (c)       = " << endl << format(R, Formatter::FMT_C) << endl << endl;

	//2D点
	Point2f P(5, 1);
	cout << "Point (2D) = " << P << endl << endl;
	//3D点
	Point3f P3f(2, 6, 7);
	cout << "Point (3D) = " << P3f << endl << endl;
	//vec模板类,数值响亮输出
	vector<float> v;
	v.push_back((float)CV_PI);   v.push_back(2);    v.push_back(3.01f);
	cout << "Vector of floats via Mat = " << Mat(v) << endl << endl;
	//矢量输出
	vector <Point2f> vPoints(20);
	vector<Point2f> vPoints(20);
	for (size_t i = 0; i < vPoints.size(); ++i)
		vPoints[i] = Point2f((float)(i * 5), (float)(i % 7));
	cout << "A vector of 2D Points = " << vPoints << endl << endl;


	waitKey(0);


	return 0;
}

 输出结果以下所示:

 

三、图像矩阵如何存储在内存中?

   正如以前所说到的那样,矩阵的大小取决于所使用的颜色系统。更准确地说,它取决于使用的通道数量。在灰度图像的状况下,咱们有相似的东西:

tutorial_how_matrix_stored_1.png

对于多通道图像,列包含与通道数同样多的子列。例如,在BGR颜色系统的状况下:

tutorial_how_matrix_stored_2.png

可是值得注意到的是:通道的顺序是反向的:BGR而不是RGB。由于在许多状况下,内存足够大以便以连续方式存储行,因此行能够一个接一个地跟随,从而建立单个长行。由于一切都在一个接一个的地方,这可能有助于加快扫描过程。咱们可使用cv :: Mat :: isContinuous()函数来询问矩阵是不是这种状况。

四、如何浏览图像的每一个像素?

   下面是官方提供的一个高效的图片处理的示例,

让咱们考虑一种简单的减色方法。经过使用无符号字符C和C ++类型进行矩阵项存储,像素通道能够具备多达256个不一样的值。对于三通道图像,这能够容许造成太多颜色(确切地说是1600万)。使用如此多的色调可能会严重影响咱们的算法性能。可是,有时只需少许工做便可得到相同的最终结果。

在这种状况下,咱们一般会减小色彩空间。这意味着咱们将颜色空间当前值除以新的输入值,最终获得更少的颜色。例如,0到9之间的每一个值都取新零值,每一个值在10到19之间,值为10,依此类推。当您将uchar(unsigned char - 也就是0到255之间的值)值除以int值时,结果也将是char。这些值可能只是char值。所以,任何分数都将向下舍入。利用这一事实,uchar域中的上层操做可表示为:

  简单的颜色空间缩减算法包括仅经过图像矩阵的每一个像素并应用该公式。值得注意的是,咱们进行了除法和乘法运算。这些操做对于系统来讲是很是昂贵的。若是可能的话,经过使用更便宜的操做(例如一些减法,添加或在最好的状况下使用简单的赋值)来避免它们是值得的。此外,请注意,上部操做只有有限数量的输入值。uchar系统的状况下,这确切地说是256。

  所以,对于较大的图像,最好事先计算全部可能的值,并在分配期间经过使用查找表进行分配。查找表是简单数组(具备一个或多个维度),对于给定的输入值变量,它保存最终输出值。它的优点在于咱们不须要进行计算,咱们只须要读取结果。

  咱们的测试用例程序(以及此处提供的示例)将执行如下操做:读取控制台行参数图像(多是颜色或灰度 - 控制台行参数)并使用给定的控制台行参数整数值应用缩减。在OpenCV中,目前有三种主要方式逐像素地浏览图像。为了使事情更有趣,将使用全部这些方法扫描每一个图像,并打印出它花了多长时间。

#include "stdafx.h"//viusal studio 必须加的linux下需删掉
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include "opencv2/imgcodecs.hpp"
#include <opencv2/highgui.hpp>
#include <iostream>
#include <sstream>

using namespace std;
using namespace cv;

static void help()
{
	cout
		<< "\n--------------------------------------------------------------------------" << endl
		<< "This program shows how to scan image objects in OpenCV (cv::Mat). As use case"
		<< " we take an input image and divide the native color palette (255) with the " << endl
		<< "input. Shows C operator[] method, iterators and at function for on-the-fly item address calculation." << endl
		<< "Usage:" << endl
		<< "./how_to_scan_images <imageNameToUse> <divideWith> [G]" << endl
		<< "if you add a G parameter the image is processed in gray scale" << endl
		<< "--------------------------------------------------------------------------" << endl
		<< endl;
}

Mat& ScanImageAndReduceC(Mat& I, const uchar* table);
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* table);
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar * table);

int main(int argc, char* argv[])
{
	help();
	if (argc < 3)
	{
		cout << "Not enough parameters" << endl;
		return -1;
	}

	Mat I, J;
	if (argc == 4 && !strcmp(argv[3], "G"))
		I = imread(argv[1], IMREAD_GRAYSCALE);
	else
		I = imread(argv[1], IMREAD_COLOR);

	if (I.empty())
	{
		cout << "The image" << argv[1] << " could not be loaded." << endl;
		return -1;
	}

	//! [dividewith]
	int divideWith = 0; // convert our input string to number - C++ style
	stringstream s;
	s << argv[2];
	s >> divideWith;
	if (!s || !divideWith)
	{
		cout << "Invalid number entered for dividing. " << endl;
		return -1;
	}

	uchar table[256];
	for (int i = 0; i < 256; ++i)
		table[i] = (uchar)(divideWith * (i / divideWith));
	//! [dividewith]

	const int times = 100;
	double t;

	t = (double)getTickCount();

	for (int i = 0; i < times; ++i)
	{
		cv::Mat clone_i = I.clone();
		J = ScanImageAndReduceC(clone_i, table);
	}

	t = 1000 * ((double)getTickCount() - t) / getTickFrequency();
	t /= times;

	cout << "Time of reducing with the C operator [] (averaged for "
		<< times << " runs): " << t << " milliseconds." << endl;

	t = (double)getTickCount();

	for (int i = 0; i < times; ++i)
	{
		cv::Mat clone_i = I.clone();
		J = ScanImageAndReduceIterator(clone_i, table);
	}

	t = 1000 * ((double)getTickCount() - t) / getTickFrequency();
	t /= times;

	cout << "Time of reducing with the iterator (averaged for "
		<< times << " runs): " << t << " milliseconds." << endl;

	t = (double)getTickCount();

	for (int i = 0; i < times; ++i)
	{
		cv::Mat clone_i = I.clone();
		ScanImageAndReduceRandomAccess(clone_i, table);
	}

	t = 1000 * ((double)getTickCount() - t) / getTickFrequency();
	t /= times;

	cout << "Time of reducing with the on-the-fly address generation - at function (averaged for "
		<< times << " runs): " << t << " milliseconds." << endl;

	//! [table-init]
	Mat lookUpTable(1, 256, CV_8U);
	uchar* p = lookUpTable.ptr();
	for (int i = 0; i < 256; ++i)//
		p[i] = table[i];
	//! [table-init]

	t = (double)getTickCount();

	for (int i = 0; i < times; ++i)
		//! [table-use]
		LUT(I, lookUpTable, J);
	//! [table-use]

	t = 1000 * ((double)getTickCount() - t) / getTickFrequency();
	t /= times;

	cout << "Time of reducing with the LUT function (averaged for "
		<< times << " runs): " << t << " milliseconds." << endl;
	return 0;
}

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
	// accept only char type matrices
	CV_Assert(I.depth() == CV_8U);

	int channels = I.channels();

	int nRows = I.rows;
	int nCols = I.cols * channels;

	if (I.isContinuous())
	{
		nCols *= nRows;
		nRows = 1;
	}

	int i, j;
	uchar* p;
	for (i = 0; i < nRows; ++i)
	{
		p = I.ptr<uchar>(i);
		for (j = 0; j < nCols; ++j)
		{
			p[j] = table[p[j]];
		}
	}
	return I;
}

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
	// accept only char type matrices
	CV_Assert(I.depth() == CV_8U);

	const int channels = I.channels();
	switch (channels)
	{
	case 1:
	{
		MatIterator_<uchar> it, end;
		for (it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
			*it = table[*it];
		break;
	}
	case 3:
	{
		MatIterator_<Vec3b> it, end;
		for (it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
		{
			(*it)[0] = table[(*it)[0]];
			(*it)[1] = table[(*it)[1]];
			(*it)[2] = table[(*it)[2]];
		}
	}
	}

	return I;
}

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
	// accept only char type matrices
	CV_Assert(I.depth() == CV_8U);

	const int channels = I.channels();
	switch (channels)
	{
	case 1:
	{
		for (int i = 0; i < I.rows; ++i)
			for (int j = 0; j < I.cols; ++j)
				I.at<uchar>(i, j) = table[I.at<uchar>(i, j)];
		break;
	}
	case 3:
	{
		Mat_<Vec3b> _I = I;

		for (int i = 0; i < I.rows; ++i)
			for (int j = 0; j < I.cols; ++j)
			{
				_I(i, j)[0] = table[_I(i, j)[0]];
				_I(i, j)[1] = table[_I(i, j)[1]];
				_I(i, j)[2] = table[_I(i, j)[2]];
			}
		I = _I;
		break;
	}
	}
	return I;
}

  程序运行方法的是:

执行文件 图像名.jpg 想要减小的整数值[G]

  这里我用的是visual studio,则执行方法是,选中工程名——>右键——>属性

 

程序运行结果以下:

 下面对程序的优势进行分析:

高效的方式(经过访问的方式提高访问速度)

  说到性能,你没法击败经典的C访问。所以,咱们建议的最有效的方法是:

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);
    int channels = I.channels();
    int nRows = I.rows;
    int nCols = I.cols * channels;
    if (I.isContinuous())
    {
        nCols *= nRows;
        nRows = 1;
    }
    int i,j;
    uchar* p;
    for( i = 0; i < nRows; ++i)
    {
        p = I.ptr<uchar>(i);
        for ( j = 0; j < nCols; ++j)
        {
            p[j] = table[p[j]];
        }
    }
    return I;
}

  在这里,咱们基本上只获取指向每行开头的指针,而后直到它结束。在矩阵以连续方式存储的特殊状况下,咱们只须要一次请求指针并一直到最后。咱们须要注意彩色图像:咱们有三个通道,因此咱们须要在每行中传递三倍以上的项目。

还有另一种方法。Mat对象的数据数据成员返回指向第一行第一列的指针。若是此指针为null,则表示该对象中没有有效输入。检查这是检查图像加载是否成功的最简单方法。若是存储是连续的,咱们可使用它来遍历整个数据指针。若是是灰度图像,这将看起来像:

uchar * p = I.data;
for(unsigned  int i = 0; i <ncol * nrows; ++ i)
    * p ++ = table [* p];

  你会获得相同的结果。可是,这段代码后来很难阅读。若是你有更先进的技术,那就更难了。并且,在实践中我发现你会获得相同的性能结果(由于大多数现代编译器可能会自动为你作出这个小优化技巧)。

迭代器(安全)方法

  若是有效的方法确保您经过正确数量的uchar字段并跳过行之间可能出现的间隙是您的责任。迭代器方法被认为是一种更安全的方式,由于它从用户接管这些任务。您须要作的就是询问图像矩阵的开始和结束,而后只需增长开始迭代器直到结束。要获取迭代器指向的值,请使用*运算符(在它以前添加它)。

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);
    const int channels = I.channels();
    switch(channels)
    {
    case 1:
        {
            MatIterator_<uchar> it, end;
            for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
                *it = table[*it];
            break;
        }
    case 3:
        {
            MatIterator_<Vec3b> it, end;
            for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
            {
                (*it)[0] = table[(*it)[0]];
                (*it)[1] = table[(*it)[1]];
                (*it)[2] = table[(*it)[2]];
            }
        }
    }
    return I;
}

  在彩色图像的状况下,咱们每列有三个uchar项目。这能够被认为是uchar项目的简短向量,已经在OpenCV中使用Vec3b名称进行了洗礼要访问第n个子列,咱们使用简单的operator []访问。重要的是要记住OpenCV迭代器遍历列并自动跳到下一行。所以,若是使用简单的uchar迭代器,在彩色图像的状况下,您将只能访问蓝色通道值。

带参考返回的动态地址计算

建议不要使用最终方法进行扫描。它是为了获取或修改图像中的某些随机元素。其基本用法是指定要访问的项目的行号和列号。在咱们以前的扫描方法中,您已经能够观察到经过咱们正在查看图像的类型来讲这很重要。这里没有什么不一样,由于您须要手动指定在自动查找中使用的类型。对于如下源代码(使用+ cv :: at()函数)的灰度图像,您能够观察到这种状况

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);
    const int channels = I.channels();
    switch(channels)
    {
    case 1:
        {
            for( int i = 0; i < I.rows; ++i)
                for( int j = 0; j < I.cols; ++j )
                    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
            break;
        }
    case 3:
        {
         Mat_<Vec3b> _I = I;
         for( int i = 0; i < I.rows; ++i)
            for( int j = 0; j < I.cols; ++j )
               {
                   _I(i,j)[0] = table[_I(i,j)[0]];
                   _I(i,j)[1] = table[_I(i,j)[1]];
                   _I(i,j)[2] = table[_I(i,j)[2]];
            }
         I = _I;
         break;
        }
    }
    return I;
}

  这些函数采用您的输入类型和坐标,并即时计算查询项的地址。而后返回对它的引用。当您得到时,这多是常量,而在设置时,这多是很是数做为调试模式中的安全步骤执行检查输入坐标是否有效且确实存在。若是不是这种状况,您将在标准错误输出流上得到一个很好的输出消息。与发布模式中的有效方式相比,使用它的惟一区别是,对于图像的每一个元素,您将得到一个新的行指针,用于咱们使用C运算符[]来获取列元素。

  若是您须要使用此方法对图像执行屡次查找,则为每一个访问输入类型和at关键字可能会很麻烦且耗时。为了解决这个问题,OpenCV有一个cv :: Mat_数据类型。它与Mat相同,须要在定义时经过查看数据矩阵来指定数据类型,可是做为回报,您可使用operator()来快速访问项目。为了使事情变得更好,这能够很容易地从一般的cv :: Mat数据类型转换。您能够在上部函数的彩色图像的状况下看到此示例用法。然而,重要的是要注意cv :: at()能够完成相同的操做(具备相同的运行时速度功能。为懒惰的程序员技巧写一点。

核心功能

这是在图像中实现查找表修改的奖励方法。在图像处理中,您但愿将全部给定图像值修改成其余值是很常见的。OpenCV提供了修改图像值的功能,无需编写图像的扫描逻辑。咱们使用核心模块cv :: LUT()函数。首先,咱们构建一个Mat类型的查找表:

  Mat lookUpTable(1,256,CV_8U);
    uchar * p = lookUpTable.ptr();
    for(int i = 0; i <256; ++ i)
        p [i] = table [i];

  最后调用函数(我是咱们的输入图像,J是输出图像):

LUT(I,lookUpTable,J);

五、基本数据持久性和存储

  在后续的模型中会用到函数存储和读取数据。校准或机器学习等不少应用会在完成计算时将结果保存,方便下一次检索到他们。OpenCV提供XML/YAML持久化层能够完成这个任务。

  要将OpenCV数据或其余的数值数据写入文件,可使用FileStorage类中(如STL流的)C运算符的流,下面是一个建立保存数据的存储文件的示例:

#include "stdafx.h"
#include "opencv2/opencv.hpp"
using namespace cv;
int main(int, char** argv)
{
	// 建立流
	FileStorage fs("test.yml", FileStorage::WRITE);
	// Save an int
	int fps = 5;
	fs << "fps" << fps;
	// 建立mat文例
	Mat m1 = Mat::eye(2, 3, CV_32F);
	Mat m2 = Mat::ones(3, 2, CV_32F);
	Mat result = (m1 + 1).mul(m1 + 3);
	// write the result
	fs << "Result" << result;
	//释放文件
	fs.release();
	FileStorage fs2("test.yml", FileStorage::READ);
	Mat r;
	fs2["Result"] >> r;
	std::cout << r << std::endl;
	fs2.release();
	return 0;
}

  该程序首先保存一个.yml文件,而后将数据从.yml文件中读取出来,程序运行结果以下:

同时,在test.cpp的文件家中会产生一个test.yml文件,这个文件是由

FileStorage fs("test.yml", FileStorage::WRITE);

  产生的,打开YAML格式文件能够看到以下内容:

六、C接口于C++接口的混合使用方法

  下面是一个将C接口与C ++接口混合使用的示例:

#include "stdafx.h"
#include <iostream>

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

using namespace cv;  //  //新的C ++接口API位于此命名空间内。导入它。
using namespace std;
//! [head]

static void help(char* progName)
{
	cout << endl << progName
		<< " shows how to use cv::Mat and IplImages together (converting back and forth)." << endl
		<< "Also contains example for image read, splitting the planes, merging back and " << endl
		<< " color conversion, plus iterating through pixels. " << endl
		<< "Usage:" << endl
		<< progName << " [image-name Default: 111.jpg]" << endl << endl;
}

//! [start]
// 注释掉define只使用最新的C ++ API
#define DEMO_MIXED_API_USE

#ifdef DEMO_MIXED_API_USE
#  include <opencv2/highgui/highgui_c.h>
#  include <opencv2/imgcodecs/imgcodecs_c.h>
#endif

int main(int argc, char** argv)
{
	help(argv[0]);
	const char* imagename = argc > 1 ? argv[1] : "111.jpg";

#ifdef DEMO_MIXED_API_USE
	Ptr<IplImage> IplI(cvLoadImage(imagename));     // Ptr <T>是一个安全的引用计数指针类
	if (!IplI)
	{
		cerr << "Can not load image " << imagename << endl;
		return -1;
	}
	Mat I = cv::cvarrToMat(IplI); // 转换为新样式容器。只建立了标题。图像未复制。
#else
	Mat I = imread(imagename);        // 较新的cvLoadImage替代方案,MATLAB风格的函数
	if (I.empty())                   //与if(!I.data)相同
	{
		cerr << "Can not load image " << imagename << endl;
		return -1;
	}
#endif
	//! [start]

		//! [new]
	  //将图像转换为YUV颜色空间。输出图像将自动建立。
	Mat I_YUV;
	cvtColor(I, I_YUV, COLOR_BGR2YCrCb);

	vector<Mat> planes;     //使用STL的向量结构来存储多个Mat对象
	split(I_YUV, planes);  //将图像拆分为单独的颜色平面(YUV)
	//! [new]

#if 1 // change it to 0 if you want to see a blurred and noisy version of this processing
	//! [scanning]
  // Mat扫描
	//方法1.使用迭代器处理Y平面
	MatIterator_<uchar> it = planes[0].begin<uchar>(), it_end = planes[0].end<uchar>();
	for (; it != it_end; ++it)
	{
		double v = *it * 1.7 + rand() % 21 - 10;
		*it = saturate_cast<uchar>(v*v / 255);
	}

	for (int y = 0; y < I_YUV.rows; y++)
	{
		//方法2.使用预先存储的行指针处理第一个色度平面。
		uchar* Uptr = planes[1].ptr<uchar>(y);
		for (int x = 0; x < I_YUV.cols; x++)
		{
			Uptr[x] = saturate_cast<uchar>((Uptr[x] - 128) / 2 + 128);

			//方法3.使用单个元素访问处理第二色度平面
			uchar& Vxy = planes[2].at<uchar>(y, x);
			Vxy = saturate_cast<uchar>((Vxy - 128) / 2 + 128);
		}
	}
	//! [scanning]

#else
	//! [noisy]
	Mat noisyI(I.size(), CV_8U);          //建立指定大小和类型的矩阵

	//使用正态分布的随机值填充矩阵(数字误差关闭)。
	//还有randu()用于均匀分布的随机数生成
	randn(noisyI, Scalar::all(128), Scalar::all(20));
	//模糊噪声I,内核大小为3x3,两个sigma都设置为0.5
	GaussianBlur(noisyI, noisyI, Size(3, 3), 0.5, 0.5);
	const double brightness_gain = 0;
	const double contrast_gain = 1.7;
#ifdef DEMO_MIXED_API_USE
		//将新矩阵传递给仅适用于IplImage或CvMat的函数:
		//步骤1)转换标题(提示:不会复制数据)。
		//步骤2)调用函数(提示:传递指针不要忘记一元“&”以造成指针)
	IplImage cv_planes_0 = planes[0], cv_noise = noisyI;
	cvAddWeighted(&cv_planes_0, contrast_gain, &cv_noise, 1, -128 + brightness_gain, &cv_planes_0);
#else
	addWeighted(planes[0], contrast_gain, noisyI, 1, -128 + brightness_gain, planes[0]);
#endif
	const double color_scale = 0.5;
	// Mat :: convertTo()取代了cvConvertScale。
	//必须明确指定输出矩阵类型(咱们保持原样 -  planes [1] .type())
  //若是咱们在编译时知道数据类型(这里是“uchar”),那么cv :: convertScale的替代形式。
	//这个表达式不会建立任何临时数组(因此应该几乎和上面同样快)
	planes[2] = Mat_<uchar>(planes[2] * color_scale + 128 * (1 - color_scale));

	// Mat :: mul替换了cvMul()。一样,在简单表达式的状况下,不会建立临时数组。
	planes[0] = planes[0].mul(planes[0], 1. / 255);
	//! [noisy]
#endif


	//! [end]
	merge(planes, I_YUV);                 //如今合并结果
	cvtColor(I_YUV, I, COLOR_YCrCb2BGR);    //并生成输出RGB图像


	namedWindow("image with grain", WINDOW_AUTOSIZE);//用它来建立图像


#ifdef DEMO_MIXED_API_USE
  //这是为了证实I和IplI真正共享数据 - 上述结果
	//处理存储在I中,所以也存储在IplI中。
	cvShowImage("image with grain", IplI);
#else
	imshow("image with grain", I); //新的MATLAB样式函数显示
#endif
	//! [end]
	waitKey();

	// Tip:没有内存须要被释放
	//  全部的内存都会被 Vector<>, Mat and Ptr<> 释放
	return 0;
}

程序运行结果以下:

 

 七、使用OpenCV parallel_for_来并行化代码

一、并行化运行代码的前提条件

第一个前提条件是使用并行框架构建OpenCV。在OpenCV 3.2中,如下并行框架按此顺序可用:

  1. 英特尔线程构建模块(第三方库,应明确启用)
  2. C =并行C / C ++编程语言扩展(第三方库,应该明确启用)
  3. OpenMP(集成到编译器,应该明确启用)
  4. APPLE GCD(系统范围,自动使用(仅限APPLE))
  5. Windows RT并发(系统范围,自动使用(仅限Windows RT))
  6. Windows并发(运行时的一部分,自动使用(仅限Windows - MSVC ++> = 10))
  7. Pthreads(若是有的话)

如您所见,OpenCV库中可使用多个并行框架。一些并行库是第三方库,必须在CMake中明确构建和启用(例如TBB,C =),其余并行平台自动提供(例如APPLE GCD),但您应该能够访问并行框架直接或经过启用CMake中的选项并重建库。

第二个(弱)前提条件与您想要实现的任务更相关,由于并不是全部计算都适合/能够经过并行方式运行。为了保持简单,能够拆分为多个基本操做且没有内存依赖性(没有可能的竞争条件)的任务很容易并行化。计算机视觉处理一般易于并行化,由于大多数时候一个像素的处理不依赖于其余像素的状态。

二、目标

  编写一个程序来绘制一个Mandelbrot集,利用几乎全部可用的CPU负载。

   Mandelbrot集定义的命名是由数学家Adrien Douady向数学家Benoit Mandelbrot致敬。它在数学领域以外是着名的,由于图像表示是一类分形的一个例子,一个数学集合展现了在每一个尺度上显示的重复图案(甚至更多,Mandelbrot集合是自类似的,由于整个形状能够反复看到不一样的规模)。有关更深刻的介绍,您能够查看相应的Wikipedia文章在这里,咱们将介绍绘制Mandelbrot集的公式(来自上述维基百科文章)。

 

Mandelbrot集是c的值集 在二次映射迭代下0的轨道的复平面中

\[\left\{ {\begin{array}{*{20}{c}}
{{Z_0} = 0}\\
{{Z_{n + 1}} = Z_n^2 + c}
\end{array}} \right.\]

仍然有限。也就是说,复数c若是从z开始,则是Mandelbrot集的一部分00并重复应用迭代,z的绝对值ñ仍然有界然而大ň获得。这也能够表示为

\[\mathop {\lim }\limits_{n \to \infty } \sup \left| {{Z_{{\rm{n}} + 1}}} \right| \le 2\]

三、伪代码

   生成Mandelbrot集表示的简单算法称为“逃逸时间算法”对于渲染图像中的每一个像素,若是复数在有限或最大迭代次数下有界,咱们使用递归关系进行测试。不属于Mandelbrot集的像素将快速逃逸,而咱们假设在固定的最大迭代次数以后像素位于集合中。高迭代值将产生更详细的图像,但计算时间将相应增长。咱们使用“转义”所需的迭代次数来描绘图像中的像素值。

For each pixel (Px, Py) on the screen, do:
{
  x0 = scaled x coordinate of pixel (scaled to lie in the Mandelbrot X scale (-2, 1))
  y0 = scaled y coordinate of pixel (scaled to lie in the Mandelbrot Y scale (-1, 1))
  x = 0.0
  y = 0.0
  iteration = 0
  max_iteration = 1000
  while (x*x + y*y < 2*2  AND  iteration < max_iteration) {
    xtemp = x*x - y*y + x0
    y = 2*x*y + y0
    x = xtemp
    iteration = iteration + 1
  }
  color = palette[iteration]
  plot(Px, Py, color)

  为了在伪代码和理论之间创建联系,咱们有:  

\[\begin{array}{*{20}{c}}
{z = x + iy}\\
{{z^2} = {x^2} + i2xy - {y^2}}\\
{c = {x_0} + i{y_0}}
\end{array}\]

how_to_use_OpenCV_parallel_for_640px-Mandelset_hires.png

在这个图中,咱们记得复数的实部在x轴上,虚部在y轴上。若是咱们放大特定位置,您能够看到整个形状能够重复显示。

四、代码实现

 

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>

using namespace std;
using namespace cv;

namespace
{
	//! [mandelbrot-escape-time-algorithm]
	int mandelbrot(const complex<float> &z0, const int max)
	{
		complex<float> z = z0;
		for (int t = 0; t < max; t++)
		{
			if (z.real()*z.real() + z.imag()*z.imag() > 4.0f) return t;
			z = z * z + z0;
		}

		return max;
	}
	//! [mandelbrot-escape-time-algorithm]

	//! [mandelbrot-grayscale-value]
	int mandelbrotFormula(const complex<float> &z0, const int maxIter = 500) {
		int value = mandelbrot(z0, maxIter);
		if (maxIter - value == 0)
		{
			return 0;
		}

		return cvRound(sqrt(value / (float)maxIter) * 255);
	}
	//! [mandelbrot-grayscale-value]

	//! [mandelbrot-parallel]
	class ParallelMandelbrot : public ParallelLoopBody
	{
	public:
		ParallelMandelbrot(Mat &img, const float x1, const float y1, const float scaleX, const float scaleY)
			: m_img(img), m_x1(x1), m_y1(y1), m_scaleX(scaleX), m_scaleY(scaleY)
		{
		}

		virtual void operator ()(const Range& range) const CV_OVERRIDE
		{
			for (int r = range.start; r < range.end; r++)
			{
				int i = r / m_img.cols;
				int j = r % m_img.cols;

				float x0 = j / m_scaleX + m_x1;
				float y0 = i / m_scaleY + m_y1;

				complex<float> z0(x0, y0);
				uchar value = (uchar)mandelbrotFormula(z0);
				m_img.ptr<uchar>(i)[j] = value;
			}
		}

		ParallelMandelbrot& operator=(const ParallelMandelbrot &) {
			return *this;
		};

	private:
		Mat &m_img;
		float m_x1;
		float m_y1;
		float m_scaleX;
		float m_scaleY;
	};
	//! [mandelbrot-parallel]

	//! [mandelbrot-sequential]
	void sequentialMandelbrot(Mat &img, const float x1, const float y1, const float scaleX, const float scaleY)
	{
		for (int i = 0; i < img.rows; i++)
		{
			for (int j = 0; j < img.cols; j++)
			{
				float x0 = j / scaleX + x1;
				float y0 = i / scaleY + y1;

				complex<float> z0(x0, y0);
				uchar value = (uchar)mandelbrotFormula(z0);
				img.ptr<uchar>(i)[j] = value;
			}
		}
	}
	//! [mandelbrot-sequential]
}

int main()
{
	//! [mandelbrot-transformation]
	Mat mandelbrotImg(4800, 5400, CV_8U);
	float x1 = -2.1f, x2 = 0.6f;
	float y1 = -1.2f, y2 = 1.2f;
	float scaleX = mandelbrotImg.cols / (x2 - x1);
	float scaleY = mandelbrotImg.rows / (y2 - y1);
	//! [mandelbrot-transformation]

	double t1 = (double)getTickCount();

#ifdef CV_CXX11

	//! [mandelbrot-parallel-call-cxx11]
	parallel_for_(Range(0, mandelbrotImg.rows*mandelbrotImg.cols), [&](const Range& range) {
		for (int r = range.start; r < range.end; r++)
		{
			int i = r / mandelbrotImg.cols;
			int j = r % mandelbrotImg.cols;

			float x0 = j / scaleX + x1;
			float y0 = i / scaleY + y1;

			complex<float> z0(x0, y0);
			uchar value = (uchar)mandelbrotFormula(z0);
			mandelbrotImg.ptr<uchar>(i)[j] = value;
		}
	});
	//! [mandelbrot-parallel-call-cxx11]

#else

	//! [mandelbrot-parallel-call]
	ParallelMandelbrot parallelMandelbrot(mandelbrotImg, x1, y1, scaleX, scaleY);
	parallel_for_(Range(0, mandelbrotImg.rows*mandelbrotImg.cols), parallelMandelbrot);
	//! [mandelbrot-parallel-call]

#endif

	t1 = ((double)getTickCount() - t1) / getTickFrequency();
	cout << "Parallel Mandelbrot: " << t1 << " s" << endl;

	Mat mandelbrotImgSequential(4800, 5400, CV_8U);
	double t2 = (double)getTickCount();
	sequentialMandelbrot(mandelbrotImgSequential, x1, y1, scaleX, scaleY);
	t2 = ((double)getTickCount() - t2) / getTickFrequency();
	cout << "Sequential Mandelbrot: " << t2 << " s" << endl;
	cout << "Speed-up: " << t2 / t1 << " X" << endl;

	imwrite("Mandelbrot_parallel.png", mandelbrotImg);
	imwrite("Mandelbrot_sequential.png", mandelbrotImgSequential);

	return EXIT_SUCCESS;
}

  程序运行结果以下:

 

 

 

 

 

 

 

 

参考资料:

一、Mat - 基本图像容器

二、OpenCV矩阵的基本操做

三、MoreWindows博客目录(微软最有价值专家,原创技术文章152篇)

四、Open Source Computer Vision Library

五、opencv实例精讲