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實例精講

相關文章
相關標籤/搜索