第二章 資料載入、顯示與保存
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 顏色模型與轉換
- RGB顏色模型
- YUV顏色模型
- HSV顏色模型
- Lab顏色模型
- 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呼叫
