主頁 >  其他 > OpenCV4函式合集

OpenCV4函式合集

2021-08-07 09:30:37 其他

第二章 資料載入、顯示與保存

2.1 影像存盤容器

2.1.1 Mat類介紹

Mat分為矩陣頭和指向存盤資料的矩陣指標兩部分,

代碼清單2-1 創建Mat類

cv::Mat a; //創建一個名為a的矩陣頭
a = cv::imread("test.jpd"); //向a中賦值影像資料,矩陣指標指向像素資料
cv::Mat b = a; //復制矩陣頭,并命名為b

代碼清單2-2 宣告一個指定型別的Mat類

cv::Mat A = Mat_<double>(3,3); //創建一個3*3的矩陣用于存放double型別資料

代碼清單2-3 通過OpenCV資料型別創建Mat類

cv::Mat a(640,480,CV_8UC3); //創建一個640*480的3通道矩陣用于存放彩色影像
cv::Mat a(3,3,CV_8UC1); //創建一個3*3的8位無符號整數的單通道矩陣
cv::Mat a(3,3,CV_8U); //創建單通道矩陣,c1標識可以省略

2.1.2 Mat類構造與賦值

1.Mat類的構造

代碼清單2-4 默認建構式使用方式

cv::Mat::Mat();

代碼清單2-5 利用矩陣尺寸和型別引數構造Mat類

cv::Mat::Mat(int rows,
            int cols,
            int type
            )
  • rows:構造矩陣的行數
  • cols:矩陣的列數
  • type:矩陣中存盤的資料型別

代碼清單2-6 用Size()結構構造Mat

cv::Mat(Size size(),
       int type
       )
  • size:二維陣列變數尺寸,通過Size(cols,rows)進行賦值
  • type:與代碼清單2-5中的引數一致

代碼清單2-7 用Size()結構構造Mat示例

cv::Mat a(Size(480,640),CV_8UC1); //構造一個行為640、列為480的單通道矩陣
cv::Mat b(Size(480,640),CV_32FC3); //構造一個行為640、列為480的3通道矩陣

代碼清單2-8 利用已有矩陣構造Mat類

cv::Mat::Mat(const Mat & m);
  • m:是已經構建完成的Mat類矩陣資料

提示:如果希望復制兩個一模一樣的Mat類而彼此之間不會受影響,那么可以使用m=a.clone()實作

代碼清單2-9 構造已有Mat類的子類

cv::Mat::Mat(const Mat & m,
            const Range & rowRange,
            const Range & rowRange = Rang::all()
            )
  • m:是已經構建完成的Mat類矩陣資料
  • rowRange:在已有矩陣中需要截取的行數范圍,是一個Rang變數,例如從第2行到第5行可以表示位Rang(2,5)
  • rowRange:在已有矩陣中需要截取的列數范圍,是一個Rang變數,例如從第2列到第5列可以表示位Rang(2,5),默認所有列都會截取,

代碼清單2-10 在原Mat中截取子Mat類

cv::Mat b(a, Rang(2,5), Rang(2,5)); //從a中截取部分資料構造b
cv::Mat c(a, Rang(2,5)); //默認最后一個引數構成c

2.2 影像的讀取與顯示

2.2.1 影像讀取函式 imread

imread()

empty()

2.2.2 影像視窗函式 namedWindow

namedWindow()

2.2.3 影像顯示函式 imshow

imshow()

cv::waitKey()

2.3 視頻加載與攝像頭呼叫

2.3.1 視頻資料的讀取

VideoCapture類建構式

isOpened()

“>>”

empty()

get()

代碼清單2-28 讀取視頻檔案

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

using namespace std;
using namespace cv;

int main()
{
	system("color F0");  //更改輸出界面顏色
	VideoCapture video("cup.mp4");
	if (video.isOpened())
	{
		cout << "視頻中影像的寬度=" << video.get(CAP_PROP_FRAME_WIDTH) << endl;
		cout << "視頻中影像的高度=" << video.get(CAP_PROP_FRAME_HEIGHT) << endl;
		cout << "視頻幀率=" << video.get(CAP_PROP_FPS) << endl;
		cout << "視頻的總幀數=" << video.get(CAP_PROP_FRAME_COUNT);
	}
	else
	{
		cout << "請確認視頻檔案名稱是否正確" << endl;
		return -1;
	}
	while (1)
	{
		Mat frame;
		video >> frame;
		if (frame.empty())
		{
			break;
		}
		imshow("video", frame);
		waitKey(1000 / video.get(CAP_PROP_FPS));
	}
	waitKey();
	return 0;
}

2.3.2 攝像頭的直接呼叫

VideoCapture類還可以呼叫攝像頭

2.4 資料保存

2.4.1 影像的保存

imwrite()

代碼清單2-32 保存影像

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

using	namespace std;
using	namespace cv;

void AlphaMat(Mat &mat)
{
	CV_Assert(mat.channels() == 4);
	for (int i = 0; i < mat.rows; ++i)
		{
			for (int j = 0; j < mat.cols; ++j)
			{
				Vec4b& bgra = mat.at<Vec4b>(i, j);
				bgra[0] = UCHAR_MAX;  // 藍色通道
				bgra[1] = saturate_cast<uchar>((float(mat.cols - j)) / ((float)mat.cols) * UCHAR_MAX);  // 綠色通道
				bgra[2] = saturate_cast<uchar>((float(mat.rows - i)) / ((float)mat.rows) * UCHAR_MAX);  // 紅色通道
				bgra[3] = saturate_cast<uchar>(0.5 * (bgra[1] + bgra[2]));  // Alpha通道
			}
		}
}
int main(int agrc, char** agrv)
{
	// Create mat with alpha channel
	Mat mat(480, 640, CV_8UC4);
	AlphaMat(mat);
	vector<int> compression_params;
	compression_params.push_back(IMWRITE_PNG_COMPRESSION);  //PNG格式影像壓縮標志
	compression_params.push_back(9);  //設定最高壓縮質量		
	bool result = imwrite("alpha.png", mat, compression_params);
	if (!result)
	{
		cout << "保存成PNG格式影像失敗" << endl;
		return -1;
	}
	cout << "保存成功" << endl;
	return 0;
}

2.4.2 視頻的保存

VideoWriter類建構式

isOpened()

get()

“<<” 或者 write()

release()

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

using namespace cv;
using namespace std;

int main()
{
	Mat img;
	VideoCapture video(0);  //使用某個攝像頭

	//讀取視頻
	//VideoCapture video;
	//video.open("cup.mp4");  

	if (!video.isOpened())  // 判斷是否呼叫成功
	{
		cout << "打開攝像頭失敗,請確實攝像頭是否安裝成功";
		return -1;
	}

	video >> img;  //獲取影像
	//檢測是否成功獲取影像
	if (img.empty())   //判斷有沒有讀取影像成功
	{
		cout << "沒有獲取到影像" << endl;
		return -1;
	}
	bool isColor = (img.type() == CV_8UC3);  //判斷相機(視頻)型別是否為彩色

	VideoWriter writer;
	int codec = VideoWriter::fourcc('M', 'J', 'P', 'G');  // 選擇編碼格式
	//OpenCV 4.0版本設定編碼格式
	//int codec = CV_FOURCC('M', 'J', 'P', 'G'); 

	double fps = 25.0;  //設定視頻幀率 
	string filename = "live.avi";  //保存的視頻檔案名稱
	writer.open(filename, codec, fps, img.size(), isColor);  //創建保存視頻檔案的視頻流

	if (!writer.isOpened())   //判斷視頻流是否創建成功
	{
		cout << "打開視頻檔案失敗,請確實是否為合法輸入" << endl;
		return -1;
	}

	while (1)
	{
		//檢測是否執行完畢
		if (!video.read(img))   //判斷能都繼續從攝像頭或者視頻檔案中讀出一幀影像
		{
			cout << "攝像頭斷開連接或者視頻讀取完成" << endl;
			break;
		}
		writer.write(img);  //把影像寫入視頻流
		//writer << img;
		imshow("Live", img);  //顯示影像
		char c = waitKey(50);
		if (c == 27)  //按ESC案件退出視頻保存
		{
			break;
		}
	}
	// 退出程式時刻自動關閉視頻流
	//video.release();
	//writer.release();	
	return 0;
}

2.4.3 保存和讀取XML和YMAL檔案

FileStorage類建構式

isOpened()

open()

“<<” 、 “>>”

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

using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
	system("color F0");  //修改運行程式背景和文字顏色
	//string fileName = "datas.xml";  //檔案的名稱
	string fileName = "datas.yaml";  //檔案的名稱
	//以寫入的模式打開檔案
	cv::FileStorage fwrite(fileName, cv::FileStorage::WRITE);
	
	//存入矩陣Mat型別的資料
	Mat mat = Mat::eye(3, 3, CV_8U);
	fwrite.write("mat", mat);  //使用write()函式寫入資料
	//存入浮點型資料,節點名稱為x
	float x = 100;
	fwrite << "x" << x;
	//存入字串型資料,節點名稱為str
	String str = "Learn OpenCV 4";
	fwrite << "str" << str;
	//存入陣列,節點名稱為number_array
	fwrite << "number_array" << "[" <<4<<5<<6<< "]";
	//存入多node節點資料,主名稱為multi_nodes
	fwrite << "multi_nodes" << "{" << "month" << 8 << "day" << 28 << "year"
		<< 2019 << "time" << "[" << 0 << 1 << 2 << 3 << "]" << "}";

	//關閉檔案
	fwrite.release();

	//以讀取的模式打開檔案
	cv::FileStorage fread(fileName, cv::FileStorage::READ);
	//判斷是否成功打開檔案
	if (!fread.isOpened())
	{
		cout << "打開檔案失敗,請確認檔案名稱是否正確!" << endl;
		return -1;
	}

	//讀取檔案中的資料
	float xRead;
	fread["x"] >> xRead;  //讀取浮點型資料
	cout << "x=" << xRead << endl;

	//讀取字串資料
	string strRead;
	fread["str"] >> strRead;
	cout << "str=" << strRead << endl;

	//讀取含多個資料的number_array節點
	FileNode fileNode = fread["number_array"];
	cout << "number_array=[";
	//回圈遍歷每個資料
	for (FileNodeIterator i = fileNode.begin(); i != fileNode.end(); i++)
	{
		float a;
		*i >> a;
		cout << a<<" ";
	}
	cout << "]" << endl;

	//讀取Mat型別資料
	Mat matRead;
	fread["mat="] >> matRead;
	cout << "mat=" << mat << endl;

	//讀取含有多個子節點的節點資料,不使用FileNode和迭代器進行讀取
	FileNode fileNode1 = fread["multi_nodes"];
	int month = (int)fileNode1["month"];
	int day = (int)fileNode1["day"];
	int year = (int)fileNode1["year"];
	cout << "multi_nodes:" << endl 
		<< "  month=" << month << "  day=" << day << "  year=" << year;
	cout << "  time=[";
	for (int i = 0; i < 4; i++)
	{
		int a = (int)fileNode1["time"][i];
		cout << a << " ";
	}
	cout << "]" << endl;
	
	//關閉檔案
	fread.release();
	return 0;
}

第三章 影像基本操作

3.1 影像顏色空間

3.1.1 顏色模型與轉換

  1. RGB顏色模型
  2. YUV顏色模型
  3. HSV顏色模型
  4. Lab顏色模型
  5. GRAY顏色模型

6.不同顏色模型間的互想轉換

cvtColor()函式用于將影像從一個顏色模型轉換為另一個顏色模型

void cv::cvtColor(InputArray src,
                 OutputArray dst,
                 int code,
                 int dstCn = 0
                 )
  • src:待轉換顏色模型原始影像
  • dst:轉換顏色模型后的目標影像
  • code:顏色空間轉換的標志,如由RGB空間到HSV空間
  • dstCn:目標影像中的通道數

代碼清單3-2 影像顏色模型相互轉換

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
    Mat img = imread("E:/BaiduNetdiskDownload/data/lena.png");
    if (img.empty())
    {
        cout << "請確認影像檔案名稱師范正確" << endl;
        return -1;
    }
    Mat gray, HSV, YUV, Lab, img32;
    img.convertTo(img32, CV_32F, 1.0 / 255); //將CV_8U型別轉換成CV_32F型別
    //img32.convertTo(img,CV_8U,255); //將CV_32F型別轉換成CV_8U型別
    cvtColor(img32, HSV, COLOR_BGR2HSV);
    cvtColor(img32, YUV, COLOR_BGR2YUV);
    cvtColor(img32, Lab, COLOR_BGR2Lab);
    cvtColor(img32, gray, COLOR_BGR2GRAY);
    imshow("原圖", img32);
    imshow("HSV", HSV);
    imshow("YUV", YUV);
    imshow("Lab", Lab);
    imshow("gray", gray);
    waitKey(0);
    return 0;
}

convertTo()

3.1.2 多通道分離與合并

split()

merge()

代碼清單3-6 實作影像分離與合并

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat HSV;
	cvtColor(img, HSV, COLOR_RGB2HSV);
	Mat imgs0, imgs1, imgs2;  //用于存放陣列型別的結果
	Mat imgv0, imgv1, imgv2;  //用于存放vector型別的結果
	Mat result0, result1, result2;  //多通道合并的結果

									//輸入陣列引數的多通道分離與合并
	Mat imgs[3];
	split(img, imgs);
	imgs0 = imgs[0];
	imgs1 = imgs[1];
	imgs2 = imgs[2];
	imshow("RGB-B通道", imgs0);  //顯示分離后B通道的像素值
	imshow("RGB-G通道", imgs1);  //顯示分離后G通道的像素值
	imshow("RGB-R通道", imgs2);  //顯示分離后R通道的像素值
	imgs[2] = img;  //將陣列中的影像通道數變成不統一
	merge(imgs, 3, result0);  //合并影像
							  //imshow("result0", result0);  //imshow最多顯示4個通道,因此結果在Image Watch中查看
	Mat zero = cv::Mat::zeros(img.rows, img.cols, CV_8UC1);
	imgs[0] = zero;
	imgs[2] = zero;
	merge(imgs, 3, result1);  //用于還原G通道的真實情況,合并結果為綠色
	imshow("result1", result1);  //顯示合并結果

								 //輸入vector引數的多通道分離與合并
	vector<Mat> imgv;
	split(HSV, imgv);
	imgv0 = imgv.at(0);
	imgv1 = imgv.at(1);
	imgv2 = imgv.at(2);
	imshow("HSV-H通道", imgv0);  //顯示分離后H通道的像素值
	imshow("HSV-S通道", imgv1);  //顯示分離后S通道的像素值
	imshow("HSV-V通道", imgv2);  //顯示分離后V通道的像素值
	imgv.push_back(HSV);  //將vector中的影像通道數變成不統一
	merge(imgv, result2);  //合并影像
						   //imshow("result2", result2);  /imshow最多顯示4個通道,因此結果在Image Watch中查看
	waitKey(0);
	return 0;
}

3.2 影像像素操作處理

3.2.1 影像像素統計

1.尋找影像像素最大值與最小值

minMaxLoc()

資料型別 Point

CU::Mat::reshape()

代碼清單3-9 尋找矩陣中的最值

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	system("color F0");  //更改輸出界面顏色
	float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };
	Mat img = Mat(3, 4, CV_32FC1, a);  //單通道矩陣
	Mat imgs = Mat(2, 3, CV_32FC2, a);  //多通道矩陣
	double minVal, maxVal;  //用于存放矩陣中的最大值和最小值
	Point minIdx, maxIdx;  用于存放矩陣中的最大值和最小值在矩陣中的位置

	/*尋找單通道矩陣中的最值*/
	minMaxLoc(img, &minVal, &maxVal, &minIdx, &maxIdx);
	cout << "img中最大值是:" << maxVal << "  " << "在矩陣中的位置:" << maxIdx << endl;
	cout << "img中最小值是:" << minVal << "  " << "在矩陣中的位置:" << minIdx << endl;

	/*尋找多通道矩陣中的最值*/
	Mat imgs_re = imgs.reshape(1, 4);  //將多通道矩陣變成單通道矩陣
	minMaxLoc(imgs_re, &minVal, &maxVal, &minIdx, &maxIdx);
	cout << "imgs中最大值是:" << maxVal << "  " << "在矩陣中的位置:" << maxIdx << endl;
	cout << "imgs中最小值是:" << minVal << "  " << "在矩陣中的位置:" << minIdx << endl;
	return 0;
}

2.計算影像的平均值和標準差

meanStdDev() 函式用于同時計算平均值和標準差

mean() 計算平均值

cv::Scalar 型別

代碼清單3-12 計算矩陣平均值和標準差

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	system("color F0");  //更改輸出界面顏色
	float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };
	Mat img = Mat(3, 4, CV_32FC1, a);  //單通道矩陣
	Mat imgs = Mat(2, 3, CV_32FC2, a);  //多通道矩陣

	cout << "/* 用meanStdDev同時求取影像的均值和標準差 */" << endl;
	Scalar myMean;
	myMean = mean(imgs);
	cout << "imgs均值=" << myMean << endl;
	cout << "imgs第一個通道的均值=" << myMean[0] << "    "
		<< "imgs第二個通道的均值=" << myMean[1] << endl << endl;

	cout << "/* 用meanStdDev同時求取影像的均值和標準差 */" << endl;
	Mat myMeanMat, myStddevMat;

	meanStdDev(img, myMeanMat, myStddevMat);
	cout << "img均值=" << myMeanMat << "    " << endl;
	cout << "img標準差=" << myStddevMat << endl << endl;
	meanStdDev(imgs, myMeanMat, myStddevMat);
	cout << "imgs均值=" << myMeanMat << "    " << endl << endl;
	cout << "imgs標準差=" << myStddevMat << endl;
	return 0;
}

2.2.2 兩影像間的像素操作

1.兩幅影像的比較運算

max()

min()

代碼清單3-14 兩個矩陣或影像進行比較運算

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	float a[12] = { 1, 2, 3.3, 4, 5, 9, 5, 7, 8.2, 9, 10, 2 };
	float b[12] = { 1, 2.2, 3, 1, 3, 10, 6, 7, 8, 9.3, 10, 1 };
	Mat imga = Mat(3, 4, CV_32FC1, a);
	Mat imgb = Mat(3, 4, CV_32FC1, b);
	Mat imgas = Mat(2, 3, CV_32FC2, a);
	Mat imgbs = Mat(2, 3, CV_32FC2, b);

	//對兩個單通道矩陣進行比較運算
	Mat myMax, myMin;
	max(imga, imgb, myMax);
	min(imga, imgb, myMin);

	//對兩個多通道矩陣進行比較運算
	Mat myMaxs, myMins;
	max(imgas, imgbs, myMaxs);
	min(imgas, imgbs, myMins);

	//對兩張彩色影像進行比較運算
	Mat img0 = imread("len.png");
	Mat img1 = imread("noobcv.jpg");

	if (img0.empty() || img1.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat comMin, comMax;
	max(img0, img1, comMax);
	min(img0, img1, comMin);
	imshow("comMin", comMin);
	imshow("comMax", comMax);

	//與掩模進行比較運算
	Mat src1 = Mat::zeros(Size(512, 512), CV_8UC3);
	Rect rect(100, 100, 300, 300);
	src1(rect) = Scalar(255, 255, 255);  //生成一個低通300*300的掩模
	Mat comsrc1, comsrc2;
	min(img0, src1, comsrc1);
	imshow("comsrc1", comsrc1);

	Mat src2 = Mat(512, 512, CV_8UC3, Scalar(0, 0, 255));  //生成一個顯示紅色通道的低通掩模
	min(img0, src2, comsrc2);
	imshow("comsrc2", comsrc2);

	//對兩張灰度影像進行比較運算
	Mat img0G, img1G, comMinG, comMaxG;
	cvtColor(img0, img0G, COLOR_BGR2GRAY);
	cvtColor(img1, img1G, COLOR_BGR2GRAY);
	max(img0G, img1G, comMaxG);
	min(img0G, img1G, comMinG);
	imshow("comMinG", comMinG);
	imshow("comMaxG", comMaxG);
	waitKey(0);
	return 0;
}

2.兩幅影像的邏輯運算

biwise_and()

biwise_or()

biwise_xor()

biwise_not()

代碼清單3-16 兩個黑白影像像素邏輯運算

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	//創建兩個黑白影像
	Mat img0 = Mat::zeros(200, 200, CV_8UC1);
	Mat img1 = Mat::zeros(200, 200, CV_8UC1);
	Rect rect0(50, 50, 100, 100);
	img0(rect0) = Scalar(255);
	Rect rect1(100, 100, 100, 100);
	img1(rect1) = Scalar(255);
	imshow("img0", img0);
	imshow("img1", img1);

	//進行邏輯運算
	Mat myAnd, myOr, myXor, myNot, imgNot;
	bitwise_not(img0, myNot);
	bitwise_and(img0, img1, myAnd);
	bitwise_or(img0, img1, myOr);
	bitwise_xor(img0, img1, myXor);
	bitwise_not(img, imgNot);
	imshow("myAnd", myAnd);
	imshow("myOr", myOr);
	imshow("myXor", myXor);
	imshow("myNot", myNot);
	imshow("img", img);
	imshow("imgNot", imgNot);
	waitKey(0);
	return 0;
}

3.2.3 影像二值化

threshold()

adaptiveThreshold()

代碼清單3-19 影像二值化

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	Mat img_B, img_B_V, gray_B, gray_B_V, gray_T, gray_T_V, gray_TRUNC;

	//彩色影像二值化
	threshold(img, img_B, 125, 255, THRESH_BINARY);
	threshold(img, img_B_V, 125, 255, THRESH_BINARY_INV);
	imshow("img_B", img_B);
	imshow("img_B_V", img_B_V);

	//灰度圖BINARY二值化
	threshold(gray, gray_B, 125, 255, THRESH_BINARY);
	threshold(gray, gray_B_V, 125, 255, THRESH_BINARY_INV);
	imshow("gray_B", gray_B);
	imshow("gray_B_V", gray_B_V);

	//灰度影像TOZERO變換
	threshold(gray, gray_T, 125, 255, THRESH_TOZERO);
	threshold(gray, gray_T_V, 125, 255, THRESH_TOZERO_INV);
	imshow("gray_T", gray_T);
	imshow("gray_T_V", gray_T_V);

	//灰度影像TRUNC變換
	threshold(gray, gray_TRUNC, 125, 255, THRESH_TRUNC);
	imshow("gray_TRUNC", gray_TRUNC);

	//灰度影像大津法和三角形法二值化
	Mat img_Thr = imread("threshold.png", IMREAD_GRAYSCALE);
	Mat img_Thr_O, img_Thr_T;
	threshold(img_Thr, img_Thr_O, 100, 255, THRESH_BINARY | THRESH_OTSU);
	threshold(img_Thr, img_Thr_T, 125, 255, THRESH_BINARY | THRESH_TRIANGLE);
	imshow("img_Thr", img_Thr);
	imshow("img_Thr_O", img_Thr_O);
	imshow("img_Thr_T", img_Thr_T);

	//灰度影像自適應二值化
	Mat adaptive_mean, adaptive_gauss;
	adaptiveThreshold(img_Thr, adaptive_mean, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 55, 0);
	adaptiveThreshold(img_Thr, adaptive_gauss, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 55, 0);

	imshow("adaptive_mean", adaptive_mean);
	imshow("adaptive_gauss", adaptive_gauss);
	waitKey(0);
	return 0;
}

3.2.4 LUT

LUT()

代碼清單3-21 對影像進行查找表映射

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

using namespace std;
using namespace cv;

int main()
{
	//LUT查找表第一層
	uchar lutFirst[256];
	for (int i = 0; i<256; i++)
	{
		if (i <= 100)
			lutFirst[i] = 0;
		if (i > 100 && i <= 200)
			lutFirst[i] = 100;
		if (i > 200)
			lutFirst[i] = 255;
	}
	Mat lutOne(1, 256, CV_8UC1, lutFirst);

	//LUT查找表第二層
	uchar lutSecond[256];
	for (int i = 0; i<256; i++)
	{
		if (i <= 100)
			lutSecond[i] = 0;
		if (i > 100 && i <= 150)
			lutSecond[i] = 100;
		if (i > 150 && i <= 200)
			lutSecond[i] = 150;
		if (i > 200)
			lutSecond[i] = 255;
	}
	Mat lutTwo(1, 256, CV_8UC1, lutSecond);

	//LUT查找表第三層
	uchar lutThird[256];
	for (int i = 0; i<256; i++)
	{
		if (i <= 100)
			lutThird[i] = 100;
		if (i > 100 && i <= 200)
			lutThird[i] = 200;
		if (i > 200)
			lutThird[i] = 255;
	}
	Mat lutThree(1, 256, CV_8UC1, lutThird);

	//擁有三通道的LUT查找表矩陣
	vector<Mat> mergeMats;
	mergeMats.push_back(lutOne);
	mergeMats.push_back(lutTwo);
	mergeMats.push_back(lutThree);
	Mat LutTree;
	merge(mergeMats, LutTree);

	//計算影像的查找表
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	Mat gray, out0, out1, out2;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	LUT(gray, lutOne, out0);
	LUT(img, lutOne, out1);
	LUT(img, LutTree, out2);
	imshow("out0", out0);
	imshow("out1", out1);
	imshow("out2", out2);
	waitKey(0);
	return 0;
}

3.3 影像變換

3.3.1 影像連接

vconcat()

hconcat()

代碼清單3-26 影像拼接

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

using namespace std;
using namespace cv;

int main()
{
	//矩陣陣列的橫豎連接
	Mat matArray[] = { Mat(1, 2, CV_32FC1, cv::Scalar(1)),
		Mat(1, 2, CV_32FC1, cv::Scalar(2)) };
	Mat vout, hout;
	vconcat(matArray, 2, vout);
	cout << "影像陣列豎向連接:" << endl << vout << endl;
	hconcat(matArray, 2, hout);
	cout << "影像陣列橫向連接:" << endl << hout << endl;

	//矩陣的橫豎拼接
	Mat A = (cv::Mat_<float>(2, 2) << 1, 7, 2, 8);
	Mat B = (cv::Mat_<float>(2, 2) << 4, 10, 5, 11);
	Mat vC, hC;
	vconcat(A, B, vC);
	cout << "多個影像豎向連接:" << endl << vC << endl;
	hconcat(A, B, hC);
	cout << "多個影像橫向連接:" << endl << hC << endl;

	//讀取4個子影像,00表示左上角、01表示右上角、10表示左下角、11表示右下角
	Mat img00 = imread("lena00.png");
	Mat img01 = imread("lena01.png");
	Mat img10 = imread("lena10.png");
	Mat img11 = imread("lena11.png");
	if (img00.empty() || img01.empty() || img10.empty() || img11.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	//顯示4個子影像
	imshow("img00", img00);
	imshow("img01", img01);
	imshow("img10", img10);
	imshow("img11", img11);

	//影像連接
	Mat img, img0, img1;
	//影像橫向連接
	hconcat(img00, img01, img0);
	hconcat(img10, img11, img1);
	//橫向連接結果再進行豎向連接
	vconcat(img0, img1, img);

	//顯示連接影像的結果
	imshow("img0", img0);
	imshow("img1", img1);
	imshow("img", img);
	waitKey(0);
	return 0;
}

3.3.2 影像尺寸變換

resize()

代碼清單3-28 影像縮放

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

using namespace std;
using namespace cv;

int main()
{
	Mat gray = imread("lena.png", IMREAD_GRAYSCALE);
	if (gray.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	Mat smallImg, bigImg0, bigImg1, bigImg2;
	resize(gray, smallImg, Size(15, 15), 0, 0, INTER_AREA);  //先將影像縮小
	resize(smallImg, bigImg0, Size(30, 30), 0, 0, INTER_NEAREST);  //最近鄰差值
	resize(smallImg, bigImg1, Size(30, 30), 0, 0, INTER_LINEAR);  //雙線性差值
	resize(smallImg, bigImg2, Size(30, 30), 0, 0, INTER_CUBIC);  //雙三次差值
	namedWindow("smallImg", WINDOW_NORMAL);  //影像尺寸太小,一定要設定可以調節視窗大小標志
	imshow("smallImg", smallImg);
	namedWindow("bigImg0", WINDOW_NORMAL);
	imshow("bigImg0", bigImg0);
	namedWindow("bigImg1", WINDOW_NORMAL);
	imshow("bigImg1", bigImg1);
	namedWindow("bigImg2", WINDOW_NORMAL);
	imshow("bigImg2", bigImg2);
	waitKey(0);
	return 0;
}

3.3.3 影像翻轉變換

flip()

代碼清單3-29 影像翻轉

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

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	Mat img_x, img_y, img_xy;
	flip(img, img_x, 0);  //沿x軸對稱
	flip(img, img_y, 1);  //沿y軸對稱
	flip(img, img_xy, -1);  //先x軸對稱,再y軸對稱
	imshow("img", img);
	imshow("img_x", img_x);
	imshow("img_y", img_y);
	imshow("img_xy", img_xy);
	waitKey(0);
	return 0;
}

3.3.4 影像放射變換

getRotationMatrix2D()

warpAffine()

getAffineTransform()

代碼清單3-34 影像旋轉與放射變換

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	Mat rotation0, rotation1, img_warp0, img_warp1;
	double angle = 30;  //設定影像旋轉的角度
	Size dst_size(img.rows, img.cols);  //設定輸出影像的尺寸
	Point2f center(img.rows / 2.0, img.cols / 2.0);  //設定影像的旋轉中心
	rotation0 = getRotationMatrix2D(center, angle, 1);  //計算放射變換矩陣
	warpAffine(img, img_warp0, rotation0, dst_size);  //進行仿射變換
	imshow("img_warp0", img_warp0);
	//根據定義的三個點進行仿射變換
	Point2f src_points[3];
	Point2f dst_points[3];
	src_points[0] = Point2f(0, 0);  //原始影像中的三個點
	src_points[1] = Point2f(0, (float)(img.cols - 1));
	src_points[2] = Point2f((float)(img.rows - 1), (float)(img.cols - 1));
	dst_points[0] = Point2f((float)(img.rows)*0.11, (float)(img.cols)*0.20);  //放射變換后影像中的三個點
	dst_points[1] = Point2f((float)(img.rows)*0.15, (float)(img.cols)*0.70);
	dst_points[2] = Point2f((float)(img.rows)*0.81, (float)(img.cols)*0.85);
	rotation1 = getAffineTransform(src_points, dst_points);  //根據對應點求取仿射變換矩陣
	warpAffine(img, img_warp1, rotation1, dst_size);  //進行仿射變換
	imshow("img_warp1", img_warp1);
	waitKey(0);
	return 0;
}

3.3.5 影像透視變換

getPerspectiveTransform()

warpPerspective()

代碼清單3-37 二維碼影像透視變換

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("noobcvqr.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	Point2f src_points[4];
	Point2f dst_points[4];
	//通過Image Watch查看的二維碼四個角點坐標
	src_points[0] = Point2f(94.0, 374.0);
	src_points[1] = Point2f(507.0, 380.0);
	src_points[2] = Point2f(1.0, 623.0);
	src_points[3] = Point2f(627.0, 627.0);
	//期望透視變換后二維碼四個角點的坐標
	dst_points[0] = Point2f(0.0, 0.0);
	dst_points[1] = Point2f(627.0, 0.0);
	dst_points[2] = Point2f(0.0, 627.0);
	dst_points[3] = Point2f(627.0, 627.0);
	Mat rotation, img_warp;
	rotation = getPerspectiveTransform(src_points, dst_points);  //計算透視變換矩陣
	warpPerspective(img, img_warp, rotation, img.size());  //透視變換投影
	imshow("img", img);
	imshow("img_warp", img_warp);
	waitKey(0);
	return 0;
}

3.3.6 極坐標變換

warpPolar()

代碼清單3-39 影像極坐標變換

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

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("dial.png");
	if (!img.data)
	{
		cout << "請檢查影像檔案名稱是否輸入正確" << endl;
		return -1;
	}

	Mat img1, img2;
	Point2f center = Point2f(img.cols / 2, img.rows/2);  //極坐標在影像中的原點
	//正極坐標變換
	warpPolar(img, img1, Size(300,600), center, center.x, INTER_LINEAR + WARP_POLAR_LINEAR);
	//逆極坐標變換
	warpPolar(img1, img2, Size(img.rows,img.cols), center, center.x, INTER_LINEAR + WARP_POLAR_LINEAR + WARP_INVERSE_MAP);

	imshow("原表盤圖", img);
	imshow("表盤極坐標變換結果", img1); 
	imshow("逆變換結果", img2);
	waitKey(0);
	return 0;
}

3.4 在影像上繪制幾何圖形

3.4.1 繪制圓形

circle()

3.4.2 繪制直線

line()

3.4.3 繪制橢圓

ellipse()

ellipse2Poly()

3.4.4 繪制多邊形

rectangle()

fillPoly()

3.4.5 文字生成

putText()

代碼清單3-47 繪制基本幾何圖形

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = Mat::zeros(Size(512, 512), CV_8UC3);  //生成一個黑色影像用于繪制幾何圖形
	//繪制圓形
	circle(img, Point(50, 50), 25, Scalar(255, 255, 255), -1);  //繪制一個實心圓
	circle(img, Point(100, 50), 20, Scalar(255, 255, 255), 4);  //繪制一個空心圓
	//繪制直線
	line(img, Point(100, 100), Point(200, 100), Scalar(255, 255, 255), 2, LINE_4,0);  //繪制一條直線
	//繪制橢圓
	ellipse(img, Point(300, 255), Size(100, 70), 0, 0, 100, Scalar(255, 255, 255), -1);  //繪制實心橢圓的一部分
	ellipse(img, RotatedRect(Point2f(150, 100), Size2f(30, 20), 0), Scalar(0, 0, 255), 2);  //繪制一個空心橢圓
	vector<Point> points;
	ellipse2Poly(Point(200, 400), Size(100, 70),0,0,360,2,points);  //用一些點來近似一個橢圓
	for (int i = 0; i < points.size()-1; i++)  //用直線把這個橢圓畫出來
	{
		if (i==points.size()-1)
		{
			line(img, points[0], points[i], Scalar(255, 255, 255), 2);  //橢圓中后于一個點與第一個點連線
			break;
		}
		line(img, points[i], points[i+1], Scalar(255, 255, 255), 2);  //當前點與后一個點連線
	}
	//繪制矩形
	rectangle(img, Point(50, 400), Point(100, 450), Scalar(125, 125, 125), -1);  
	rectangle(img, Rect(400,450,60,50), Scalar(0, 125, 125), 2);
	//繪制多邊形
	Point pp[2][6];
	pp[0][0] = Point(72, 200);
	pp[0][1] = Point(142, 204);
	pp[0][2] = Point(226, 263);
	pp[0][3] = Point(172, 310);
	pp[0][4] = Point(117, 319);
	pp[0][5] = Point(15, 260);
	pp[1][0] = Point(359, 339);
	pp[1][1] = Point(447, 351);
	pp[1][2] = Point(504, 349);
	pp[1][3] = Point(484, 433);
	pp[1][4] = Point(418, 449);
	pp[1][5] = Point(354, 402);
	Point pp2[5];
	pp2[0] = Point(350, 83);
	pp2[1] = Point(463, 90);
	pp2[2] = Point(500, 171);
	pp2[3] = Point(421, 194);
	pp2[4] = Point(338, 141);
	const Point* pts[3] = { pp[0],pp[1],pp2 };  //pts變數的生成
	int npts[] = { 6,6,5 };  //頂點個數陣列的生成
	fillPoly(img, pts, npts, 3, Scalar(125, 125, 125),8);  //繪制3個多邊形
	//生成文字
	putText(img, "Learn OpenCV 4",Point(100, 400), 2, 1, Scalar(255, 255, 255));
	imshow("", img);
	waitKey(0);
	return 0;
}

3.5 感興趣區域

Rect資料結構和Rang資料結構

copyTo()

代碼清單3-50 截圖、深淺拷貝驗證程式

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("lena.png");
	Mat noobcv = imread("noobcv.jpg");
	if (img.empty() || noobcv.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat ROI1, ROI2, ROI2_copy, mask, img2, img_copy, img_copy2;
	resize(noobcv, mask, Size(200, 200));
	img2 = img;  //淺拷貝
				 //深拷貝的兩種方式
	img.copyTo(img_copy2);
	copyTo(img, img_copy, img);
	//兩種在圖中截取ROI區域的方式
	Rect rect(206, 206, 200, 200);  //定義ROI區域
	ROI1 = img(rect);  //截圖
	ROI2 = img(Range(300, 500), Range(300, 500));  //第二種截圖方式
	img(Range(300, 500), Range(300, 500)).copyTo(ROI2_copy);  //深拷貝
	mask.copyTo(ROI1);  //在影像中加入部分影像
	imshow("加入noobcv后影像", img);
	imshow("ROI對ROI2的影響", ROI2);
	imshow("深拷貝的ROI2_copy", ROI2_copy);
	circle(img, Point(300, 300), 20, Scalar(0, 0, 255), -1);  //繪制一個圓形
	imshow("淺拷貝的img2", img2);
	imshow("深拷貝的img_copy", img_copy);
	imshow("深拷貝的img_copy2", img_copy2);
	imshow("畫圓對ROI1的影響", ROI1);
	waitKey(0);
	return 0;
}

3.6 影像“金字塔”

3.6.1 高斯“金字塔”

pyrDown()

3.6.2 高斯“金字塔”

pyrUp()

代碼清單3-53 構建 高斯“金字塔” 和 高斯“金字塔”

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	vector<Mat> Gauss, Lap;  //高斯金字塔和拉普拉斯金字塔
	int level = 3;  //高斯金字塔下采樣次數
	Gauss.push_back(img);  //將原圖作為高斯金字塔的第0層
						   //構建高斯金字塔
	for (int i = 0; i < level; i++)
	{
		Mat gauss;
		pyrDown(Gauss[i], gauss);  //下采樣
		Gauss.push_back(gauss);
	}
	//構建拉普拉斯金字塔
	for (int i = Gauss.size() - 1; i > 0; i--)
	{
		Mat lap, upGauss;
		if (i == Gauss.size() - 1)  //如果是高斯金字塔中的最上面一層影像
		{
			Mat down;
			pyrDown(Gauss[i], down);  //上采樣
			pyrUp(down, upGauss);
			lap = Gauss[i] - upGauss;
			Lap.push_back(lap);
		}
		pyrUp(Gauss[i], upGauss);
		lap = Gauss[i - 1] - upGauss;
		Lap.push_back(lap);
	}
	//查看兩個金字塔中的影像
	for (int i = 0; i < Gauss.size(); i++)
	{
		string name = to_string(i);
		imshow("G" + name, Gauss[i]);
		imshow("L" + name, Lap[i]);
	}
	waitKey(0);
	return 0;
}

3.7 視窗互動操作

3.7.1 影像視窗滑動條

createTrackbar()

代碼清單3-55 在影像中創建滑動條改變影像亮度

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

using namespace std;
using namespace cv;

//為了能在被調函式中使用,所以設定成全域的
int value;
void callBack(int, void*);  //滑動潭訓呼函式
Mat img1, img2;

int main()
{
	img1 = imread("lena.png");
	if (!img1.data)
	{
		cout << "請確認是否輸入正確的影像檔案" << endl;
		return -1;
	}
	namedWindow("滑動條改變影像亮度");
	imshow("滑動條改變影像亮度", img1);
	value = 100;  //滑動條創建時的初值
				  //創建滑動條
	createTrackbar("亮度值百分比", "滑動條改變影像亮度", &value, 600, callBack, 0);
	waitKey();
}

static void callBack(int, void*)
{
	float a = value / 100.0;
	img2 = img1 * a;
	imshow("滑動條改變影像亮度", img2);
}

3.7.2 滑鼠相應

setMouseCallback()

MouseCallback型別

代碼清單3-58 繪制滑鼠移動軌跡

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

using namespace std;
using namespace cv;

Mat img, imgPoint; //全域的影像
Point prePoint; //前一時刻滑鼠的坐標,用于繪制直線
void mouse(int event, int x, int y, int flags, void*);

int main()
{
	img = imread("lena.png");
	if (!img.data)
		{
			cout << "請確認輸入影像名稱是否正確! " << endl;
			return -1;
		}
	img.copyTo(imgPoint);
	imshow("影像視窗 1", img);
	imshow("影像視窗 2", imgPoint);
	setMouseCallback("影像視窗 1", mouse, 0); //滑鼠影響
	waitKey(0);
	return 0;
	}

void mouse(int event, int x, int y, int flags, void*)
{
	if (event == EVENT_RBUTTONDOWN) //單擊右鍵
	{
		cout << "點擊滑鼠左鍵才可以繪制軌跡" << endl;
	}
	if (event == EVENT_LBUTTONDOWN) //單擊左鍵,輸出坐標
	{
		prePoint = Point(x, y);
		cout << "軌跡起始坐標" << prePoint << endl;
	}
	if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) //滑鼠按住左鍵移動第 3 章 影像基本操作
	{
	 //通過改變影像像素顯示滑鼠移動軌跡
		imgPoint.at<Vec3b>(y, x) = Vec3b(0, 0, 255);
		imgPoint.at<Vec3b>(y, x - 1) = Vec3b(0, 0, 255);
		imgPoint.at<Vec3b>(y, x + 1) = Vec3b(0, 0, 255);
		imgPoint.at<Vec3b>(y + 1, x) = Vec3b(0, 0, 255);
		imgPoint.at<Vec3b>(y + 1, x) = Vec3b(0, 0, 255);
		imshow("影像視窗 2", imgPoint);
		
	    //通過繪制直線顯示滑鼠移動軌跡
		Point pt(x, y);
		line(img, prePoint, pt, Scalar(0, 0, 255), 2, 5, 0);
		prePoint = pt;
		imshow("影像視窗 1", img);
	}
}

第四章 影像直方圖與模板匹配

4.1 影像直方圖的繪制

calcHist()

cvRound()

代碼清單4-2 繪制影像直方圖

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("apple.jpg");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	//設定提取直方圖的相關變數
	Mat hist;  //用于存放直方圖計算結果
	const int channels[1] = { 0 };  //通道索引
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };  //像素灰度值范圍
	const int bins[1] = { 256 };  //直方圖的維度,其實就是像素灰度值的最大值
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);  //計算影像直方圖
																//準備繪制直方圖
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(hist.at<float>(i - 1) / 15)),
			Scalar(255, 255, 255), -1);
	}
	namedWindow("histImage", WINDOW_AUTOSIZE);
	imshow("histImage", histImage);
	imshow("gray", gray);
	waitKey(0);
	return 0;
}

4.2 直方圖操作

4.2.1 直方圖歸一化

normalize()

代碼清單4-4 直方圖歸一化操作

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

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	vector<double> positiveData = { 2.0, 8.0, 10.0 };
	vector<double> normalized_L1, normalized_L2, normalized_Inf, normalized_L2SQR;
	//測驗不同歸一化方法
	normalize(positiveData, normalized_L1, 1.0, 0.0, NORM_L1);  //絕對值求和歸一化
	cout << "normalized_L1=[" << normalized_L1[0] << ", "
		<< normalized_L1[1] << ", " << normalized_L1[2] << "]" << endl;
	normalize(positiveData, normalized_L2, 1.0, 0.0, NORM_L2);  //模長歸一化
	cout << "normalized_L2=[" << normalized_L2[0] << ", "
		<< normalized_L2[1] << ", " << normalized_L2[2] << "]" << endl;
	normalize(positiveData, normalized_Inf, 1.0, 0.0, NORM_INF);  //最大值歸一化
	cout << "normalized_Inf=[" << normalized_Inf[0] << ", "
		<< normalized_Inf[1] << ", " << normalized_Inf[2] << "]" << endl;
	normalize(positiveData, normalized_L2SQR, 1.0, 0.0, NORM_MINMAX);  //偏移歸一化
	cout << "normalized_MINMAX=[" << normalized_L2SQR[0] << ", "
		<< normalized_L2SQR[1] << ", " << normalized_L2SQR[2] << "]" << endl;
	//將影像直方圖歸一化
	Mat img = imread("apple.jpg");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat gray, hist;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage_L1 = Mat::zeros(hist_h, hist_w, CV_8UC3);
	Mat histImage_Inf = Mat::zeros(hist_h, hist_w, CV_8UC3);
	Mat hist_L1, hist_Inf;
	normalize(hist, hist_L1, 1, 0, NORM_L1, -1, Mat());
	for (int i = 1; i <= hist_L1.rows; i++)
	{
		rectangle(histImage_L1, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(30 * hist_h*hist_L1.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	normalize(hist, hist_Inf, 1, 0, NORM_INF, -1, Mat());
	for (int i = 1; i <= hist_Inf.rows; i++)
	{
		rectangle(histImage_Inf, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(hist_h*hist_Inf.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow("histImage_L1", histImage_L1);
	imshow("histImage_Inf", histImage_Inf);
	waitKey(0);
	return 0;
}

4.2.2 直方圖比較

compareHist()

代碼清單4-6 比較兩個直方圖的相似性

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

using namespace cv;
using namespace std;

void drawHist(Mat &hist, int type, string name)  //歸一化并繪制直方圖函式
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(hist_h*hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}
//主函式
int main()
{
	system("color F0");  //更改輸出界面顏色
	Mat img = imread("apple.jpg");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat gray, hist, gray2, hist2, gray3, hist3;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	resize(gray, gray2, Size(), 0.5, 0.5);
	gray3 = imread("lena.png", IMREAD_GRAYSCALE);
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
	calcHist(&gray2, 1, channels, Mat(), hist2, 1, bins, ranges);
	calcHist(&gray3, 1, channels, Mat(), hist3, 1, bins, ranges);
	drawHist(hist, NORM_INF, "hist");
	drawHist(hist2, NORM_INF, "hist2");
	drawHist(hist3, NORM_INF, "hist3");
	//原圖直方圖與原圖直方圖的相關系數
	double hist_hist = compareHist(hist, hist, HISTCMP_CORREL);
	cout << "apple_apple=" << hist_hist << endl;
	//原圖直方圖與縮小原圖直方圖的相關系數
	double hist_hist2 = compareHist(hist, hist2, HISTCMP_CORREL);
	cout << "apple_apple256=" << hist_hist2 << endl;
	//兩張不同影像直方圖相關系數
	double hist_hist3 = compareHist(hist, hist3, HISTCMP_CORREL);
	cout << "apple_lena=" << hist_hist3 << endl;
	waitKey(0);
	return 0;
}

4.3 直方圖應用

4.3.1 直方圖均衡化

equalizeHist()

代碼清單4-8 直方圖均衡化實作

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

using namespace cv;
using namespace std;

void drawHist(Mat &hist, int type, string name)  //歸一化并繪制直方圖函式
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(hist_h*hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}
//主函式
int main()
{
	Mat img = imread("gearwheel.jpg");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat gray, hist, hist2;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	Mat equalImg;
	equalizeHist(gray, equalImg);  //將影像直方圖均衡化
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
	calcHist(&equalImg, 1, channels, Mat(), hist2, 1, bins, ranges);
	drawHist(hist, NORM_INF, "hist");
	drawHist(hist2, NORM_INF, "hist2");
	imshow("原圖", gray);
	imshow("均衡化后的影像", equalImg);
	waitKey(0);
	return 0;
}

4.3.2 直方圖匹配

代碼清單4-9 影像直方圖匹配

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

using namespace cv;
using namespace std;

void drawHist(Mat &hist, int type, string name)  //歸一化并繪制直方圖函式
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(20 * hist_h*hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}
//主函式
int main()
{
	Mat img1 = imread("histMatch.png");
	Mat img2 = imread("equalLena.png");
	if (img1.empty() || img2.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat hist1, hist2;
	//計算兩張影像直方圖
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&img1, 1, channels, Mat(), hist1, 1, bins, ranges);
	calcHist(&img2, 1, channels, Mat(), hist2, 1, bins, ranges);
	//歸一化兩張影像的直方圖
	drawHist(hist1, NORM_L1, "hist1");
	drawHist(hist2, NORM_L1, "hist2");
	//計算兩張影像直方圖的累積概率
	float hist1_cdf[256] = { hist1.at<float>(0) };
	float hist2_cdf[256] = { hist2.at<float>(0) };
	for (int i = 1; i < 256; i++)
	{
		hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at<float>(i);
		hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at<float>(i);

	}
	//構建累積概率誤差矩陣
	float diff_cdf[256][256];
	for (int i = 0; i < 256; i++)
	{
		for (int j = 0; j < 256; j++)
		{
			diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);
		}
	}

	//生成LUT映射表
	Mat lut(1, 256, CV_8U);
	for (int i = 0; i < 256; i++)
	{
		// 查找源灰度級為i的映射灰度
		// 和i的累積概率差值最小的規定化灰度
		float min = diff_cdf[i][0];
		int index = 0;
		//尋找累積概率誤差矩陣中每一行中的最小值
		for (int j = 1; j < 256; j++)
		{
			if (min > diff_cdf[i][j])
			{
				min = diff_cdf[i][j];
				index = j;
			}
		}
		lut.at<uchar>(i) = (uchar)index;
	}
	Mat result, hist3;
	LUT(img1, lut, result);
	imshow("待匹配影像", img1);
	imshow("匹配的模板影像", img2);
	imshow("直方圖匹配結果", result);
	calcHist(&result, 1, channels, Mat(), hist3, 1, bins, ranges);
	drawHist(hist3, NORM_L1, "hist3");  //繪制匹配后的影像直方圖
	waitKey(0);
	return 0;
}

4.3.3 直方圖反向投影

calcBackProject()

代碼清單4-11 影像直方圖反向攝影

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

using namespace cv;
using namespace std;

void drawHist(Mat &hist, int type, string name)  //歸一化并繪制直方圖函式
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 255, 0, type, -1, Mat());
	namedWindow(name, WINDOW_NORMAL);
	imshow(name, hist);
}
//主函式
int main()
{
	Mat img = imread("apple.jpg");
	Mat sub_img = imread("sub_apple.jpg");
	Mat img_HSV, sub_HSV, hist, hist2;
	if (img.empty() || sub_img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	imshow("img", img);
	imshow("sub_img", sub_img);
	//轉成HSV空間,提取S、V兩個通道
	cvtColor(img, img_HSV, COLOR_BGR2HSV);
	cvtColor(sub_img, sub_HSV, COLOR_BGR2HSV);
	int h_bins = 32; int s_bins = 32;
	int histSize[] = { h_bins, s_bins };
	//H通道值的范圍由0到179
	float h_ranges[] = { 0, 180 };
	//S通道值的范圍由0到255
	float s_ranges[] = { 0, 256 };
	const float* ranges[] = { h_ranges, s_ranges };  //每個通道的范圍
	int channels[] = { 0, 1 };  //統計的通道索引
								//繪制H-S二維直方圖
	calcHist(&sub_HSV, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
	drawHist(hist, NORM_INF, "hist");  //直方圖歸一化并繪制直方圖
	Mat backproj;
	calcBackProject(&img_HSV, 1, channels, hist, backproj, ranges, 1.0);  //直方圖反向投影
	imshow("反向投影后結果", backproj);
	waitKey(0);
	return 0;
}

4.4 影像的模板匹配

matchTemplate()

代碼清單4-13 影像的模板匹配

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("lena.png");
	Mat temp = imread("lena_face.png");
	if (img.empty() || temp.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat result;
	matchTemplate(img, temp, result, TM_CCOEFF_NORMED);//模板匹配
	double maxVal, minVal;
	Point minLoc, maxLoc;
	//尋找匹配結果中的最大值和最小值以及坐標位置
	minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);
	//繪制最佳匹配區域
	rectangle(img, cv::Rect(maxLoc.x, maxLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2);
	imshow("原圖", img);
	imshow("模板影像", temp);
	imshow("result", result);
	waitKey(0);
	return 0;
}

第五章 影像濾波

5.1 影像卷積

filter2D()

代碼清單5-2 影像卷積

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

using namespace cv;
using namespace std;

int main()
{
	//待卷積矩陣
	uchar points[25] = { 1,2,3,4,5,
		6,7,8,9,10,
		11,12,13,14,15,
		16,17,18,19,20,
		21,22,23,24,25 };
	Mat img(5, 5, CV_8UC1, points);
	//卷積模板
	Mat kernel = (Mat_<float>(3, 3) << 1, 2, 1,
		2, 0, 2,
		1, 2, 1);
	Mat kernel_norm = kernel / 12;  //卷積模板歸一化
									//未歸一化卷積結果和歸一化卷積結果
	Mat result, result_norm;
	filter2D(img, result, CV_32F, kernel, Point(-1, -1), 2, BORDER_CONSTANT);
	filter2D(img, result_norm, CV_32F, kernel_norm, Point(-1, -1), 2, BORDER_CONSTANT);
	cout << "result:" << endl << result << endl;
	cout << "result_norm:" << endl << result_norm << endl;
	//影像卷積
	Mat lena = imread("lena.png");
	if (lena.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat lena_fillter;
	filter2D(lena, lena_fillter, -1, kernel_norm, Point(-1, -1), 2, BORDER_CONSTANT);
	imshow("lena_fillter", lena_fillter);
	imshow("lena", lena);
	waitKey(0);
	return 0;
}

5.2 噪聲的種類與生成

5.2.1 椒鹽噪聲

rand()

rand_double() 、 rand_int()

cvflann類

stlib.h頭檔案

代碼清單5-4 影像中添加椒鹽噪聲

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

using namespace cv;
using namespace std;

//鹽噪聲函式
void saltAndPepper(cv::Mat image, int n)
{
	for (int k = 0; k<n / 2; k++)
	{
		//隨機確定影像中位置
		int i, j;
		i = std::rand() % image.cols;  //取余數運算,保證在影像的列數內 
		j = std::rand() % image.rows;  //取余數運算,保證在影像的行數內 
		int write_black = std::rand() % 2;  //判定為白色噪聲還是黑色噪聲的變數
		if (write_black == 0)  //添加白色噪聲
		{
			if (image.type() == CV_8UC1)  //處理灰度影像
			{
				image.at<uchar>(j, i) = 255;  //白色噪聲
			}
			else if (image.type() == CV_8UC3)  //處理彩色影像
			{
				image.at<cv::Vec3b>(j, i)[0] = 255; //cv::Vec3b為opencv定義的一個3個值的向量型別  
				image.at<cv::Vec3b>(j, i)[1] = 255; //[]指定通道,B:0,G:1,R:2  
				image.at<cv::Vec3b>(j, i)[2] = 255;
			}
		}
		else  //添加黑色噪聲
		{
			if (image.type() == CV_8UC1)
			{
				image.at<uchar>(j, i) = 0;
			}
			else if (image.type() == CV_8UC3)
			{
				image.at<cv::Vec3b>(j, i)[0] = 0; //cv::Vec3b為opencv定義的一個3個值的向量型別  
				image.at<cv::Vec3b>(j, i)[1] = 0; //[]指定通道,B:0,G:1,R:2  
				image.at<cv::Vec3b>(j, i)[2] = 0;
			}
		}

	}
}

int main()
{
	Mat lena = imread("lena.png");
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
	if (lena.empty() || equalLena.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	imshow("lena原圖", lena);
	imshow("equalLena原圖", equalLena);
	saltAndPepper(lena, 10000);  //彩色影像添加椒鹽噪聲
	saltAndPepper(equalLena, 10000);  //灰度影像添加椒鹽噪聲
	imshow("lena添加噪聲", lena);
	imshow("equalLena添加噪聲", equalLena);
	waitKey(0);
	return 0;
}

5.2.2 高斯噪聲

fill()

RNG::fill()

代碼清單5-7 影像中添加高斯噪聲

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

using namespace cv;
using namespace std;

int main()
{
	Mat lena = imread("lena.png");
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
	if (lena.empty() || equalLena.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	//生成與原影像同尺寸、資料型別和通道數的矩陣
	Mat lena_noise = Mat::zeros(lena.rows, lena.cols, lena.type());
	Mat equalLena_noise = Mat::zeros(lena.rows, lena.cols, equalLena.type());
	imshow("lena原圖", lena);
	imshow("equalLena原圖", equalLena);
	RNG rng;  //創建一個RNG類
	rng.fill(lena_noise, RNG::NORMAL, 10, 20);  //生成三通道的高斯分布亂數
	rng.fill(equalLena_noise, RNG::NORMAL, 15, 30);  //生成三通道的高斯分布亂數
	imshow("三通道高斯噪聲", lena_noise);
	imshow("單通道高斯噪聲", equalLena_noise);
	lena = lena + lena_noise;  //在彩色影像中添加高斯噪聲
	equalLena = equalLena + equalLena_noise;  //在灰度影像中添加高斯噪聲
											  //顯示添加高斯噪聲后的影像
	imshow("lena添加噪聲", lena);
	imshow("equalLena添加噪聲", equalLena);
	waitKey(0);
	return 0;
}

5.3 線性濾波

5.3.1 均值濾波

blur()

代碼清單5-9 影像均值濾波

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

using namespace cv;
using namespace std;

int main()
{
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
	Mat equalLena_gauss = imread("equalLena_gauss.png", IMREAD_ANYDEPTH);
	Mat equalLena_salt = imread("equalLena_salt.png", IMREAD_ANYDEPTH);
	if (equalLena.empty() || equalLena_gauss.empty() || equalLena_salt.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat result_3, result_9;  //存放不含噪聲濾波結果,后面數字代表濾波器尺寸
	Mat result_3gauss, result_9gauss;  //存放含有高斯噪聲濾波結果,后面數字代表濾波器尺寸
	Mat result_3salt, result_9salt;  //存放含有椒鹽噪聲濾波結果,后面數字代表濾波器尺寸
	//呼叫均值濾波函式blur()進行濾波
	blur(equalLena, result_3, Size(3, 3));
	blur(equalLena, result_9, Size(9, 9));
	blur(equalLena_gauss, result_3gauss, Size(3, 3));
	blur(equalLena_gauss, result_9gauss, Size(9, 9));
	blur(equalLena_salt, result_3salt, Size(3, 3));
	blur(equalLena_salt, result_9salt, Size(9, 9));
	//顯示不含噪聲影像
	imshow("equalLena ", equalLena);
	imshow("result_3", result_3);
	imshow("result_9", result_9);
	//顯示含有高斯噪聲影像
	imshow("equalLena_gauss", equalLena_gauss);
	imshow("result_3gauss", result_3gauss);
	imshow("result_9gauss", result_9gauss);
	//顯示含有椒鹽噪聲影像
	imshow("equalLena_salt", equalLena_salt);
	imshow("result_3salt", result_3salt);
	imshow("result_9salt", result_9salt);
	waitKey(0);
	return 0;
}

5.3.2 方框濾波

boxFilter()

sqrBoxFilter()

代碼清單5-12 影像方框濾波

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

using namespace cv;
using namespace std;

int main()
{
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);  //用于方框濾波的影像
	if (equalLena.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	//驗證方框濾波演算法的資料矩陣
	float points[25] = { 1,2,3,4,5,
		6,7,8,9,10,
		11,12,13,14,15,
		16,17,18,19,20,
		21,22,23,24,25 };
	Mat data(5, 5, CV_32FC1, points);
	//將CV_8U型別轉換成CV_32F型別
	Mat equalLena_32F;
	equalLena.convertTo(equalLena_32F, CV_32F, 1.0 / 255);
	Mat resultNorm, result, dataSqrNorm, dataSqr, equalLena_32FSqr;
	//方框濾波boxFilter()和sqrBoxFilter()
	boxFilter(equalLena, resultNorm, -1, Size(3, 3), Point(-1, -1), true);  //進行歸一化
	boxFilter(equalLena, result, -1, Size(3, 3), Point(-1, -1), false);  //不進行歸一化
	sqrBoxFilter(data, dataSqrNorm, -1, Size(3, 3), Point(-1, -1),
		true, BORDER_CONSTANT);  //進行歸一化
	sqrBoxFilter(data, dataSqr, -1, Size(3, 3), Point(-1, -1),
		false, BORDER_CONSTANT);  //不進行歸一化
	sqrBoxFilter(equalLena_32F, equalLena_32FSqr, -1, Size(3, 3), Point(-1, -1),
		true, BORDER_CONSTANT);
	//顯示處理結果
	imshow("resultNorm", resultNorm);
	imshow("result", result);
	imshow("equalLena_32FSqr", equalLena_32FSqr);
	waitKey(0);
	return 0;
}

5.3.3 高斯濾波

GaussianBlur()

getGaussianKernel()

代碼清單5-15 影像高斯濾波

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

using namespace cv;
using namespace std;

int main()
{
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
	Mat equalLena_gauss = imread("equalLena_gauss.png", IMREAD_ANYDEPTH);
	Mat equalLena_salt = imread("equalLena_salt.png", IMREAD_ANYDEPTH);
	if (equalLena.empty() || equalLena_gauss.empty() || equalLena_salt.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat result_5, result_9;  //存放不含噪聲濾波結果,后面數字代表濾波器尺寸
	Mat result_5gauss, result_9gauss;  //存放含有高斯噪聲濾波結果,后面數字代表濾波器尺寸
	Mat result_5salt, result_9salt;  存放含有椒鹽噪聲濾波結果,后面數字代表濾波器尺寸
	 //呼叫均值濾波函式blur()進行濾波
	GaussianBlur(equalLena, result_5, Size(5, 5), 10, 20);
	GaussianBlur(equalLena, result_9, Size(9, 9), 10, 20);
	GaussianBlur(equalLena_gauss, result_5gauss, Size(5, 5), 10, 20);
	GaussianBlur(equalLena_gauss, result_9gauss, Size(9, 9), 10, 20);
	GaussianBlur(equalLena_salt, result_5salt, Size(5, 5), 10, 20);
	GaussianBlur(equalLena_salt, result_9salt, Size(9, 9), 10, 20);
	//顯示不含噪聲影像
	imshow("equalLena ", equalLena);
	imshow("result_5", result_5);
	imshow("result_9", result_9);
	//顯示含有高斯噪聲影像
	imshow("equalLena_gauss", equalLena_gauss);
	imshow("result_5gauss", result_5gauss);
	imshow("result_9gauss", result_9gauss);
	//顯示含有椒鹽噪聲影像
	imshow("equalLena_salt", equalLena_salt);
	imshow("result_5salt", result_5salt);
	imshow("result_9salt", result_9salt);
	waitKey(0);
	return 0;
}

5.3.4 可分離濾波

sepFilter2D()

filter2D()

代碼清單5-17 可分離影像濾波

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

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	float points[25] = { 1,2,3,4,5,
		6,7,8,9,10,
		11,12,13,14,15,
		16,17,18,19,20,
		21,22,23,24,25 };
	Mat data(5, 5, CV_32FC1, points);
	//X方向、Y方向和聯合濾波器的構建
	Mat a = (Mat_<float>(3, 1) << -1, 3, -1);
	Mat b = a.reshape(1, 1);
	Mat ab = a*b;
	//驗證高斯濾波的可分離性
	Mat gaussX = getGaussianKernel(3, 1);
	Mat gaussData, gaussDataXY;
	GaussianBlur(data, gaussData, Size(3, 3), 1, 1, BORDER_CONSTANT);
	sepFilter2D(data, gaussDataXY, -1, gaussX, gaussX, Point(-1, -1), 0, BORDER_CONSTANT);
	//輸入兩種高斯濾波的計算結果
	cout << "gaussData=" << endl
		<< gaussData << endl;
	cout << "gaussDataXY=" << endl
		<< gaussDataXY << endl;
	//線性濾波的可分離性
	Mat dataYX, dataY, dataXY, dataXY_sep;
	filter2D(data, dataY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT);
	filter2D(dataY, dataYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT);
	filter2D(data, dataXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT);
	sepFilter2D(data, dataXY_sep, -1, b, b, Point(-1, -1), 0, BORDER_CONSTANT);
	//輸出分離濾波和聯合濾波的計算結果
	cout << "dataY=" << endl
		<< dataY << endl;
	cout << "dataYX=" << endl
		<< dataYX << endl;
	cout << "dataXY=" << endl
		<< dataXY << endl;
	cout << "dataXY_sep=" << endl
		<< dataXY_sep << endl;
	//對影像的分離操作
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat imgYX, imgY, imgXY;
	filter2D(img, imgY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT);
	filter2D(imgY, imgYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT);
	filter2D(img, imgXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT);
	imshow("img", img);
	imshow("imgY", imgY);
	imshow("imgYX", imgYX);
	imshow("imgXY", imgXY);
	waitKey(0);
	return 0;
}

5.4 非線性濾波

5.4.1 中值濾波

medianBlur()

代碼清單5-19 中值濾波

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

using namespace cv;
using namespace std;

int main()
{
	Mat gray = imread("equalLena_salt.png", IMREAD_ANYCOLOR);
	Mat img = imread("lena_salt.png", IMREAD_ANYCOLOR);
	if (gray.empty() || img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat imgResult3, grayResult3, imgResult9, grayResult9;
	//分別對含有椒鹽噪聲的彩色和灰度影像進行濾波,濾波模板為3×3
	medianBlur(img, imgResult3, 3);
	medianBlur(gray, grayResult3, 3);
	//加大濾波模板,影像濾波結果會變模糊
	medianBlur(img, imgResult9, 9);
	medianBlur(gray, grayResult9, 9);
	//顯示濾波處理結果
	imshow("img", img);
	imshow("gray", gray);
	imshow("imgResult3", imgResult3);
	imshow("grayResult3", grayResult3);
	imshow("imgResult9", imgResult9);
	imshow("grayResult9", grayResult9);
	waitKey(0);
	return 0;
}

5.4.2 雙邊濾波

bilateralFilter()

代碼清單5-21 人臉影像雙邊濾波

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

using namespace cv;
using namespace std;

int main()
{
	//讀取兩張含有人臉的影像
	Mat img1 = imread("img1.png", IMREAD_ANYCOLOR);
	Mat img2 = imread("img2.png", IMREAD_ANYCOLOR);
	if (img1.empty() || img2.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat result1, result2, result3, result4;
	//驗證不同濾波器直徑的濾波效果
	bilateralFilter(img1, result1, 9, 50, 25 / 2);
	bilateralFilter(img1, result2, 25, 50, 25 / 2);
	//驗證不同標準差值的濾波效果
	bilateralFilter(img2, result3, 9, 9, 9);
	bilateralFilter(img2, result4, 9, 200, 200);
	//顯示原圖
	imshow("img1", img1);
	imshow("img2", img2);
	//不同直徑濾波結果
	imshow("result1", result1);
	imshow("result2", result2);
	//不同標準差值濾波結果
	imshow("result3 ", result3);
	imshow("result4", result4);
	waitKey(0);
	return 0;
}

5.5 影像的邊緣檢測

5.5.1 邊緣檢測原理

convertScaleAbs()

代碼清單5-23 影像邊緣檢測

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

using namespace cv;
using namespace std;

int main()
{
	//創建邊緣檢測濾波器
	Mat kernel1 = (Mat_<float>(1, 2) << 1, -1);  //X方向邊緣檢測濾波器
	Mat kernel2 = (Mat_<float>(1, 3) << 1, 0, -1);  //X方向邊緣檢測濾波器
	Mat kernel3 = (Mat_<float>(3, 1) << 1, 0, -1);  //X方向邊緣檢測濾波器
	Mat kernelXY = (Mat_<float>(2, 2) << 1, 0, 0, -1);  //由左上到右下方向邊緣檢測濾波器
	Mat kernelYX = (Mat_<float>(2, 2) << 0, -1, 1, 0);  //由右上到左下方向邊緣檢測濾波器

														//讀取影像,黑白影像邊緣檢測結果較為明顯
	Mat img = imread("equalLena.png", IMREAD_ANYCOLOR);
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat result1, result2, result3, result4, result5, result6;

	//檢測影像邊緣
	//以[1 -1]檢測水平方向邊緣
	filter2D(img, result1, CV_16S, kernel1);
	convertScaleAbs(result1, result1);

	//以[1 0 -1]檢測水平方向邊緣
	filter2D(img, result2, CV_16S, kernel2);
	convertScaleAbs(result2, result2);

	//以[1 0 -1]'檢測由垂直方向邊緣
	filter2D(img, result3, CV_16S, kernel3);
	convertScaleAbs(result3, result3);

	//整幅影像的邊緣
	result6 = result2 + result3;
	//檢測由左上到右下方向邊緣
	filter2D(img, result4, CV_16S, kernelXY);
	convertScaleAbs(result4, result4);

	//檢測由右上到左下方向邊緣
	filter2D(img, result5, CV_16S, kernelYX);
	convertScaleAbs(result5, result5);

	//顯示邊緣檢測結果
	imshow("result1", result1);
	imshow("result2", result2);
	imshow("result3", result3);
	imshow("result4", result4);
	imshow("result5", result5);
	imshow("result6", result6);
	waitKey(0);
	return 0;
}

5.5.2 Sobel算子

Sobel()

代碼清單5-25 影像Sobel邊緣提取

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

using namespace cv;
using namespace std;

int main()
{
	//讀取影像,黑白影像邊緣檢測結果較為明顯
	Mat img = imread("equalLena.png", IMREAD_ANYCOLOR);
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat resultX, resultY, resultXY;

	//X方向一階邊緣
	Sobel(img, resultX, CV_16S, 2, 0, 1);
	convertScaleAbs(resultX, resultX);

	//Y方向一階邊緣
	Sobel(img, resultY, CV_16S, 0, 1, 3);
	convertScaleAbs(resultY, resultY);

	//整幅影像的一階邊緣
	resultXY = resultX + resultY;

	//顯示影像
	imshow("resultX", resultX);
	imshow("resultY", resultY);
	imshow("resultXY", resultXY);
	waitKey(0);
	return 0;
}

5.5.3 Scharr算子

Scharr()

代碼清單5-27 影像Scharr邊緣提取

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

using namespace cv;
using namespace std;

int main()
{
	//讀取影像,黑白影像邊緣檢測結果較為明顯
	Mat img = imread("equalLena.png", IMREAD_ANYDEPTH);
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat resultX, resultY, resultXY;

	//X方向一階邊緣
	Scharr(img, resultX, CV_16S, 1, 0);
	convertScaleAbs(resultX, resultX);

	//Y方向一階邊緣
	Scharr(img, resultY, CV_16S, 0, 1);
	convertScaleAbs(resultY, resultY);

	//整幅影像的一階邊緣
	resultXY = resultX + resultY;

	//顯示影像
	imshow("resultX", resultX);
	imshow("resultY", resultY);
	imshow("resultXY", resultXY);
	waitKey(0);
	return 0;
}

5.5.4 生成邊緣檢測濾波器

getDerivKernels()

代碼清單5-29 計算Sobel算子和Scharr算子

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

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	Mat sobel_x1, sobel_y1, sobel_x2, sobel_y2, sobel_x3, sobel_y3;  //存放分離的Sobel算子
	Mat scharr_x, scharr_y;  //存放分離的Scharr算子
	Mat sobelX1, sobelX2, sobelX3, scharrX;  //存放最終算子

											 //一階X方向Sobel算子
	getDerivKernels(sobel_x1, sobel_y1, 1, 0, 3);
	sobel_x1 = sobel_x1.reshape(CV_8U, 1);
	sobelX1 = sobel_y1*sobel_x1;  //計算濾波器

								  //二階X方向Sobel算子
	getDerivKernels(sobel_x2, sobel_y2, 2, 0, 5);
	sobel_x2 = sobel_x2.reshape(CV_8U, 1);
	sobelX2 = sobel_y2*sobel_x2;  //計算濾波器

								  //三階X方向Sobel算子
	getDerivKernels(sobel_x3, sobel_y3, 3, 0, 7);
	sobel_x3 = sobel_x3.reshape(CV_8U, 1);
	sobelX3 = sobel_y3*sobel_x3;  //計算濾波器

								  //X方向Scharr算子
	getDerivKernels(scharr_x, scharr_y, 1, 0, FILTER_SCHARR);
	scharr_x = scharr_x.reshape(CV_8U, 1);
	scharrX = scharr_y*scharr_x;  //計算濾波器

								  //輸出結果
	cout << "X方向一階Sobel算子:" << endl << sobelX1 << endl;
	cout << "X方向二階Sobel算子:" << endl << sobelX2 << endl;
	cout << "X方向三階Sobel算子:" << endl << sobelX3 << endl;
	cout << "X方向Scharr算子:" << endl << scharrX << endl;
	waitKey(0);
	return 0;
}

5.5.5 Laplacian算子

Laplacian()

代碼清單5-31 利用Laplacian算子檢測影像邊緣

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

using namespace cv;
using namespace std;

int main()
{
	//讀取影像,黑白影像邊緣檢測結果較為明顯
	Mat img = imread("equalLena.png", IMREAD_ANYDEPTH);
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat result, result_g, result_G;

	//未濾波提取邊緣
	Laplacian(img, result, CV_16S, 3, 1, 0);
	convertScaleAbs(result, result);

	//濾波后提取Laplacian邊緣
	GaussianBlur(img, result_g, Size(3, 3), 5, 0);  //高斯濾波
	Laplacian(result_g, result_G, CV_16S, 3, 1, 0);
	convertScaleAbs(result_G, result_G);

	//顯示影像
	imshow("result", result);
	imshow("result_G", result_G);
	waitKey(0);
	return 0;
}

5.5.6 Canny演算法

Canny()

代碼清單5-3 利用Canny演算法提取影像邊緣

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

using namespace cv;
using namespace std;

int main()
{
	//讀取影像,黑白影像邊緣檢測結果較為明顯
	Mat img = imread("equalLena.png", IMREAD_ANYDEPTH);
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat resultHigh, resultLow, resultG;

	//大閾值檢測影像邊緣
	Canny(img, resultHigh, 100, 200, 3);

	//小閾值檢測影像邊緣
	Canny(img, resultLow, 20, 40, 3);

	//高斯模糊后檢測影像邊緣
	GaussianBlur(img, resultG, Size(3, 3), 5);
	Canny(resultG, resultG, 100, 200, 3);

	//顯示影像
	imshow("resultHigh", resultHigh);
	imshow("resultLow", resultLow);
	imshow("resultG", resultG);
	waitKey(0);
	return 0;
}

第六章 影像形態學操作

6.1 像素距離與連通域

6.1.1 影像像素距離變換

distanceTransform()

代碼清單6-3 影像距離變換

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

using namespace cv;
using namespace std;

int main()
{
	//構建建議矩陣,用于求取像素之間的距離
	Mat a = (Mat_<uchar>(5, 5) << 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1,
		1, 1, 0, 1, 1,
		1, 1, 1, 1, 1,
		1, 1, 1, 1, 1);
	Mat dist_L1, dist_L2, dist_C, dist_L12;

	//計算街區距離
	distanceTransform(a, dist_L1, 1, 3, CV_8U);
	cout << "街區距離:" << endl << dist_L1 << endl;

	//計算歐式距離
	distanceTransform(a, dist_L2, 2, 5, CV_8U);
	cout << "歐式距離:" << endl << dist_L2 << endl;

	//計算棋盤距離
	distanceTransform(a, dist_C, 3, 5, CV_8U);
	cout << "棋盤距離:" << endl << dist_C << endl;

	//對影像進行距離變換
	Mat rice = imread("rice.png", IMREAD_GRAYSCALE);
	if (rice.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat riceBW, riceBW_INV;

	//將影像轉成二值影像,同時把黑白區域顏色呼喚
	threshold(rice, riceBW, 50, 255, THRESH_BINARY);
	threshold(rice, riceBW_INV, 50, 255, THRESH_BINARY_INV);

	//距離變換
	Mat dist, dist_INV;
	distanceTransform(riceBW, dist, 1, 3, CV_32F);  //為了顯示清晰,將資料型別變成CV_32F
	distanceTransform(riceBW_INV, dist_INV, 1, 3, CV_8U);

	//顯示變換結果
	imshow("riceBW", riceBW);
	imshow("dist", dist);
	imshow("riceBW_INV", riceBW_INV);
	imshow("dist_INV", dist_INV);

	waitKey(0);
	return 0;
}

6.1.2 影像連通域分析

connectedComponents()

代碼清單6-8 影像連通域計算

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	//對影像進行距離變換
	Mat img = imread("rice.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat rice, riceBW;

	//將影像轉成二值影像,用于統計連通域
	cvtColor(img, rice, COLOR_BGR2GRAY);
	threshold(rice, riceBW, 50, 255, THRESH_BINARY);

	//生成隨機顏色,用于區分不同連通域
	RNG rng(10086);
	Mat out;
	int number = connectedComponents(riceBW, out, 8, CV_16U);  //統計影像中連通域的個數
	vector<Vec3b> colors;
	for (int i = 0; i < number; i++)
	{
		//使用均勻分布的亂數確定顏色
		Vec3b vec3 = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
		colors.push_back(vec3);
	}

	//以不同顏色標記出不同的連通域
	Mat result = Mat::zeros(rice.size(), img.type());
	int w = result.cols;
	int h = result.rows;
	for (int row = 0; row < h; row++)
	{
		for (int col = 0; col < w; col++)
		{
			int label = out.at<uint16_t>(row, col);
			if (label == 0)  //背景的黑色不改變
			{
				continue;
			}
			result.at<Vec3b>(row, col) = colors[label];
		}
	}

	//顯示結果
	imshow("原圖", img);
	imshow("標記后的影像", result);

	waitKey(0);
	return 0;
}

connectedComponentsWithStats()

代碼清單6-9 連通域資訊統計

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	//對影像進行距離變換
	Mat img = imread("rice.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	imshow("原圖", img);
	Mat rice, riceBW;

	//將影像轉成二值影像,用于統計連通域
	cvtColor(img, rice, COLOR_BGR2GRAY);
	threshold(rice, riceBW, 50, 255, THRESH_BINARY);

	//生成隨機顏色,用于區分不同連通域
	RNG rng(10086);
	Mat out, stats, centroids;
	//統計影像中連通域的個數
	int number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
	vector<Vec3b> colors;
	for (int i = 0; i < number; i++)
	{
		//使用均勻分布的亂數確定顏色
		Vec3b vec3 = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
		colors.push_back(vec3);
	}

	//以不同顏色標記出不同的連通域
	Mat result = Mat::zeros(rice.size(), img.type());
	int w = result.cols;
	int h = result.rows;
	for (int i = 1; i < number; i++)
	{
		// 中心位置
		int center_x = centroids.at<double>(i, 0);
		int center_y = centroids.at<double>(i, 1);
		//矩形邊框
		int x = stats.at<int>(i, CC_STAT_LEFT);
		int y = stats.at<int>(i, CC_STAT_TOP);
		int w = stats.at<int>(i, CC_STAT_WIDTH);
		int h = stats.at<int>(i, CC_STAT_HEIGHT);
		int area = stats.at<int>(i, CC_STAT_AREA);

		// 中心位置繪制
		circle(img, Point(center_x, center_y), 2, Scalar(0, 255, 0), 2, 8, 0);
		// 外接矩形
		Rect rect(x, y, w, h);
		rectangle(img, rect, colors[i], 1, 8, 0);
		putText(img, format("%d", i), Point(center_x, center_y),
			FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1);
		cout << "number: " << i << ",area: " << area << endl;
	}
	//顯示結果
	imshow("標記后的影像", img);

	waitKey(0);
	return 0;
}

6.2 腐蝕與膨脹

6.2.1 影像腐蝕

erode()

getStructuringElement()

代碼清單6-12 影像腐蝕

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;
//繪制包含區域函式
void drawState(Mat &img, int number, Mat centroids, Mat stats, String str) {
	RNG rng(10086);
	vector<Vec3b> colors;
	for (int i = 0; i < number; i++)
	{
		//使用均勻分布的亂數確定顏色
		Vec3b vec3 = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
		colors.push_back(vec3);
	}

	for (int i = 1; i < number; i++)
	{
		// 中心位置
		int center_x = centroids.at<double>(i, 0);
		int center_y = centroids.at<double>(i, 1);
		//矩形邊框
		int x = stats.at<int>(i, CC_STAT_LEFT);
		int y = stats.at<int>(i, CC_STAT_TOP);
		int w = stats.at<int>(i, CC_STAT_WIDTH);
		int h = stats.at<int>(i, CC_STAT_HEIGHT);

		// 中心位置繪制
		circle(img, Point(center_x, center_y), 2, Scalar(0, 255, 0), 2, 8, 0);
		// 外接矩形
		Rect rect(x, y, w, h);
		rectangle(img, rect, colors[i], 1, 8, 0);
		putText(img, format("%d", i), Point(center_x, center_y),
			FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1);
	}
	imshow(str, img);
}

int main()
{
	//生成用于腐蝕的原影像
	Mat src = (Mat_<uchar>(6, 6) << 0, 0, 0, 0, 255, 0,
		0, 255, 255, 255, 255, 255,
		0, 255, 255, 255, 255, 0,
		0, 255, 255, 255, 255, 0,
		0, 255, 255, 255, 255, 0,
		0, 0, 0, 0, 0, 0);
	Mat struct1, struct2;
	struct1 = getStructuringElement(0, Size(3, 3));  //矩形結構元素
	struct2 = getStructuringElement(1, Size(3, 3));  //十字結構元素

	Mat erodeSrc;  //存放腐蝕后的影像
	erode(src, erodeSrc, struct2);
	namedWindow("src", WINDOW_GUI_NORMAL);
	namedWindow("erodeSrc", WINDOW_GUI_NORMAL);
	imshow("src", src);
	imshow("erodeSrc", erodeSrc);

	Mat LearnCV_black = imread("LearnCV_black.png", IMREAD_ANYCOLOR);
	Mat LearnCV_write = imread("LearnCV_write.png", IMREAD_ANYCOLOR);
	Mat erode_black1, erode_black2, erode_write1, erode_write2;
	//黑背景影像腐蝕
	erode(LearnCV_black, erode_black1, struct1);
	erode(LearnCV_black, erode_black2, struct2);
	imshow("LearnCV_black", LearnCV_black);
	imshow("erode_black1", erode_black1);
	imshow("erode_black2", erode_black2);

	//白背景腐蝕
	erode(LearnCV_write, erode_write1, struct1);
	erode(LearnCV_write, erode_write2, struct2);
	imshow("LearnCV_write", LearnCV_write);
	imshow("erode_write1", erode_write1);
	imshow("erode_write2", erode_write2);

	//驗證腐蝕對小連通域的去除
	Mat img = imread("rice.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat img2;
	copyTo(img, img2, img);  //克隆一個單獨的影像,用于后期影像繪制
	Mat rice, riceBW;

	//將影像轉成二值影像,用于統計連通域
	cvtColor(img, rice, COLOR_BGR2GRAY);
	threshold(rice, riceBW, 50, 255, THRESH_BINARY);

	Mat out, stats, centroids;
	//統計影像中連通域的個數
	int number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
	drawState(img, number, centroids, stats, "未腐蝕時統計連通域");  //繪制影像

	erode(riceBW, riceBW, struct1);  //對影像進行腐蝕
	number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
	drawState(img2, number, centroids, stats, "腐蝕后統計連通域");  //繪制影像

	waitKey(0);
	return 0;
}

6.2.2 影像膨脹

dilate()

getStructuringElement()

代碼清單6-14 影像膨脹

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	//生成用于腐蝕的原影像
	Mat src = (Mat_<uchar>(6, 6) << 0, 0, 0, 0, 255, 0,
		0, 255, 255, 255, 255, 255,
		0, 255, 255, 255, 255, 0,
		0, 255, 255, 255, 255, 0,
		0, 255, 255, 255, 255, 0,
		0, 0, 0, 0, 0, 0);
	Mat struct1, struct2;
	struct1 = getStructuringElement(0, Size(3, 3));  //矩形結構元素
	struct2 = getStructuringElement(1, Size(3, 3));  //十字結構元素

	Mat erodeSrc;  //存放膨脹后的影像
	dilate(src, erodeSrc, struct2);
	namedWindow("src", WINDOW_GUI_NORMAL);
	namedWindow("dilateSrc", WINDOW_GUI_NORMAL);
	imshow("src", src);
	imshow("dilateSrc", erodeSrc);

	Mat LearnCV_black = imread("LearnCV_black.png", IMREAD_ANYCOLOR);
	Mat LearnCV_write = imread("LearnCV_write.png", IMREAD_ANYCOLOR);
	if (LearnCV_black.empty() || LearnCV_write.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	Mat dilate_black1, dilate_black2, dilate_write1, dilate_write2;
	//黑背景影像膨脹
	dilate(LearnCV_black, dilate_black1, struct1);
	dilate(LearnCV_black, dilate_black2, struct2);
	imshow("LearnCV_black", LearnCV_black);
	imshow("dilate_black1", dilate_black1);
	imshow("dilate_black2", dilate_black2);

	//白背景影像膨脹
	dilate(LearnCV_write, dilate_write1, struct1);
	dilate(LearnCV_write, dilate_write2, struct2);
	imshow("LearnCV_write", LearnCV_write);
	imshow("dilate_write1", dilate_write1);
	imshow("dilate_write2", dilate_write2);

	//比較膨脹和腐蝕的結果
	Mat erode_black1, resultXor, resultAnd;
	erode(LearnCV_black, erode_black1, struct1);
	bitwise_xor(erode_black1, dilate_write1, resultXor);
	bitwise_and(erode_black1, dilate_write1, resultAnd);
	imshow("resultXor", resultXor);
	imshow("resultAnd", resultAnd);
	waitKey(0);
	return 0;
}

6.3 形態學應用

6.3.1 開運算

morphologyEx()

6.3.2 閉運算

morphologyEx()

6.3.3 形態學梯度

morphologyEx()

6.3.4 頂帽運算

morphologyEx()

6.3.5 黑帽運算

morphologyEx()

6.3.6 擊中擊不中變換

morphologyEx()

代碼清單6-16 形態學操作應用

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	//用于驗證形態學應用的二值化矩陣
	Mat src = (Mat_<uchar>(9, 12) << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
		0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
		0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
		0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
		0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
		0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
		0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
	namedWindow("src", WINDOW_NORMAL);  //可以自由調節顯示影像的尺寸
	imshow("src", src);
	//3×3矩形結構元素
	Mat kernel = getStructuringElement(0, Size(3, 3));

	//對二值化矩陣進行形態學操作
	Mat open, close, gradient, tophat, blackhat, hitmiss;

	//對二值化矩陣進行開運算
	morphologyEx(src, open, MORPH_OPEN, kernel);
	namedWindow("open", WINDOW_NORMAL);  //可以自由調節顯示影像的尺寸
	imshow("open", open);

	//對二值化矩陣進行閉運算
	morphologyEx(src, close, MORPH_CLOSE, kernel);
	namedWindow("close", WINDOW_NORMAL);  //可以自由調節顯示影像的尺寸
	imshow("close", close);

	//對二值化矩陣進行梯度運算
	morphologyEx(src, gradient, MORPH_GRADIENT, kernel);
	namedWindow("gradient", WINDOW_NORMAL);  //可以自由調節顯示影像的尺寸
	imshow("gradient", gradient);

	//對二值化矩陣進行頂帽運算
	morphologyEx(src, tophat, MORPH_TOPHAT, kernel);
	namedWindow("tophat", WINDOW_NORMAL);  //可以自由調節顯示影像的尺寸
	imshow("tophat", tophat);

	//對二值化矩陣進行黑帽運算
	morphologyEx(src, blackhat, MORPH_BLACKHAT, kernel);
	namedWindow("blackhat", WINDOW_NORMAL);   //可以自由調節顯示影像的尺寸
	imshow("blackhat", blackhat);

	//對二值化矩陣進行擊中擊不中變換
	morphologyEx(src, hitmiss, MORPH_HITMISS, kernel);
	namedWindow("hitmiss", WINDOW_NORMAL);  //可以自由調節顯示影像的尺寸
	imshow("hitmiss", hitmiss);

	//用影像驗證形態學操作效果
	Mat keys = imread("keys.jpg",IMREAD_GRAYSCALE);
	imshow("原影像", keys);
	threshold(keys, keys, 80, 255, THRESH_BINARY);
	imshow("二值化后的keys", keys);

	//5×5矩形結構元素
	Mat kernel_keys = getStructuringElement(0, Size(5, 5));
	Mat open_keys, close_keys, gradient_keys, tophat_keys, blackhat_keys, hitmiss_keys;

	//對影像進行開運算
	morphologyEx(keys, open_keys, MORPH_OPEN, kernel_keys);
	imshow("open_keys", open_keys);

	//對影像進行閉運算
	morphologyEx(keys, close_keys, MORPH_CLOSE, kernel_keys);
	imshow("close_keys", close_keys);

	//對影像進行梯度運算
	morphologyEx(keys, gradient_keys, MORPH_GRADIENT, kernel_keys);
	imshow("gradient_keys", gradient_keys);

	//對影像進行頂帽運算
	morphologyEx(keys, tophat_keys, MORPH_TOPHAT, kernel_keys);
	imshow("tophat_keys", tophat_keys);

	//對影像進行黑帽運算
	morphologyEx(keys, blackhat_keys, MORPH_BLACKHAT, kernel_keys);
	imshow("blackhat_keys", blackhat_keys);

	//對影像進行擊中擊不中變換
	morphologyEx(keys, hitmiss_keys, MORPH_HITMISS, kernel_keys);
	imshow("hitmiss_keys", hitmiss_keys);

	waitKey(0);
	return 0;
}

6.3.7 影像細化

thinning()

ximgproc建構式

代碼清單6-18 影像細化

#include <opencv2\opencv.hpp>
#include <opencv2\ximgproc.hpp>  //細化函式thining所在的頭檔案
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	//中文字進行細化
	Mat img = imread("LearnCV_black.png", IMREAD_ANYCOLOR);
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	//英文字+實心圓和圓環細化
	Mat words = Mat::zeros(100, 200, CV_8UC1);  //創建一個黑色的背景圖片
	putText(words, "Learn", Point(30, 30), 2, 1, Scalar(255), 2);  //添加英文
	putText(words, "OpenCV 4", Point(30, 60), 2, 1, Scalar(255), 2);
	circle(words, Point(80, 75), 10, Scalar(255), -1);  //添加實心圓
	circle(words, Point(130, 75), 10, Scalar(255), 3);  //添加圓環

	//進行細化
	Mat thin1, thin2;
	ximgproc::thinning(img, thin1, 0);  //注意類名
	ximgproc::thinning(words, thin2, 0);

	//顯示處理結果
	imshow("thin1", thin1);
	imshow("img", img);
	namedWindow("thin2", WINDOW_NORMAL);
	imshow("thin2", thin2);
	namedWindow("words", WINDOW_NORMAL);
	imshow("words", words);
	waitKey(0);
	return 0;
}

第七章 目標檢測

7.1 形狀檢測

7.1.1 直線檢測

HoughLines()

Canny()

line()

代碼清單7-2 檢測直線并繪制直線

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

using namespace cv;
using namespace std;

void drawLine(Mat &img, //要標記直線的影像
	vector<Vec2f> lines,   //檢測的直線資料
	double rows,   //原影像的行數(高)
	double cols,  //原影像的列數(寬)
	Scalar scalar,  //繪制直線的顏色
	int n  //繪制直線的線寬
)
{
	Point pt1, pt2;
	for (size_t i = 0; i < lines.size(); i++)
	{
		float rho = lines[i][0];  //直線距離坐標原點的距離
		float theta = lines[i][1];  //直線過坐標原點垂線與x軸夾角
		double a = cos(theta);  //夾角的余弦值
		double b = sin(theta);  //夾角的正弦值
		double x0 = a*rho, y0 = b*rho;  //直線與過坐標原點的垂線的交點
		double length = max(rows, cols);  //影像高寬的最大值
										  //計算直線上的一點
		pt1.x = cvRound(x0 + length  * (-b));
		pt1.y = cvRound(y0 + length  * (a));
		//計算直線上另一點
		pt2.x = cvRound(x0 - length  * (-b));
		pt2.y = cvRound(y0 - length  * (a));
		//兩點繪制一條直線
		line(img, pt1, pt2, scalar, n);
	}
}

int main()
{
	Mat img = imread("HoughLines.jpg", IMREAD_GRAYSCALE);
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat edge;

	//檢測邊緣影像,并二值化
	Canny(img, edge, 80, 180, 3, false);
	threshold(edge, edge, 170, 255, THRESH_BINARY);

	//用不同的累加器進行檢測直線
	vector<Vec2f> lines1, lines2;
	HoughLines(edge, lines1, 1, CV_PI / 180, 50, 0, 0);
	HoughLines(edge, lines2, 1, CV_PI / 180, 150, 0, 0);

	//在原影像中繪制直線
	Mat img1, img2;
	img.copyTo(img1);
	img.copyTo(img2);
	drawLine(img1, lines1, edge.rows, edge.cols, Scalar(255), 2);
	drawLine(img2, lines2, edge.rows, edge.cols, Scalar(255), 2);

	//顯示影像
	imshow("edge", edge);
	imshow("img", img);
	imshow("img1", img1);
	imshow("img2", img2);
	waitKey(0);
	return 0;
}

HoughLinesP()

代碼清單7-4 檢測影像中的現代

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("HoughLines.jpg", IMREAD_GRAYSCALE);
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat edge;

	//檢測邊緣影像,并二值化
	Canny(img, edge, 80, 180, 3, false);
	threshold(edge, edge, 170, 255, THRESH_BINARY);

	//利用漸進概率式霍夫變換提取直線
	vector<Vec4i> linesP1, linesP2;
	HoughLinesP(edge, linesP1, 1, CV_PI / 180, 150, 30, 10);  //兩個點連接最大距離10
	HoughLinesP(edge, linesP2, 1, CV_PI / 180, 150, 30, 30);  //兩個點連接最大距離30

															  //繪制兩個點連接最大距離10直線檢測結果
	Mat img1;
	img.copyTo(img1);
	for (size_t i = 0; i < linesP1.size(); i++)
	{
		line(img1, Point(linesP1[i][0], linesP1[i][1]),
			Point(linesP1[i][2], linesP1[i][3]), Scalar(255), 3);
	}

	//繪制兩個點連接最大距離30直線檢測結果
	Mat img2;
	img.copyTo(img2);
	for (size_t i = 0; i < linesP2.size(); i++)
	{
		line(img2, Point(linesP2[i][0], linesP2[i][1]),
			Point(linesP2[i][2], linesP2[i][3]), Scalar(255), 3);
	}

	//顯示影像
	imshow("img1", img1);
	imshow("img2", img2);
	waitKey(0);
	return 0;
}

HoughLinesPointSet()

代碼清單7-6 在二維點集中檢測直線

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

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	Mat lines;  //存放檢測直線結果的矩陣
	vector<Vec3d> line3d;  //換一種結果存放形式
	vector<Point2f> point;  //待檢測是否存在直線的所有點
	const static float Points[20][2] = {
		{ 0.0f,   369.0f },{ 10.0f,  364.0f },{ 20.0f,  358.0f },{ 30.0f,  352.0f },
		{ 40.0f,  346.0f },{ 50.0f,  341.0f },{ 60.0f,  335.0f },{ 70.0f,  329.0f },
		{ 80.0f,  323.0f },{ 90.0f,  318.0f },{ 100.0f, 312.0f },{ 110.0f, 306.0f },
		{ 120.0f, 300.0f },{ 130.0f, 295.0f },{ 140.0f, 289.0f },{ 150.0f, 284.0f },
		{ 160.0f, 277.0f },{ 170.0f, 271.0f },{ 180.0f, 266.0f },{ 190.0f, 260.0f }
	};
	//將所有點存放在vector中,用于輸入函式中
	for (int i = 0; i < 20; i++)
	{
		point.push_back(Point2f(Points[i][0], Points[i][1]));
	}
	//引數設定
	double rhoMin = 0.0f;  //最小長度
	double rhoMax = 360.0f;  //最大長度
	double rhoStep = 1;  //離散化單位距離長度
	double thetaMin = 0.0f;  //最小角度
	double thetaMax = CV_PI / 2.0f;  //最大角度
	double thetaStep = CV_PI / 180.0f;  離散化單位角度弧度
	HoughLinesPointSet(point, lines, 20, 1, rhoMin, rhoMax, rhoStep,
		thetaMin, thetaMax, thetaStep);
	lines.copyTo(line3d);

	//輸出結果
	for (int i = 0; i < line3d.size(); i++)
	{
		cout << "votes:" << (int)line3d.at(i).val[0] << ", "
			<< "rho:" << line3d.at(i).val[1] << ", "
			<< "theta:" << line3d.at(i).val[2] << endl;
	}
	return 0;
}

7.1.2 直線擬合

fitLine()

代碼清單7-8 直線擬合

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	Vec4f lines;  //存放你和后的直線
	vector<Point2f> point;  //待檢測是否存在直線的所有點
	const static float Points[20][2] = {
		{ 0.0f,   0.0f },{ 10.0f,  11.0f },{ 21.0f,  20.0f },{ 30.0f,  30.0f },
		{ 40.0f,  42.0f },{ 50.0f,  50.0f },{ 60.0f,  60.0f },{ 70.0f,  70.0f },
		{ 80.0f,  80.0f },{ 90.0f,  92.0f },{ 100.0f, 100.0f },{ 110.0f, 110.0f },
		{ 120.0f, 120.0f },{ 136.0f, 130.0f },{ 138.0f, 140.0f },{ 150.0f, 150.0f },
		{ 160.0f, 163.0f },{ 175.0f, 170.0f },{ 181.0f, 180.0f },{ 200.0f, 190.0f }
	};
	//將所有點存放在vector中,用于輸入函式中
	for (int i = 0; i < 20; i++)
	{
		point.push_back(Point2f(Points[i][0], Points[i][1]));
	}
	//引數設定
	double param = 0;  //距離模型中的數值引數C
	double reps = 0.01;  //坐標原點與直線之間的距離精度
	double aeps = 0.01;  //角度精度
	fitLine(point, lines, DIST_L1, 0, 0.01, 0.01);
	double k = lines[1] / lines[0];  //直線斜率
	cout << "直線斜率:" << k << endl;
	cout << "直線上一點坐標x:" << lines[2] << ", y::" << lines[3] << endl;
	cout << "直線決議式:y=" << k << "(x-" << lines[2] << ")+" << lines[3] << endl;
	return 0;
}

7.1.3 圓形檢測

HoughCircles()

代碼清單7-10 圓形檢測

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("keys.jpg");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	imshow("原圖", img);
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	GaussianBlur(gray, gray, Size(9, 9), 2, 2);  //平滑濾波

												 //檢測圓形
	vector<Vec3f> circles;
	double dp = 2; //
	double minDist = 10;  //兩個圓心之間的最小距離
	double	param1 = 100;  //Canny邊緣檢測的較大閾值
	double	param2 = 100;  //累加器閾值
	int min_radius = 20;  //圓形半徑的最小值
	int max_radius = 100;  //圓形半徑的最大值
	HoughCircles(gray, circles, HOUGH_GRADIENT, dp, minDist, param1, param2,
		min_radius, max_radius);

	//影像中標記出圓形
	for (size_t i = 0; i < circles.size(); i++)
	{
		//讀取圓心
		Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
		//讀取半徑
		int radius = cvRound(circles[i][2]);
		//繪制圓心
		circle(img, center, 3, Scalar(0, 255, 0), -1, 8, 0);
		//繪制圓
		circle(img, center, radius, Scalar(0, 0, 255), 3, 8, 0);
	}

	//顯示結果
	imshow("圓檢測結果", img);
	waitKey(0);
	return 0;
}

7.2 輪廓檢測

findContours()

drawContours()

7.2.1 輪廓發現與繪制

代碼清單7-14 輪廓發現與繪制

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	Mat img = imread("keys.jpg");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	imshow("原圖", img);
	Mat gray, binary;
	cvtColor(img, gray, COLOR_BGR2GRAY);  //轉化成灰度圖
	GaussianBlur(gray, gray, Size(13, 13), 4, 4);  //平滑濾波
	threshold(gray, binary, 170, 255, THRESH_BINARY | THRESH_OTSU);  //自適應二值化

																	 // 輪廓發現與繪制
	vector<vector<Point>> contours;  //輪廓
	vector<Vec4i> hierarchy;  //存放輪廓結構變數
	findContours(binary, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
	//繪制輪廓
	for (int t = 0; t < contours.size(); t++)
	{
		drawContours(img, contours, t, Scalar(0, 0, 255), 2, 8);
	}
	//輸出輪廓結構描述子
	for (int i = 0; i < hierarchy.size(); i++)
	{
		cout << hierarchy[i] << endl;
	}

	//顯示結果
	imshow("輪廓檢測結果", img);
	waitKey(0);
	return 0;
}

7.2.2 輪廓面積

contourArea()

代碼清單7-16 計算輪廓面積

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	//用四個點表示三角形輪廓
	vector<Point> contour;
	contour.push_back(Point2f(0, 0));
	contour.push_back(Point2f(10, 0));
	contour.push_back(Point2f(10, 10));
	contour.push_back(Point2f(5, 5));
	double area = contourArea(contour);
	cout << "area =" << area << endl;

	Mat img = imread("coins.jpg");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	imshow("原圖", img);
	Mat gray, binary;
	cvtColor(img, gray, COLOR_BGR2GRAY);  //轉化成灰度圖
	GaussianBlur(gray, gray, Size(9, 9), 2, 2);  //平滑濾波
	threshold(gray, binary, 170, 255, THRESH_BINARY | THRESH_OTSU);  //自適應二值化

																	 // 輪廓檢測
	vector<vector<Point>> contours;  //輪廓
	vector<Vec4i> hierarchy;  //存放輪廓結構變數
	findContours(binary, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());

	//輸出輪廓面積
	for (int t = 0; t < contours.size(); t++)
	{
		double area1 = contourArea(contours[t]);
		cout << "第" << t << "輪廓面積=" << area1 << endl;
	}
	return 0;
}

7.2.3 輪廓長度(周長)

arcLength()

代碼清單7-18 計算輪廓長度

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	//用四個點表示三角形輪廓
	vector<Point> contour;
	contour.push_back(Point2f(0, 0));
	contour.push_back(Point2f(10, 0));
	contour.push_back(Point2f(10, 10));
	contour.push_back(Point2f(5, 5));

	double length0 = arcLength(contour, true);
	double length1 = arcLength(contour, false);
	cout << "length0 =" << length0 << endl;
	cout << "length1 =" << length1 << endl;

	Mat img = imread("coins.jpg");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	imshow("原圖", img);
	Mat gray, binary;
	cvtColor(img, gray, COLOR_BGR2GRAY);  //轉化成灰度圖
	GaussianBlur(gray, gray, Size(9, 9), 2, 2);  //平滑濾波
	threshold(gray, binary, 170, 255, THRESH_BINARY | THRESH_OTSU);  //自適應二值化

																	 // 輪廓檢測
	vector<vector<Point>> contours;  //輪廓
	vector<Vec4i> hierarchy;  //存放輪廓結構變數
	findContours(binary, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());

	//輸出輪廓長度
	for (int t = 0; t < contours.size(); t++)
	{
		double length2 = arcLength(contours[t], true);
		cout << "第" << t << "個輪廓長度=" << length2 << endl;
	}
	return 0;
}

7.2.4 輪廓外接多邊形

boundingRect()

minAreaRect()

代碼清單7-21 計算輪廓外接矩形

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("stuff.jpg");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat img1, img2;
	img.copyTo(img1);  //深拷貝用來繪制最大外接矩形
	img.copyTo(img2);  //深拷貝用來繪制最小外接矩形
	imshow("img", img);

	// 去噪聲與二值化
	Mat canny;
	Canny(img, canny, 80, 160, 3, false);
	imshow("", canny);

	//膨脹運算,將細小縫隙填補上
	Mat kernel = getStructuringElement(0, Size(3, 3));
	dilate(canny, canny, kernel);

	// 輪廓發現與繪制
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(canny, contours, hierarchy, 0, 2, Point());

	//尋找輪廓的外接矩形
	for (int n = 0; n < contours.size(); n++)
	{
		// 最大外接矩形
		Rect rect = boundingRect(contours[n]);
		rectangle(img1, rect, Scalar(0, 0, 255), 2, 8, 0);

		// 最小外接矩形
		RotatedRect rrect = minAreaRect(contours[n]);
		Point2f points[4];
		rrect.points(points);  //讀取最小外接矩形的四個頂點
		Point2f cpt = rrect.center;  //最小外接矩形的中心

									 // 繪制旋轉矩形與中心位置
		for (int i = 0; i < 4; i++)
		{
			if (i == 3)
			{
				line(img2, points[i], points[0], Scalar(0, 255, 0), 2, 8, 0);
				break;
			}
			line(img2, points[i], points[i + 1], Scalar(0, 255, 0), 2, 8, 0);
		}
		//繪制矩形的中心
		circle(img, cpt, 2, Scalar(255, 0, 0), 2, 8, 0);
	}

	//輸出繪制外接矩形的結果
	imshow("max", img1);
	imshow("min", img2);
	waitKey(0);
	return 0;
}

approxPolyDP()

代碼清單7-23 對多個輪廓進行多邊形逼近

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

using namespace cv;
using namespace std;

//繪制輪廓函式
void drawapp(Mat result, Mat img2)
{
	for (int i = 0; i < result.rows; i++)
	{
		//最后一個坐標點與第一個坐標點連接
		if (i == result.rows - 1)
		{
			Vec2i point1 = result.at<Vec2i>(i);
			Vec2i point2 = result.at<Vec2i>(0);
			line(img2, point1, point2, Scalar(0, 0, 255), 2, 8, 0);
			break;
		}
		Vec2i point1 = result.at<Vec2i>(i);
		Vec2i point2 = result.at<Vec2i>(i + 1);
		line(img2, point1, point2, Scalar(0, 0, 255), 2, 8, 0);
	}
}

int main(int argc, const char *argv[])
{
	Mat img = imread("approx.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	// 邊緣檢測
	Mat canny;
	Canny(img, canny, 80, 160, 3, false);
	//膨脹運算
	Mat kernel = getStructuringElement(0, Size(3, 3));
	dilate(canny, canny, kernel);

	// 輪廓發現與繪制
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(canny, contours, hierarchy, 0, 2, Point());

	//繪制多邊形
	for (int t = 0; t < contours.size(); t++)
	{
		//用最小外接矩形求取輪廓中心
		RotatedRect rrect = minAreaRect(contours[t]);
		Point2f center = rrect.center;
		circle(img, center, 2, Scalar(0, 255, 0), 2, 8, 0);

		Mat result;
		approxPolyDP(contours[t], result, 4, true);  //多邊形擬合
		drawapp(result, img);
		cout << "corners : " << result.rows << endl;

		//判斷形狀和繪制輪廓
		if (result.rows == 3)
		{
			putText(img, "triangle", center, 0, 1, Scalar(0, 255, 0), 1, 8);
		}
		if (result.rows == 4)
		{
			putText(img, "rectangle", center, 0, 1, Scalar(0, 255, 0), 1, 8);
		}
		if (result.rows == 8)
		{
			putText(img, "poly-8", center, 0, 1, Scalar(0, 255, 0), 1, 8);
		}
		if (result.rows > 12)
		{
			putText(img, "circle", center, 0, 1, Scalar(0, 255, 0), 1, 8);
		}
	}
	imshow("result", img);
	waitKey(0);
	return 0;
}

7.2.5 點到輪廓距離

pointPolygonTest()

代碼清單7-25 點到輪廓距離

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

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	Mat img = imread("approx.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	// 邊緣檢測
	Mat canny;
	Canny(img, canny, 80, 160, 3, false);
	//膨脹運算
	Mat kernel = getStructuringElement(0, Size(3, 3));
	dilate(canny, canny, kernel);

	// 輪廓發現
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(canny, contours, hierarchy, 0, 2, Point());

	//創建影像中的一個像素點并繪制圓形
	Point point = Point(250, 200);
	circle(img, point, 2, Scalar(0, 0, 255), 2, 8, 0);

	//多邊形
	for (int t = 0; t < contours.size(); t++)
	{
		//用最小外接矩形求取輪廓中心
		RotatedRect rrect = minAreaRect(contours[t]);
		Point2f center = rrect.center;
		circle(img, center, 2, Scalar(0, 255, 0), 2, 8, 0);  //繪制圓心點
															 //輪廓外部點距離輪廓的距離
		double dis = pointPolygonTest(contours[t], point, true);
		//輪廓內部點距離輪廓的距離
		double dis2 = pointPolygonTest(contours[t], center, true);
		//輸出點結果
		cout << "外部點距離輪廓距離:" << dis << endl;
		cout << "內部點距離輪廓距離:" << dis2 << endl;
	}
	return 0;
}

7.2.6 凸包檢測

convexHull()

代碼清單7-27 凸包檢測

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("hand.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	// 二值化
	Mat gray, binary;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	threshold(gray, binary, 105, 255, THRESH_BINARY);

	//開運算消除細小區域
	Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	morphologyEx(binary, binary, MORPH_OPEN, k);
	imshow("binary", binary);

	// 輪廓發現
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(binary, contours, hierarchy, 0, 2, Point());
	for (int n = 0; n < contours.size(); n++)
	{
		//計算凸包
		vector<Point> hull;
		convexHull(contours[n], hull);
		//繪制凸包
		for (int i = 0; i < hull.size(); i++)
		{
			//繪制凸包頂點
			circle(img, hull[i], 4, Scalar(255, 0, 0), 2, 8, 0);
			//連接凸包
			if (i == hull.size() - 1)
			{
				line(img, hull[i], hull[0], Scalar(0, 0, 255), 2, 8, 0);
				break;
			}
			line(img, hull[i], hull[i + 1], Scalar(0, 0, 255), 2, 8, 0);
		}
	}
	imshow("hull", img);
	waitKey(0);
	return 0;
}

7.3 矩的計算

7.3.1 幾何矩與中心矩

moments()

代碼清單7-29 計算影像矩

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("approx.png");

	// 二值化
	Mat gray, binary;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	threshold(gray, binary, 105, 255, THRESH_BINARY);

	//開運算消除細小區域
	Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	morphologyEx(binary, binary, MORPH_OPEN, k);

	// 輪廓發現
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(binary, contours, hierarchy, 0, 2, Point());
	for (int n = 0; n < contours.size(); n++) 
	{
		Moments M;
		M = moments(contours[n], true);
		cout << "spatial moments:" << endl
			<< "m00:" << M.m00 << " m01:" << M.m01 << " m10:" << M.m10 << endl
			<< "m11:" << M.m11 << " m02:" << M.m02 << " m20:" << M.m20 << endl
			<< "m12:" << M.m12 << " m21:" << M.m21 << " m03:" << M.m03 << " m30:"<< M.m30 << endl;

		cout << "central moments:" << endl
			<< "mu20:" << M.mu20 << " mu02:" << M.mu02 << " mu11:" << M.mu11 << endl
			<< "mu30:" << M.mu30 << " mu21:" << M.mu21 << " mu12:" << M.mu12 << " mu03:" << M.mu03 << endl;

		cout << "central normalized moments:" << endl
			<< "nu20:" << M.nu20 << " nu02:" << M.nu02 << " nu11:" << M.nu11 << endl
			<< "nu30:" << M.nu30 << " nu21:" << M.nu21 << " nu12:" << M.nu12 << " nu03:" << M.nu03 << endl;
	}
	return 0;
}

7.3.2 Hu矩

HuMoments()

代碼清單7-31 計算影像的Hu矩

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

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改輸出界面顏色
	Mat img = imread("approx.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	// 二值化
	Mat gray, binary;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	threshold(gray, binary, 105, 255, THRESH_BINARY);

	//開運算消除細小區域
	Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	morphologyEx(binary, binary, MORPH_OPEN, k);

	// 輪廓發現
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(binary, contours, hierarchy, 0, 2, Point());
	for (int n = 0; n < contours.size(); n++)
	{
		Moments M;
		M = moments(contours[n], true);
		Mat hu;
		HuMoments(M, hu);  //計算Hu矩
		cout << hu << endl;
	}
	return 0;
}

7.3.3基于Hu矩的輪廓匹配

matchShapes()

代碼清單7-33 基于Hu矩的輪廓匹配

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

using namespace cv;
using namespace std;

void findcontours(Mat &image, vector<vector<Point>> &contours)
{
	Mat gray, binary;
	vector<Vec4i> hierarchy;
	//影像灰度化
	cvtColor(image, gray, COLOR_BGR2GRAY);
	//影像二值化
	threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
	//尋找輪廓
	findContours(binary, contours, hierarchy, 0, 2);
}

int main()
{
	Mat img = imread("ABC.png");
	Mat img_B = imread("B.png");
	if (img.empty() || img_B.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	resize(img_B, img_B, Size(), 0.5, 0.5);
	imwrite("B.png", img_B);
	imshow("B", img_B);

	// 輪廓提取
	vector<vector<Point>> contours1;
	vector<vector<Point>> contours2;
	findcontours(img, contours1);
	findcontours(img_B, contours2);
	// hu矩計算
	Moments mm2 = moments(contours2[0]);
	Mat hu2;
	HuMoments(mm2, hu2);
	// 輪廓匹配
	for (int n = 0; n < contours1.size(); n++)
	{
		Moments mm = moments(contours1[n]);
		Mat hum;
		HuMoments(mm, hum);
		//Hu矩匹配
		double dist;
		dist = matchShapes(hum, hu2, CONTOURS_MATCH_I1, 0);
		if (dist < 1)
		{
			drawContours(img, contours1, n, Scalar(0, 0, 255), 3, 8);
		}
	}
	imshow("match result", img);
	waitKey(0);
	return 0;
}

7.4 點集擬合

minEnclosingTriangle()

minEnclosingCircle()

代碼清單7-36 點集外包輪廓

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
	Mat img(500, 500, CV_8UC3, Scalar::all(0));
	RNG& rng = theRNG();  //生成隨機點

	while (true)
	{
		int i, count = rng.uniform(1, 101);
		vector<Point> points;
		//生成隨機點
		for (i = 0; i < count; i++)
		{
			Point pt;
			pt.x = rng.uniform(img.cols / 4, img.cols * 3 / 4);
			pt.y = rng.uniform(img.rows / 4, img.rows * 3 / 4);
			points.push_back(pt);
		}

		//尋找包圍點集的三角形 
		vector<Point2f> triangle;
		double area = minEnclosingTriangle(points, triangle);

		//尋找包圍點集的圓形
		Point2f center;
		float radius = 0;
		minEnclosingCircle(points, center, radius);

		//創建兩個圖片用于輸出結果
		img = Scalar::all(0);
		Mat img2;
		img.copyTo(img2);

		//在影像中繪制坐標點
		for (i = 0; i < count; i++)
		{
			circle(img, points[i], 3, Scalar(255, 255, 255), FILLED, LINE_AA);
			circle(img2, points[i], 3, Scalar(255, 255, 255), FILLED, LINE_AA);
		}
			
		//繪制三角形
		for (i = 0; i < 3; i++)
		{
			if (i==2)
			{
				line(img, triangle[i], triangle[0], Scalar(255, 255, 255), 1, 16);
				break;
			}
			line(img, triangle[i], triangle[i + 1], Scalar(255, 255, 255), 1, 16);
		}

		//繪制圓形
		circle(img2, center, cvRound(radius), Scalar(255, 255, 255), 1, LINE_AA);

		//輸出結果
		imshow("triangle", img);
		imshow("circle", img2);

		//按q鍵或者ESC鍵退出程式
		char key = (char)waitKey();
		if (key == 27 || key == 'q' || key == 'Q')
		{
			break;
		}
	}
	return 0;
}

7.5 QR二維碼檢測

QRCodeDetector類

detect()

decode()

detectAndDecode()

代碼清單7-40 二維碼識別

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

using namespace cv;
using namespace std;

int main(int argc, char** argv) 
{
	Mat img = imread("qrcode2.png");

	Mat gray, qrcode_bin;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	QRCodeDetector qrcodedetector;
	vector<Point> points;
	string information;
	bool isQRcode;
	isQRcode = qrcodedetector.detect(gray, points);  //識別二維碼
	if (isQRcode)
	{
		//解碼二維碼
		information = qrcodedetector.decode(gray, points, qrcode_bin);
		cout << points << endl;  //輸出二維碼四個頂點的坐標
	}
	else
	{
		cout << "無法識別二維碼,請確認影像時候含有二維碼" << endl;
		return -1;
	}
	//繪制二維碼的邊框
	for (int i = 0; i < points.size(); i++)
	{
		if (i== points.size()-1)
		{
			line(img, points[i], points[0], Scalar(0, 0, 255), 2, 8);
			break;
		}
		line(img, points[i], points[i + 1], Scalar(0, 0, 255), 2, 8);
	}
	//將解碼內容輸出到圖片上
	putText(img, information.c_str(),Point(20, 30), 0, 1.0, Scalar(0, 0, 255), 2, 8);

	//利用函式直接定位二維碼并解碼
	string information2;
	vector<Point> points2;
	information2 = qrcodedetector.detectAndDecode(gray,points2);
	cout << points2 << endl;
	putText(img, information2.c_str(), Point(20, 55), 0, 1.0, Scalar(0, 0, 0), 2, 8);

	//輸出結果
	imshow("result", img);
	namedWindow("qrcode_bin", WINDOW_NORMAL);
	imshow("qrcode_bin", qrcode_bin);
	waitKey(0);
	return 0;
}

第八章 影像分析與修復

8.1 傅里葉變換

8.1.1 離散傅里葉變換

df()

idft()

getOptimalDFTSize()

magnitude()

代碼清單8-6 離散傅里葉變換

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

using namespace std;
using namespace cv;

int main()
{
	//對矩陣進行處理,展示正逆變換的關系
	Mat a = (Mat_<float>(5, 5) << 1, 2, 3, 4, 5,
		2, 3, 4, 5, 6,
		3, 4, 5, 6, 7,
		4, 5, 6, 7, 8,
		5, 6, 7, 8, 9);
	Mat b, c, d;
	dft(a, b, DFT_COMPLEX_OUTPUT);  //正變換
	dft(b, c, DFT_INVERSE | DFT_SCALE | DFT_REAL_OUTPUT);  //逆變換只輸出實數
	idft(b, d, DFT_SCALE);  //逆變換

	//對影像進行處理
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	resize(gray, gray, Size(502, 502));
	imshow("原影像", gray);

	//計算合適的離散傅里葉變換尺寸
	int rows = getOptimalDFTSize(gray.rows);
	int cols = getOptimalDFTSize(gray.cols);

	//擴展影像
	Mat appropriate;
	int T = (rows - gray.rows) / 2;  //上方擴展行數
	int B = rows - gray.rows - T;  //下方擴展行數
	int L = (cols - gray.cols) / 2;  //左側擴展行數
	int R = cols - gray.cols - L;  //右側擴展行數
	copyMakeBorder(gray, appropriate, T, B, L, R, BORDER_CONSTANT);
	imshow("擴展后的影像", appropriate);

	//構建離散傅里葉變換輸入量
	Mat flo[2], complex;
	flo[0] = Mat_<float>(appropriate);  //實數部分
	flo[1] = Mat::zeros(appropriate.size(), CV_32F);  //虛數部分
	merge(flo, 2, complex);  //合成一個多通道矩陣

	//進行離散傅里葉變換
	Mat result;
	dft(complex, result);

	//將復數轉化為幅值
	Mat resultC[2];
	split(result, resultC);  //分成實數和虛數
	Mat amplitude;
	magnitude(resultC[0], resultC[1], amplitude);

	//進行對數放縮公式為: M1 = log(1+M),保證所有數都大于0
	amplitude = amplitude + 1;
	log(amplitude, amplitude);//求自然對數

	//與原影像尺寸對應的區域								
	amplitude = amplitude(Rect(T, L, gray.cols, gray.rows));
	normalize(amplitude, amplitude, 0, 1, NORM_MINMAX);  //歸一化
	imshow("傅里葉變換結果幅值影像", amplitude);  //顯示結果

	//重新排列傅里葉影像中的象限,使得原點位于影像中心
	int centerX = amplitude.cols / 2;
	int centerY = amplitude.rows / 2;
	//分解成四個小區域
	Mat Qlt(amplitude, Rect(0, 0, centerX, centerY));//ROI區域的左上
	Mat Qrt(amplitude, Rect(centerX, 0, centerX, centerY));//ROI區域的右上
	Mat Qlb(amplitude, Rect(0, centerY, centerX, centerY));//ROI區域的左下
	Mat Qrb(amplitude, Rect(centerX, centerY, centerX, centerY));//ROI區域的右下

	//交換象限,左上和右下進行交換
	Mat med;
	Qlt.copyTo(med);
	Qrb.copyTo(Qlt);
	med.copyTo(Qrb);
	//交換象限,左下和右上進行交換
	Qrt.copyTo(med);
	Qlb.copyTo(Qrt);
	med.copyTo(Qlb);

	imshow("中心化后的幅值影像", amplitude);
	waitKey(0);
	return 0;
}

8.1.2 傅里葉變換進行卷積

dft()

mulSpectrums()

代碼清單8-8 通過傅里葉變換進行卷積

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	Mat grayfloat = Mat_<float>(gray);  //更改影像資料型別為float
	Mat kernel = (Mat_<float>(5, 5) << 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1,
		1, 1, 1, 1, 1,
		1, 1, 1, 1, 1,
		1, 1, 1, 1, 1);
	//構建輸出影像
	Mat result;
	int rwidth = abs(grayfloat.rows - kernel.rows) + 1;
	int rheight = abs(grayfloat.cols - kernel.cols) + 1;
	result.create(rwidth, rheight, grayfloat.type());

	// 計算最優離散傅里葉變換尺寸
	int width = getOptimalDFTSize(grayfloat.cols + kernel.cols - 1);
	int height = getOptimalDFTSize(grayfloat.rows + kernel.rows - 1);

	//改變輸入影像尺寸
	Mat tempA;
	int A_T = 0;
	int A_B = width - grayfloat.rows;
	int A_L = 0;
	int A_R = height - grayfloat.cols;
	copyMakeBorder(grayfloat, tempA, 0, A_B, 0, A_R, BORDER_CONSTANT);

	//改變濾波器尺寸
	Mat tempB;
	int B_T = 0;
	int B_B = width - kernel.rows;
	int B_L = 0;
	int B_R = height - kernel.cols;
	copyMakeBorder(kernel, tempB, 0, B_B, 0, B_R, BORDER_CONSTANT);

	//分別進行離散傅里葉變換
	dft(tempA, tempA, 0, grayfloat.rows);
	dft(tempB, tempB, 0, kernel.rows);

	//多個傅里葉變換的結果相乘
	mulSpectrums(tempA, tempB, tempA, DFT_COMPLEX_OUTPUT);

	//相乘結果進行逆變換
	//dft(tempA, tempA, DFT_INVERSE | DFT_SCALE, result.rows);
	idft(tempA, tempA, DFT_SCALE, result.rows);

	//對逆變換結果進行歸一化
	normalize(tempA, tempA, 0, 1, NORM_MINMAX);

	//截取部分結果作為濾波結果
	tempA(Rect(0, 0, result.cols, result.rows)).copyTo(result);

	//顯示結果
	imshow("原影像", gray);
	imshow("濾波結果", result);
	waitKey(0);
}

8.1.3 離散余弦變換

dct()

idct()

代碼清單8-11 影像離散余弦變換

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

using namespace cv;
using namespace std;

int main()
{
	Mat kernel = (Mat_<float>(5, 5) << 1, 2, 3, 4, 5,
		                               2, 3, 4, 5, 6,
		                               3, 4, 5, 6, 7,
		                               4, 5, 6, 7, 8,
		                               5, 6, 7, 8, 9);
	Mat a, b;
	dct(kernel, a);
	idct(a, b);

	//對影像進行處理
	Mat img = imread("lena.png");
	if (!img.data)
	{
		cout << "讀入影像出錯,請確認影像名稱是否正確" << endl;
		return -1;
	}
	imshow("原影像", img);
	
	//計算最佳變換尺寸
	int width = 2 * getOptimalDFTSize((img.cols + 1) / 2);
	int height = 2 * getOptimalDFTSize((img.rows + 1) / 2);
	
	//擴展影像尺寸
	int T = 0;
	int B = height - T - img.rows;
	int L = 0;
	int R = width - L - img.rows;
	Mat appropriate;
	copyMakeBorder(img, appropriate, T, B, L, R, BORDER_CONSTANT, Scalar(0));

	//提三個通道需要分別進行DCT變換
	vector<Mat> channels;
	split(appropriate, channels);

	//提取NGR顏色各個通道的值
	Mat one = channels.at(0);	
	Mat two = channels.at(1);
	Mat three = channels.at(2);

	//進行DCT變換
	Mat oneDCT, twoDCT, threeDCT;
	dct(Mat_<float>(one), oneDCT);
	dct(Mat_<float>(two), twoDCT);
	dct(Mat_<float>(three), threeDCT);

	//重新組成三個通道
	vector<Mat> channelsDCT;
	channelsDCT.push_back(Mat_<uchar>(oneDCT));
	channelsDCT.push_back(Mat_<uchar>(twoDCT));
	channelsDCT.push_back(Mat_<uchar>(threeDCT));

	//輸出影像
	Mat result;
	merge(channelsDCT, result);
	imshow("DCT影像", result);
	waitKey();
	return 0;
}

8.2 積分影像

integral()

代碼清單8-15 計算積分影像

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

using namespace cv;
using namespace std;

int main()
{
	//創建一個16×16全為1的矩陣,因為256=16×16
	Mat img = Mat::ones(16, 16, CV_32FC1);

	//在影像中加入隨機噪聲
	RNG rng(10086);
	for (int y = 0; y < img.rows; y++)
	{
		for (int x = 0; x < img.cols; x++)
		{
			float d = rng.uniform(-0.5, 0.5);
			img.at<float>(y, x) = img.at<float>(y, x) + d;
		}
	}
	
	//計算標準求和積分
	Mat sum;
	integral(img, sum);
	//為了便于顯示,轉成CV_8U格式
	Mat sum8U = Mat_<uchar>(sum);

	//計算平方求和積分
	Mat sqsum;
	integral(img, sum, sqsum);
	//為了便于顯示,轉成CV_8U格式
	Mat sqsum8U = Mat_<uchar>(sqsum);

	//計算傾斜求和積分
	Mat tilted;
	integral(img, sum, sqsum, tilted);
	//為了便于顯示,轉成CV_8U格式
	Mat tilted8U = Mat_<uchar>(tilted);

	//輸出結果
	namedWindow("sum8U", WINDOW_NORMAL);
	namedWindow("sqsum8U", WINDOW_NORMAL);
	namedWindow("tilted8U", WINDOW_NORMAL);
	imshow("sum8U", sum8U);
	imshow("sqsum8U", sqsum8U);
	imshow("tilted8U", tilted8U);

	waitKey();
	return 0;
}

8.3 影像分割

8.3.1 漫水填充法

floodFill()

代碼清單8-18 漫水填充法分割影像

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

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //將DOS界面調成白底黑字
	Mat img = imread("lena.png");
	if (!(img.data))
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}

	
	RNG rng(10086);//亂數,用于隨機生成像素
	
	//設定操作標志flags
	int connectivity = 4;  //連通鄰域方式
	int maskVal = 255;  //掩碼影像的數值
	int flags = connectivity|(maskVal<<8)| FLOODFILL_FIXED_RANGE;  //漫水填充操作方式標志

	//設定與選中像素點的差值
	Scalar loDiff = Scalar(20, 20, 20);
	Scalar upDiff = Scalar(20, 20, 20);

	//宣告掩模矩陣變數
	Mat mask = Mat::zeros(img.rows + 2, img.cols + 2, CV_8UC1);

	while (true)
	{
		//隨機產生影像中某一像素點
		int py = rng.uniform(0,img.rows-1);
		int px = rng.uniform(0, img.cols - 1);
		Point point = Point(px, py);
		
		//彩色影像中填充的像素值
		Scalar newVal = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));

		//漫水填充函式
		int area = floodFill(img, mask, point, newVal, &Rect(),loDiff,upDiff,flags);
	
		//輸出像素點和填充的像素數目
		cout << "像素點x:" << point.x << "  y:" << point.y
			<< "     填充像數數目:" << area << endl;

		//輸出填充的影像結果
		imshow("填充的彩色影像", img);
		imshow("掩模影像", mask);

		//判斷是否結束程式
		int c = waitKey(0);
		if ((c&255)==27)
		{
			break;
		}
	}
	return 0;
}

8.3.2 分水嶺法

watershed()

代碼清單8-20 分水嶺法分割影像

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

using namespace std;
using namespace cv;

int main()
{
	Mat img, imgGray, imgMask;
	Mat maskWaterShed;  // watershed()函式的引數
	img = imread("HoughLines.jpg");  //原影像
	if (img.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	cvtColor(img, imgGray, COLOR_BGR2GRAY);
	//GaussianBlur(imgGray, imgGray, Size(5, 5), 10, 20);  //模糊用于減少邊緣數目

	//提取邊緣并進行閉運算
	Canny(imgGray, imgMask, 150, 300);
	//Mat k = getStructuringElement(0, Size(3, 3));
	//morphologyEx(imgMask, imgMask, MORPH_CLOSE, k);

	imshow("邊緣影像", imgMask);
	imshow("原影像", img);

	//計算連通域數目
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(imgMask, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);

	//在maskWaterShed上繪制輪廓,用于輸入分水嶺演算法
	maskWaterShed = Mat::zeros(imgMask.size(), CV_32S);
	for (int index = 0; index < contours.size(); index++)
	{
		drawContours(maskWaterShed, contours, index, Scalar::all(index + 1),
			-1, 8, hierarchy, INT_MAX);
	}
	//分水嶺演算法   需要對原影像進行處理
	watershed(img, maskWaterShed);

	vector<Vec3b> colors;  // 隨機生成幾種顏色
	for (int i = 0; i < contours.size(); i++)
	{
		int b = theRNG().uniform(0, 255);
		int g = theRNG().uniform(0, 255);
		int r = theRNG().uniform(0, 255);
		colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
	}

	Mat resultImg = Mat(img.size(), CV_8UC3);  //顯示影像
	for (int i = 0; i < imgMask.rows; i++)
	{
		for (int j = 0; j < imgMask.cols; j++)
		{
			// 繪制每個區域的顏色
			int index = maskWaterShed.at<int>(i, j);
			if (index == -1)  // 區域間的值被置為-1(邊界)
			{
				resultImg.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
			}
			else if (index <= 0 || index > contours.size())  // 沒有標記清楚的區域被置為0 
			{
				resultImg.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
			}
			else  // 其他每個區域的值保持不變:1,2,…,contours.size()
			{
				resultImg.at<Vec3b>(i, j) = colors[index - 1];  // 把些區域繪制成不同顏色
			}
		}
	}

	resultImg = resultImg * 0.6 + img * 0.4;
	imshow("分水嶺結果", resultImg);

	//繪制每個區域的影像
	for (int n = 1; n <= contours.size(); n++)
	{
		Mat resImage1 = Mat(img.size(), CV_8UC3);  // 宣告一個最后要顯示的影像
		for (int i = 0; i < imgMask.rows; i++)
		{
			for (int j = 0; j < imgMask.cols; j++)
			{
				int index = maskWaterShed.at<int>(i, j);
				if (index == n)
					resImage1.at<Vec3b>(i, j) = img.at<Vec3b>(i, j);
				else
					resImage1.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
			}
		}
		//顯示影像
		imshow(to_string(n), resImage1);
	}

	waitKey(0);
	return 0;
}

8.3.3 Grabcut 法

grabCut()

代碼清單8-22 利用Grabcut 法進行影像分割

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("lena.png");
	if (!img.data)  //防止錯誤讀取影像
	{
		cout<<"讀取影像錯誤,請確認影像檔案是否正確" << endl; 
		return 0;
	}

	//繪制矩形
	Mat imgRect;
	img.copyTo(imgRect);  //備份影像,方式繪制矩形框對結果產生影響
	Rect rect(80, 30, 340, 390);
	rectangle(imgRect, rect, Scalar(255, 255, 255),2);
	imshow("選擇的矩形區域", imgRect);

	//進行分割
	Mat bgdmod = Mat::zeros(1, 65, CV_64FC1);
	Mat fgdmod = Mat::zeros(1, 65, CV_64FC1);
	Mat mask = Mat::zeros(img.size(), CV_8UC1);
	grabCut(img, mask, rect, bgdmod, fgdmod, 5, GC_INIT_WITH_RECT);
	
	//將分割出的前景繪制回來
	Mat result;
	for (int row = 0; row < mask.rows; row++) 
	{
		for (int col = 0; col < mask.cols; col++) 
		{
			int n = mask.at<uchar>(row, col);
			//將明顯是前景和可能是前景的區域都保留
			if (n == 1 || n == 3) 
			{
				mask.at<uchar>(row, col) = 255;
			}
			//將明顯是背景和可能是背景的區域都洗掉
			else 
			{
				mask.at<uchar>(row, col) = 0;
			}
		}
	}
	bitwise_and(img, img, result, mask);
	imshow("分割結果", result);
	waitKey(0);
	return 0;
}

8.3.4 Mean-Shift 法

pyrMeanShiftFiltering()

TermCriteria建構式

代碼清單8-25 利用Mean-Shift 法分割影像

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("coins.png");
	if (!img.data)
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}

	//分割處理
	Mat result1, result2;
	TermCriteria T10 = TermCriteria(TermCriteria::COUNT | TermCriteria::EPS, 10, 0.1);
	pyrMeanShiftFiltering(img, result1, 20, 40, 2, T10);  //第一次分割
	pyrMeanShiftFiltering(result1, result2, 20, 40, 2, T10);  //第一次分割的結果再次分割

															  //顯示分割結果
	imshow("img", img);
	imshow("result1", result1);
	imshow("result2", result2);

	//對影像提取Canny邊緣
	Mat imgCanny, result1Canny, result2Canny;
	Canny(img, imgCanny, 150, 300);
	Canny(result1, result1Canny, 150, 300);
	Canny(result2, result2Canny, 150, 300);

	//顯示邊緣檢測結果
	imshow("imgCanny", imgCanny);
	imshow("result1Canny", result1Canny);
	imshow("result2Canny", result2Canny);
	waitKey(0);
	return 0;
}

8.4 影像修復

inpaint()

代碼清單8-27 影像修復

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

using namespace cv;
using namespace std;

int main()
{
	Mat img1 = imread("inpaint1.png");
	Mat img2 = imread("inpaint2.png");
	if (img1.empty() || img2.empty())
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}
	imshow("img1", img1);
	imshow("img2", img2);

	//轉換為灰度圖
	Mat img1Gray, img2Gray;
	cvtColor(img1, img1Gray, COLOR_RGB2GRAY, 0);
	cvtColor(img2, img2Gray, COLOR_RGB2GRAY, 0);

	//通過閾值處理生成Mask掩模
	Mat img1Mask, img2Mask;
	threshold(img1Gray, img1Mask, 245, 255, THRESH_BINARY);
	threshold(img2Gray, img2Mask, 245, 255, THRESH_BINARY);

	//對Mask膨脹處理,增加Mask面積
	Mat Kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
	dilate(img1Mask, img1Mask, Kernel);
	dilate(img2Mask, img2Mask, Kernel);

	//影像修復
	Mat img1Inpaint, img2Inpaint;
	inpaint(img1, img1Mask, img1Inpaint, 5, INPAINT_NS);
	inpaint(img2, img2Mask, img2Inpaint, 5, INPAINT_NS);

	//顯示處理結果
	imshow("img1Mask", img1Mask);
	imshow("img1修復后", img1Inpaint);
	imshow("img2Mask", img2Mask);
	imshow("img2修復后", img2Inpaint);
	waitKey();
	return 0;
}

第九章 特征點檢測與匹配

9.1 角點檢測

9.1.1 顯示關鍵點

drawKeypoints()

Keypoint資料型別

代碼清單9-3 繪制關鍵點

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("lena.png", IMREAD_COLOR);
     //判斷加載影像是否存在
     if (!img.data)
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}
     

	Mat imgGray;
	cvtColor(img, imgGray, COLOR_BGR2GRAY);
	//生成關鍵點
	vector<KeyPoint> keypoints;
	RNG rng(10086);
	for (int i = 0; i < 100; i++)
	{
		float pty = rng.uniform(0, img.rows - 1);
		float ptx = rng.uniform(0, img.cols - 1);
		KeyPoint keypoint;  //對KeyPoint類進行賦值
		keypoint.pt.x = ptx;
		keypoint.pt.y = pty;
		keypoints.push_back(keypoint);  //保存進關鍵點向量中
	}

	//繪制關鍵點
	drawKeypoints(img, keypoints, img, Scalar(0, 0, 0));
	drawKeypoints(imgGray, keypoints, imgGray, Scalar(255, 255, 255));
	
	//顯示影像繪制結果
	imshow("img", img);
	imshow("imgGray", imgGray);
	waitKey(0);
	return 0;
}

9.1.2 Harris 角點檢測

cornerHarris()

代碼清單9-5 檢測Harris 角點

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

using namespace cv;
using namespace std;


int main()
{
	Mat img = imread("lena.png", IMREAD_COLOR);
	if (!img.data)
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}

	//轉成灰度影像
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);

	//計算Harris系數
	Mat harris;
	int blockSize = 2;  //鄰域半徑
	int apertureSize = 3;  //
	cornerHarris(gray, harris, blockSize, apertureSize, 0.04);
	
	//歸一化便于進行數值比較和結果顯示
	Mat harrisn;
	normalize(harris, harrisn, 0, 255, NORM_MINMAX);
	//將影像的資料型別變成CV_8U
	convertScaleAbs(harrisn, harrisn);
	
	//尋找Harris角點
	vector<KeyPoint> keyPoints;
	for (int row = 0; row < harrisn.rows; row++)
	{
		for (int col = 0; col < harrisn.cols; col++)
		{
			int R = harrisn.at<uchar>(row, col);
			if (R > 125)
			{
				//向角點存入KeyPoint中
				KeyPoint keyPoint;
				keyPoint.pt.y = row;
				keyPoint.pt.x = col;
				keyPoints.push_back(keyPoint);
			}
		}
	}

	//繪制角點與顯示結果
	drawKeypoints(img, keyPoints, img);
	imshow("系數矩陣", harrisn);
	imshow("Harris角點", img);
	waitKey(0);
	return 0;
}

9.1.3 Shi-Tomas 角點檢測

goodFeaturesToTrack()

代碼清單9-7 檢測Shi-Tomas 角點

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("lena.png");
	if (!img.data)
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}
	//深拷貝用于第二種方法繪制角點
	Mat img2;
	img.copyTo(img2);
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	// Detector parameters

	//提取角點
	int maxCorners = 100;  //檢測角點數目
	double quality_level = 0.01;  //質量等級,或者說閾值與最佳角點的比例關系
	double minDistance = 0.04;  //兩個角點之間的最小歐式距離
	vector<Point2f> corners;
	goodFeaturesToTrack(gray, corners, maxCorners, quality_level, minDistance, Mat(), 3, false);

	//繪制角點
	vector<KeyPoint> keyPoints;  //存放角點的KeyPoint類,用于后期繪制角點時用
	RNG rng(10086);
	for (int i = 0; i < corners.size(); i++) 
	{
		//第一種方式繪制角點,用circle()函式繪制角點
		int b = rng.uniform(0, 256);
		int g = rng.uniform(0, 256);
		int r = rng.uniform(0, 256);
		circle(img, corners[i], 5, Scalar(b, g, r), 2, 8, 0);

		//將角點存放在KeyPoint類中
		KeyPoint keyPoint;
		keyPoint.pt = corners[i];
		keyPoints.push_back(keyPoint);
	}

	//第二種方式繪制角點,用drawKeypoints()函式
	drawKeypoints(img2, keyPoints, img2);
	//輸出繪制角點的結果
	imshow("用circle()函式繪制角點結果", img);
	imshow("通過繪制關鍵點函式繪制角點結果", img2);
	waitKey(0);
	return 0;
}

9.1.4 亞像素級別角點檢測

cornerSubPix()

代碼清單9-9 計算亞像素級別角點坐標

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

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //改變DOS界面顏色
	Mat img = imread("lena.png",IMREAD_COLOR);
	if (!img.data)
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}
	//彩色影像轉成灰度影像
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);

	//提取角點
	int maxCorners = 100;  //檢測角點數目
	double quality_level = 0.01;  //質量等級,或者說閾值與最佳角點的比例關系
	double minDistance = 0.04;  //兩個角點之間的最小歐式距離
	vector<Point2f> corners;
	goodFeaturesToTrack(gray, corners, maxCorners, quality_level, minDistance, Mat(), 3, false);
	
	//計算亞像素級別角點坐標
	vector<Point2f> cornersSub = corners;  //角點備份,方式別函式修改
	Size winSize = Size(5, 5);
	Size zeroZone = Size(-1, -1);
	TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 40, 0.001);
	cornerSubPix(gray, cornersSub , winSize, zeroZone, criteria);

	//輸出初始坐標和精細坐標
	for (size_t i = 0; i < corners.size(); i++)
	{
		string str = to_string(i);
		str = "第" + str + "個角點點初始坐標:";
		cout << str << corners[i] << "   精細后坐標:" << cornersSub[i] << endl;
	}
	return 0;
}

9.2 特征點檢測

9.2.1關鍵點

KeyPoint()

detect()

9.2.2 描述子

compute()

detectAndCompute()

9.2.3 SIFT 特征點檢測

SIFT ::create()

9.2.4 SURF 特征點檢測

SURF::create()

代碼清單9-16 計算SURF 特征點

#include <opencv2\opencv.hpp>
#include <xfeatures2d.hpp>  //SURF特征點頭檔案
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;  
using namespace xfeatures2d;  //SURF特征點命名空間

int main()
{
	Mat img = imread("lena.png");
	if (!img.data)
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}

	//創建SURF特征點類變數
	Ptr<SURF> surf = SURF::create(500,  //關鍵點閾值
		                            4,  //4組金字塔
		                            3,  //每組金字塔有3層
		                         true,  //使用128維描述子
		                        false);  //計算關鍵點方向

	//計算SURF關鍵點
	vector<KeyPoint> Keypoints;
	surf->detect(img, Keypoints);  //確定關鍵點

	//計算SURF描述子
	Mat descriptions;
	surf->compute(img, Keypoints, descriptions);  //計算描述子
	
	//繪制特征點
	Mat imgAngel;
	img.copyTo(imgAngel);
	//繪制不含角度和大小的結果
	drawKeypoints(img, Keypoints, img,Scalar(255,255,255));
	//繪制含有角度和大小的結果
	drawKeypoints(img, Keypoints, imgAngel, Scalar(255, 255, 255),DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

	//顯示結果
	imshow("不含角度和大小的結果", img);
	imshow("含有角度和大小的結果", imgAngel);
	waitKey(0); 
	return 0;
}

9.2.5 ORB特征點檢測

ORB::create()

ORB::HARRIS_SCORE

代碼清單9-18 計算ORB特征點

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("lena.png");
	if (!img.data)
	{
		cout << "請確認影像檔案名稱是否正確" << endl;
		return -1;
	}

	//創建 ORB 特征點類變數
	Ptr<ORB> orb = ORB::create(500, //特征點數目
		1.2f, //金字塔層級之間的縮放比例
		8, //金字塔影像層數系數
		31, //邊緣閾值
		0, //原圖在金字塔中的層數
		2, //生成描述子時需要用的像素點數目
		ORB::HARRIS_SCORE, //使用 Harris 方法評價特征點
		31, //生成描述子時關鍵點周圍鄰域的尺寸
		20 //計算 FAST 角點時像素值差值的閾值
	);

	//計算 ORB 關鍵點
	vector<KeyPoint> Keypoints;
	orb->detect(img, Keypoints); //確定關鍵點

								 //計算 ORB 描述子
	Mat descriptions;
	orb->compute(img, Keypoints, descriptions); //計算描述子

												//繪制特征點
	Mat imgAngel;
	img.copyTo(imgAngel);
	//繪制不含角度和大小的結果
	drawKeypoints(img, Keypoints, img, Scalar(255, 255, 255));
	//繪制含有角度和大小的結果
	drawKeypoints(img, Keypoints, imgAngel, Scalar(255, 255, 255), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

	//顯示結果

	imshow("不含角度和大小的結果", img);
	imshow("含有角度和大小的結果", imgAngel);
	waitKey(0);
	return 0;
}

9.3 特征點匹配

9.3.1 DescriptorMatcher類介紹

DescriptorMatcher::match()

DMatch類

DescriptorMatcher::knnMatch()

DescriptorMatcher::radiusMatch()

9.3.2 暴力匹配

BFMatcher建構式

9.3.3 顯示特征點匹配結果

drawMatches()

代碼清單9-25 ORB特征點暴力匹配

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;  

void orb_features(Mat &gray, vector<KeyPoint> &keypionts, Mat &descriptions)
{
	Ptr<ORB> orb = ORB::create(1000, 1.2f);
	orb->detect(gray, keypionts);
	orb->compute(gray, keypionts, descriptions);
}

int main()
{
	Mat img1, img2;  
	img1 = imread("box.png");  
	img2 = imread("box_in_scene.png");

	if (!(img1.data && img2.dataend))
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}

	//提取ORB特征點
	vector<KeyPoint> Keypoints1, Keypoints2;
	Mat descriptions1, descriptions2;

	//計算特征點
	orb_features(img1, Keypoints1, descriptions1);
	orb_features(img2, Keypoints2, descriptions2);

	//特征點匹配
	vector<DMatch> matches;  //定義存放匹配結果的變數
	BFMatcher matcher(NORM_HAMMING);  //定義特征點匹配的類,使用漢明距離
	matcher.match(descriptions1, descriptions2, matches);  //進行特征點匹配
	cout << "matches=" << matches.size() << endl;  //匹配成功特征點數目
	
	//通過漢明距離刪選匹配結果
	double min_dist = 10000, max_dist = 0;
	for (int i = 0; i < matches.size(); i++)
	{
		double dist = matches[i].distance;
		if (dist < min_dist) min_dist = dist;
		if (dist > max_dist) max_dist = dist;
	}

	//輸出所有匹配結果中最大韓明距離和最小漢明距離
	cout << "min_dist=" << min_dist << endl;
	cout << "max_dist=" << max_dist << endl;

	//將漢明距離較大的匹配點對洗掉
	vector<DMatch>  good_matches;
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance <= max(2 * min_dist, 20.0))
		{
			good_matches.push_back(matches[i]);
		}
	}
	cout << "good_min=" << good_matches.size() << endl;  //剩余特征點數目

	//繪制匹配結果
	Mat outimg, outimg1;
	drawMatches(img1, Keypoints1, img2, Keypoints2, matches, outimg);
	drawMatches(img1, Keypoints1, img2, Keypoints2, good_matches, outimg1);
	imshow("未篩選結果", outimg);
	imshow("最小漢明距離篩選", outimg1);

	waitKey(0);  
	return 0; 
}

9.3.4 FLANN匹配

FlannBasedMatcher建構式

代碼清單9-27 用FLANN方法匹配特征點

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

void orb_features(Mat &gray, vector<KeyPoint> &keypionts, Mat &descriptions)
{
	Ptr<ORB> orb = ORB::create(1000, 1.2f);
	orb->detect(gray, keypionts);
	orb->compute(gray, keypionts, descriptions);
}

int main()
{
	Mat img1, img2;
	img1 = imread("box.png");
	img2 = imread("box_in_scene.png");

	if (!(img1.data && img2.dataend))
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}

	//提取ORB特征點
	vector<KeyPoint> Keypoints1, Keypoints2;
	Mat descriptions1, descriptions2;

	//計算SURF特征點
	orb_features(img1, Keypoints1, descriptions1);
	orb_features(img2, Keypoints2, descriptions2);

	//判斷描述子資料型別,如果資料型別不符需要進行型別轉換,主要針對ORB特征點
	if ((descriptions1.type() != CV_32F) && (descriptions2.type() != CV_32F))
	{
		descriptions1.convertTo(descriptions1, CV_32F);
		descriptions2.convertTo(descriptions2, CV_32F);
	}

	//特征點匹配
	vector<DMatch> matches;  //定義存放匹配結果的變數
	FlannBasedMatcher matcher;  //使用默認值即可
	matcher.match(descriptions1, descriptions2, matches);
	cout << "matches=" << matches.size() << endl;  //匹配成功特征點數目


												   //尋找距離最大值和最小值,如果是ORB特征點min_dist取值需要大一些
	double max_dist = 0; double min_dist = 100;
	for (int i = 0; i < descriptions1.rows; i++)
	{
		double dist = matches[i].distance;
		if (dist < min_dist) min_dist = dist;
		if (dist > max_dist) max_dist = dist;
	}
	cout << " Max dist :" << max_dist << endl;
	cout << " Min dist :" << min_dist << endl;

	//將最大值距離的0.4倍作為最優匹配結果進行篩選
	std::vector< DMatch > good_matches;
	for (int i = 0; i < descriptions1.rows; i++)
	{
		if (matches[i].distance < 0.40 * max_dist)
		{
			good_matches.push_back(matches[i]);
		}
	}
	cout << "good_matches=" << good_matches.size() << endl;  //匹配成功特征點數目

															 //繪制匹配結果
	Mat outimg, outimg1;
	drawMatches(img1, Keypoints1, img2, Keypoints2, matches, outimg);
	drawMatches(img1, Keypoints1, img2, Keypoints2, good_matches, outimg1);
	imshow("未篩選結果", outimg);
	imshow("篩選結果", outimg1);

	waitKey(0);
	return 0;
}

9.3.5 RANSAC 優化特征點匹配

findHomography()

代碼清單9-29 RANSAC 演算法優化特征點匹配結果

#include <iostream>
#include <opencv2\opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;  

void match_min(vector<DMatch> matches, vector<DMatch> & good_matches)
{
	double min_dist = 10000, max_dist = 0;
	for (int i = 0; i < matches.size(); i++)
	{
		double dist = matches[i].distance;
		if (dist < min_dist) min_dist = dist;
		if (dist > max_dist) max_dist = dist;
	}
	cout << "min_dist=" << min_dist << endl;
	cout << "max_dist=" << max_dist << endl;

	for (int i = 0; i < matches.size(); i++)
		if (matches[i].distance <= max(2 * min_dist, 20.0))
			good_matches.push_back(matches[i]);
}

//RANSAC演算法實作
void ransac(vector<DMatch> matches, vector<KeyPoint> queryKeyPoint, vector<KeyPoint> trainKeyPoint, vector<DMatch> &matches_ransac)
{
	//定義保存匹配點對坐標
	vector<Point2f> srcPoints(matches.size()), dstPoints(matches.size());
	//保存從關鍵點中提取到的匹配點對的坐標
	for (int i = 0; i<matches.size(); i++)
	{
		srcPoints[i] = queryKeyPoint[matches[i].queryIdx].pt;
		dstPoints[i] = trainKeyPoint[matches[i].trainIdx].pt;
	}
	
	//匹配點對進行RANSAC過濾
	vector<int> inliersMask(srcPoints.size());
	//Mat homography;
	//homography = findHomography(srcPoints, dstPoints, RANSAC, 5, inliersMask);
	findHomography(srcPoints, dstPoints, RANSAC, 5, inliersMask);
	//手動的保留RANSAC過濾后的匹配點對
	for (int i = 0; i<inliersMask.size(); i++)
		if (inliersMask[i])
			matches_ransac.push_back(matches[i]);
}

void orb_features(Mat &gray, vector<KeyPoint> &keypionts, Mat &descriptions)
{
	Ptr<ORB> orb = ORB::create(1000, 1.2f);
	orb->detect(gray, keypionts);
	orb->compute(gray, keypionts, descriptions);
}

int main()
{
	Mat img1 = imread("box.png");  //讀取影像,根據圖片所在位置填寫路徑即可
	Mat img2 = imread("box_in_scene.png");
	if (!(img1.data && img2.data))
	{
		cout << "讀取影像錯誤,請確認影像檔案是否正確" << endl;
		return -1;
	}

	//提取ORB特征點
	vector<KeyPoint> Keypoints1, Keypoints2;
	Mat descriptions1, descriptions2;

	//基于區域分割的ORB特征點提取
	orb_features(img1, Keypoints1, descriptions1);
	orb_features(img2, Keypoints2, descriptions2);

	//特征點匹配
	vector<DMatch> matches, good_min,good_ransac;
	BFMatcher matcher(NORM_HAMMING);
	matcher.match(descriptions1, descriptions2, matches);
	cout << "matches=" << matches.size() << endl;

	//最小漢明距離
	match_min(matches, good_min);
	cout << "good_min=" << good_min.size() << endl;

	//用ransac演算法篩選匹配結果
	ransac(good_min, Keypoints1, Keypoints2, good_ransac);
	cout << "good_matches.size=" << good_ransac.size() << endl;

	//繪制匹配結果
	Mat outimg, outimg1, outimg2;
	drawMatches(img1, Keypoints1, img2, Keypoints2, matches, outimg);
	drawMatches(img1, Keypoints1, img2, Keypoints2, good_min, outimg1);
	drawMatches(img1, Keypoints1, img2, Keypoints2, good_ransac, outimg2);
	imshow("未篩選結果", outimg);
	imshow("最小漢明距離篩選", outimg1);
	imshow("ransac篩選", outimg2);
	waitKey(0);  //等待鍵盤輸入
	return 0;  //程式結束
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/292275.html

標籤:其他

上一篇:程式員因開發速度太慢而遭公司起訴,索賠金額高達90萬!

下一篇:vue實作百度語音播報API呼叫

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more