--------------------------------
Author : ShawnDong
updateDate :2018.11.28
Blog : ShawnDong98.github.io
--------------------------------
概述
什么是圖像特征
可以表達(dá)圖像中對(duì)象的主要信息大渤,并且以此為依據(jù)可以從其他未知圖像中檢測(cè)出相似或者相同對(duì)象
常見的圖像特征
- 邊緣
- 角點(diǎn)
- 紋理
提取方法
描述子生成
**特征提取與描述
- SIFT
- SURF
- HOG
- Haar
- LBP
- KAZE
- AKAZE
- BRISK
對(duì)特征檢測(cè)的描述可以簡單總結(jié)為DDM
- Detection:檢測(cè)對(duì)象
- Description:描述對(duì)象
- Matching:匹配對(duì)象
Haaris角點(diǎn)檢測(cè)
Harris角點(diǎn)檢測(cè)理論(1998提出)
最核心的部分:
參數(shù)說明
void cv::cornerHarris(
InputArray src,
OutputArray dst,
int blockSize,
int ksize,
double k,
int borderType = BORDER_DEFAULT
)
blockSize:計(jì)算lambda1和lambda2乘積時(shí)的矩陣大小
Kszie: 窗口大小
K: 表示計(jì)算角度相應(yīng)時(shí)候的參數(shù)大小疯潭,默認(rèn)在0.04~0.06
閾值t, 用來過濾角度響應(yīng)
代碼演示
void Harris_Demo(int, void*)
{
Mat norm_dst, normScaleDst;
dst = Mat::zeros(gray_src.size(), CV_32FC1);
int blockSize = 2;
int ksize = 3;
double k = 0.04;
cornerHarris(gray_src, dst, blockSize, ksize, k, BORDER_DEFAULT);
normalize(dst, norm_dst, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
convertScaleAbs(norm_dst, normScaleDst);
Mat resultImg = src.clone();
for (int row = 0; row < resultImg.rows; row++)
{
uchar* currentRow = normScaleDst.ptr(row);
for (int col = 0; col < resultImg.cols; col++)
{
int value = (int)* currentRow;
if (value > thresh)
{
circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
}
currentRow++;
}
}
imshow(output_title, resultImg);
}
突發(fā)奇想:RGB圖片是如何轉(zhuǎn)化為灰度圖像的懒棉?
總的來說RGB圖像是有3個(gè)通道巷懈,也就是一個(gè)3維的矩陣该抒,而灰度圖,大家都知道只有一個(gè)通道顶燕,那么如何將一個(gè)3通道的事物轉(zhuǎn)為1通道的事物呢凑保?
其實(shí)這其中是有一個(gè)轉(zhuǎn)換公式的,簡單來說涌攻,就是把RGB3個(gè)通道的分量按照一定的比例計(jì)算到灰度圖像中欧引。即 公式所闡述的那樣,Gray = R0.299 + G0.587 + B*0.114
Shi-Tomasi角點(diǎn)檢測(cè)
Shi-Tomasi角點(diǎn)檢測(cè)理論(94)
和Harris角點(diǎn)檢測(cè)大致相似癣漆,取lambda1和lambda2中的最小值维咸,計(jì)算量小。
參數(shù)說明
void cv::goodFeatureToTrack(
InputArray image,
OutputArray corners,
int maxCorners,
double qualityLevel,
InputArray mask = noArray(),
int blockSize,
bool useHarrisDectector = false,
double k = 0.04
)
maxCorners:表示返回角點(diǎn)的數(shù)目惠爽,如果檢測(cè)出來角點(diǎn)數(shù)目大于最大數(shù)目癌蓖,則返回最大數(shù)目
qualityLevel: 表示最小可接受的向量值1500, 0.01婚肆, 15
minDistance: 兩個(gè)角點(diǎn)之間的最小距離(歐幾里得距離)
blocksize: 計(jì)算導(dǎo)數(shù)微分不同的窗口大小
useHarrisDetector:是否使用Harris角點(diǎn)檢測(cè)
代碼演示
void ShiTomasi_Demo(int, void*)
{
if (num_corners < 5)
{
num_corners = 5;
}
vector<Point2f>corners;
double qualityLevel = 0.01;
double minDistance = 10;
int blockSize = 3;
bool useHarris = false;
double k = 0.04;
Mat resultImg = src.clone();
goodFeaturesToTrack(gray_src, corners, num_corners, qualityLevel, minDistance, Mat(), blockSize, useHarris, k);
printf("Number of Detected Corners: %d\n", corners.size());
for (size_t t = 0; t < corners.size(); t++)
{
circle(resultImg, corners[t], 2, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 2, 8, 0);
}
imshow(output_title, resultImg);
}
自定義角點(diǎn)檢測(cè)器
自定義角點(diǎn)檢測(cè)器
- 基于Harris與Shi-Tomasi角點(diǎn)檢測(cè)
- 首先通過計(jì)算矩陣M得到lambda1和lambda2兩個(gè)特征值根據(jù)他們得到角點(diǎn)響應(yīng)值
- 然后自己設(shè)置閾值實(shí)現(xiàn)計(jì)算出閾值得到有效響應(yīng)值的角點(diǎn)位置
相關(guān)API說明
void cv::cornerEigenValsAndVecs(
InputArray src,
OutputArray dst,
int blockSize,
int ksize,
int boderType=BODER_DEFAULT
)
void cv::cornerMinEigenVal(
InputArray src,
OutputArray dst,
int blockSize,
int ksize = 3,
int boderType=BODER_DEFAULT
)
自定義harris特征檢測(cè)
//自定義Harris角點(diǎn)檢測(cè)
cvtColor(src1, gray_src, COLOR_BGR2GRAY);
//計(jì)算特征值
int blockSize = 3;
double k = 0.04;
int ksize = 3;
harris_dst = Mat::zeros(src1.size(), CV_32FC(6));
harrisRspImg = Mat::zeros(src1.size(), CV_32FC1);
cornerEigenValsAndVecs(gray_src, harris_dst, blockSize, ksize, 4);
//計(jì)算響應(yīng)
for (int row = 0; row < harris_dst.rows; row++)
{
for (int col = 0; col < harris_dst.cols; col++)
{
double lambda1 = harris_dst.at<Vec6f>(row, col)[0];
double lambda2 = harris_dst.at<Vec6f>(row, col)[1];
harrisRspImg.at<float>(row, col) = lambda1 * lambda2 - k * pow((lambda1 + lambda2), 2);
}
}
minMaxLoc(harrisRspImg, &harris_min_rsp, &harris_max_rsp, 0, 0, Mat());
cout << harris_min_rsp << endl;
cout << harris_max_rsp << endl;
namedWindow(harris_win, CV_WINDOW_AUTOSIZE);
createTrackbar("Quality Value:", harris_win, &qualityLevel, max_count, CustomHarris_Demo);
CustomHarris_Demo(0, 0);
void CustomHarris_Demo(int, void*)
{
if (qualityLevel < 10)
{
qualityLevel = 10;
}
Mat resultImg = src1.clone();
float t = harris_min_rsp + (((double)qualityLevel) / max_count)*(harris_max_rsp - harris_min_rsp);
for (int row = 0; row < src1.rows; row++)
{
for (int col = 0; col < src1.cols; col++)
{
float v = harrisRspImg.at<float>(row, col);
if (v > t)
{
circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
}
}
}
imshow(harris_win, resultImg);
}
自定義ShiTomasi特征檢測(cè)
//計(jì)算最小特征值
int blockSize = 3;
double k = 0.04;
int ksize = 3;
cvtColor(src1, gray_src, COLOR_BGR2GRAY);
shiTomasiRsp = Mat::zeros(src1.size(), CV_32FC1);
cornerMinEigenVal(gray_src, shiTomasiRsp, blockSize, ksize, 4);
minMaxLoc(shiTomasiRsp, &shitomasi_min_rsp, &shitomasi_max_rsp, 0, 0, Mat());
namedWindow(shiTomasi_win, CV_WINDOW_AUTOSIZE);
createTrackbar("Quality:", shiTomasi_win, &sm_qualityLevel, max_count, CustomShiTomasi_Demo);
CustomShiTomasi_Demo(0, 0);
void CustomShiTomasi_Demo(int, void*)
{
if (sm_qualityLevel < 20)
{
sm_qualityLevel = 20;
}
Mat resultImg = src1.clone();
float t = shitomasi_min_rsp + (((double)sm_qualityLevel) / max_count)*(shitomasi_max_rsp - shitomasi_min_rsp);
for (int row = 0; row < src1.rows; row++)
{
for (int col = 0; col < src1.cols; col++)
{
float v = shiTomasiRsp.at<float>(row, col);
if (v > t)
{
circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
}
}
}
imshow(shiTomasi_win, resultImg);
}
亞像素級(jí)別角點(diǎn)檢測(cè)
提高檢測(cè)精準(zhǔn)度
理論與現(xiàn)實(shí)總是不一致的租副,實(shí)際情況下幾乎所有的角點(diǎn)都不會(huì)是一個(gè)真正的準(zhǔn)確像素點(diǎn)。(100较性, 5)實(shí)際上(100.234用僧, 5.789)
亞像素定位
- 插值方法
- 基于圖像矩計(jì)算
- 曲線擬合方法-(高斯曲面、多項(xiàng)式赞咙、橢圓曲面)
代碼演示
void SubPixel_Demo(int, void*)
{
if (max_corners < 5)
{
max_corners = 5;
}
vector<Point2f> corners;
double qualityLevel = 0.01;
double minDistance = 10;
int blockSize = 3;
double k = 0.04;
cvtColor(src, gray_src, CV_BGR2GRAY);
goodFeaturesToTrack(gray_src, corners, max_corners, qualityLevel, minDistance, Mat(), blockSize, false, k);
cout << "number of corners: " << corners.size() << endl;
Mat resultImg = src.clone();
for (size_t t = 0; t < corners.size(); t++)
{
circle(resultImg, corners[t], 2, Scalar(0, 0, 255), 2, 8, 0);
}
imshow(output_title, resultImg);
Size winSize = Size(5, 5);
Size zerozone = Size(-1, -1);
TermCriteria tc = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);
cornerSubPix(gray_src, corners, winSize, zerozone, tc);
for (size_t t = 0; t < corners.size(); t++)
{
cout << (t + 1) << "point[x, y]: " << corners[t].x << "," << corners[t].y << endl;
}
}
SURF特征檢測(cè)
SURF(Speeded Up Rpbust Features)特征關(guān)鍵特性:
- 特征檢測(cè)
- 尺度空間
- 選擇不變性
- 特征向量
工作原理
- 選擇圖像中的POI(Point of Interest) Hessian Matrix
- 在不同的尺度空間發(fā)現(xiàn)關(guān)鍵點(diǎn)责循,非最大信號(hào)抑制
- 發(fā)現(xiàn)特征點(diǎn)方法、旋轉(zhuǎn)不變性要求
- 生成特征向量
Hessian矩陣
積分向量
相關(guān)API
cv::xfeatures2d::SURF::create(
double hessian Threshold = 100,
int nOctaves = 4,
int cOctaveLayers = 3,
bool extended = false,
bool upright = false
)
- upright:0表示計(jì)算選擇不變性攀操, 1表示不計(jì)算院仿, 速度更快
- HessianThreshold 默認(rèn)值在300~500之間
- Octave: 4表示在四個(gè)尺度空間
- OctaveLayers: 表示每個(gè)尺度的層數(shù)
代碼演示
//SURF特征檢測(cè)
int minHessian = 100;
Ptr<SURF> detector = SURF::create(minHessian);
vector<KeyPoint> keypoints;
detector->detect(src, keypoints, Mat());
//繪制關(guān)鍵點(diǎn)
Mat keypoint_img;
drawKeypoints(src, keypoints, keypoint_img, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow(output_title, keypoint_img);
SIFT特征檢測(cè)
SIFT(Scale-Invariant Feature Transform)特征檢測(cè)關(guān)鍵特性:
- 建立尺度空間,尋找最大值
工作原理:
1.構(gòu)建圖像高斯金字塔, 求取DOG歹垫, 發(fā)現(xiàn)每一級(jí)的最大與最小值剥汤。
2.構(gòu)建的高斯金字塔,每一層根據(jù)sigma的值不同排惨,可以分為多個(gè)等級(jí)吭敢,最少有四個(gè)
- 關(guān)鍵點(diǎn)定位(尋找關(guān)鍵點(diǎn)準(zhǔn)確位置與刪除弱邊緣)
1.我們?cè)谙袼丶?jí)別獲得了極值點(diǎn)的位置,但是更準(zhǔn)確的點(diǎn)應(yīng)該在亞像素位置暮芭,如何得到這個(gè)過程稱為關(guān)鍵點(diǎn)(準(zhǔn)確/精確)定位鹿驼。
2.刪除弱邊緣,通過Hessian矩陣特征值實(shí)現(xiàn),小于閾值自動(dòng)舍棄
- 關(guān)鍵點(diǎn)方向指定
1.根據(jù)給定的窗口大小幸逆,求得每一層對(duì)應(yīng)的圖像的梯度
2.計(jì)算每個(gè)高斯權(quán)重,sigma=scale1.5, 0~360之間建立36個(gè)直方圖Bins
3.找最高峰對(duì)應(yīng)的Bin桶癣, 大于max80%的都保留
4.實(shí)現(xiàn)了旋轉(zhuǎn)不變性,提高了匹配時(shí)候的穩(wěn)定性
5.大約有15%的關(guān)鍵點(diǎn)會(huì)有多個(gè)方向
- 關(guān)鍵點(diǎn)描述子
1.擬合多項(xiàng)式插值尋找最大Peak
2.得到描述子 = 448 = 128
相關(guān)API
cv::xfeature2d::SIFT::create(
int nfeatures = 0,
int nOctaveLayers = 3,
double contrastThreshold = 0.04,
double
代碼演示
//SIFT特征檢測(cè)
int numFeatures = 100;
Ptr<SIFT> detector = SIFT::create(numFeatures);
vector<KeyPoint> keypoints;
detector->detect(src, keypoints, Mat());
printf("Total KeyPoints: %d", keypoints.size());
//繪制特征點(diǎn)
Mat keypoint_img;
drawKeypoints(src, keypoints, keypoint_img, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
imshow(output_title, keypoint_img);
HOG特征檢測(cè)
HOG特征描述子提取
- 灰度圖像轉(zhuǎn)換
1.cvtColor
2.gray = R * 0.3 + 0.59 * G + 0.11 * B
- 梯度計(jì)算
-
垂直方向
-
水平方向
-
梯度
- 分網(wǎng)格的梯度方向直方圖
1.分割為8*8=64像素的Cell網(wǎng)格
2.對(duì)每個(gè)Cell求取方向直方圖(Orientation Hist)
每個(gè)Cell分為9個(gè)Bin筐乳,角度取值范圍為-180 ~ 180之間隘马, 對(duì)-180 ~ 0之間的加上180取正數(shù),對(duì)應(yīng)的值為梯度值扫步。方向?yàn)锽in數(shù)組0~9的Index
- 塊描述子
塊描述子R(Rectange)-HOG
將2x2的網(wǎng)格單元組合成為一個(gè)大的塊(Block)
對(duì)每個(gè)塊之間有1/2部分是重疊區(qū)域
主要是將每個(gè)Cell的直方圖合并成為一個(gè)大的直方圖(Bin索引不變[0~9]之間)
- 塊描述子歸一化
基于L2實(shí)現(xiàn)塊描述子歸一化魔策,歸一化因子計(jì)算
- 特征數(shù)據(jù)與檢測(cè)窗口
1.最終獲得HOG描述算子(特征數(shù)據(jù))
2.需要正向訓(xùn)練200個(gè)左右的特征樣本
3.反向訓(xùn)練600 ~ 800個(gè)左右的特征樣本
4.初步測(cè)試、開窗檢測(cè)
舉例:
對(duì)于64x128的像素塊河胎,可以分為8x16個(gè)Cell分為7x15個(gè)塊(R-HOG)闯袒,總計(jì)的直方圖向量數(shù)為:7x15x2x2x9 = 12
- 匹配方法
相關(guān)API
cv::HOGDescriptor::HOGDescriptor(
Size winSize,
Size blockSize,
Size blockStride,
Size cellSize,
int nbins,
...//不常用的參數(shù)
)
代碼演示
Mat dst, dst_gray;
resize(src, dst, Size(64, 128));
cvtColor(dst, dst_gray, COLOR_BGR2GRAY);
HOGDescriptor detector(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float> descriptors;
vector<Point> locations;
detector.compute(dst_gray, descriptors, Size(0, 0), Size(0, 0), locations);
printf("numbers of HOG descriptors: %d", descriptors.size());
HOGDescriptor hog = HOGDescriptor();
hog.setSVMDetector(hog.getDefaultPeopleDetector());
vector<Rect> foundLocations;
hog.detectMultiScale(src, foundLocations, 0, Size(8, 8), Size(32, 32), 1.05, 2);
Mat result = src.clone();
for (size_t t = 0; t < foundLocations.size(); t++)
{
rectangle(result, foundLocations[t], Scalar(0, 0, 255), 2, 8, 0);
}
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
imshow(output_title, result);
LBP(Local Binary Patterns)特征檢測(cè)
LBP特征介紹
LBP顧名思義,局部二值化游岳,也就是通過中心像素作為閾值政敢,進(jìn)行圖片的二值化。如圖所示胚迫,中心像素值為5喷户,比5小的全都賦為0,比5大的全都賦為1访锻。
由于直接利用的灰度比較褪尝,所以其具有灰度不變性;但是期犬,有兩個(gè)很明顯的缺點(diǎn):
1河哑、產(chǎn)生的二進(jìn)制模式多。
2龟虎、不具有旋轉(zhuǎn)不變性璃谨。
LBP表達(dá)
Pattern(模式):1001 1110
LBP: 1 + 2 + 4 + 8 + 16 + 32 +64 + 128 = 241, 不同位置像素權(quán)重之和
C(中心對(duì)比度): (6 + 7 + 8 + 9 + 7) / 5 - (5 + 2 + 1) / 3 = 4.7
LBP擴(kuò)展與多尺度表達(dá)
LBP統(tǒng)一模式
0->1或1->0的總跳數(shù)小于等于2則為統(tǒng)一模式
代碼演示
cvtColor(src, gray_src, COLOR_BGR2GRAY);
int width = gray_src.cols;
int height = gray_src.rows;
Mat lbpImage = Mat::zeros(gray_src.rows - 2, gray_src.cols - 2, CV_8UC1);
for (int row = 1; row < height - 1; row++)
{
for (int col = 1; col < width - 1; col++)
{
uchar c = gray_src.at<uchar>(row, col);
uchar code = 0;
code |= (gray_src.at<uchar>(row - 1, col - 1) > c) << 7;
code |= (gray_src.at<uchar>(row - 1, col) > c) << 6;
code |= (gray_src.at<uchar>(row - 1, col + 1) > c) << 5;
code |= (gray_src.at<uchar>(row, col + 1) > c) << 4;
code |= (gray_src.at<uchar>(row + 1, col + 1) > c) << 3;
code |= (gray_src.at<uchar>(row + 1, col) > c) << 2;
code |= (gray_src.at<uchar>(row + 1, col - 1) > c) << 1;
code |= (gray_src.at<uchar>(row, col - 1) > c) << 0;
lbpImage.at<uchar>(row - 1, col - 1) = code;
}
}
void ELBP_Demo(int, void*)
{
int offset = current_radius * 2;
Mat elbpImage = Mat::zeros(gray_src.rows - offset, gray_src.cols - offset, CV_8UC1);
int width = gray_src.cols;
int height = gray_src.rows;
int numNeighbors = 8;
for (int n = 0; n < numNeighbors; n++)
{
float x = static_cast<float>(current_radius) * cos(2.0 * CV_PI * n / static_cast<float>(numNeighbors));
float y = static_cast<float>(current_radius) * -sin(2.0 * CV_PI * n / static_cast<float>(numNeighbors));
int fx = static_cast<int>(floor(x));
int fy = static_cast<int>(floor(y));
int cx = static_cast<int>(ceil(x));
int cy = static_cast<int>(ceil(y));
float ty = y - fy;
float tx = x - fx;
float w1 = (1 - tx) * (1 - ty);
float w2 = tx * (1 - ty);
float w3 = (1 - tx) * ty;
float w4 = tx * ty;
for (int row = current_radius; row < (height - current_radius); row++)
{
for (int col = current_radius; col < (width - current_radius); col++)
{
float t = w1 * gray_src.at<uchar>(row + fy, col + fx) + w2 * gray_src.at<uchar>(row + fy, col + cx) +
w3 * gray_src.at<uchar>(row + cy, col + fx) + w2 * gray_src.at<uchar>(row + cy, col + cx);
elbpImage.at<uchar>(row - current_radius, col - current_radius) +=
((t > gray_src.at<uchar>(row, col)) &&
((abs(t - gray_src.at<uchar>(row, col))) > std::numeric_limits<float>::epsilon())) << n;
}
}
}
imshow("ELBP Result", elbpImage);
}
積分圖計(jì)算
積分圖像計(jì)算介紹
積分圖上任意一點(diǎn)像素點(diǎn)的大小都是其左上方向像素點(diǎn)的值之和遣总。
通過這一公式可以通過積分圖計(jì)算出任意一點(diǎn)的像素值
API說明
void cv::integral(
InputArray src,
OutputArray sum,
OutputArray sqsum,
OutputArray tilted,
int sdepth = -1,
int sqdepth = -1
)
代碼演示
Mat sumii = Mat::zeros(src.rows + 1, src.cols + 1, CV_32FC1);
Mat sqsumii = Mat::zeros(src.rows + 1, src.cols + 1, CV_64FC1);
integral(src, sumii, sqsumii);
Mat iiResult;
normalize(sumii, iiResult, 0, 255, NORM_MINMAX, CV_8UC1, Mat());
imshow("Integral Image", iiResult);
Haar特征
Haar特征介紹(Haar Like Features)
- 高類間變異性
- 低類內(nèi)變異性
- 局部強(qiáng)度差(可以理解成對(duì)比度)
- 不同尺度
- 計(jì)算效率高
特征描述子
特征描述子與匹配
- 什么是圖像特征描述子
通過detector提取keypoint睬罗,然后對(duì)keypoint根據(jù)不同的算法獲取周圍窗口的采樣點(diǎn)轨功,再歸一化,就形成了描述子容达。
- 描述子與匹配
通過匹配算法在圖像中找到描述子古涧,如下圖所示暴力匹配Broute-Froce匹配
代碼演示
int minHessian = 400;
Ptr<SURF> detector = SURF::create(minHessian);
vector<KeyPoint> keypoint_1;
vector<KeyPoint> keypoint_2;
Mat descriptor_1, descriptor_2;
detector->detectAndCompute(src1, Mat(), keypoint_1, descriptor_1);
detector->detectAndCompute(src2, Mat(), keypoint_2, descriptor_2);
BFMatcher matcher;
vector<DMatch> matches;
matcher.match(descriptor_1, descriptor_2, matches);
Mat matchesImg;
drawMatches(src1, keypoint_1, src2, keypoint_2, matches, matchesImg);
imshow("Descriptor Demo", matchesImg);
FLANN特征匹配
FLANN特征匹配介紹
這是一種快速匹配的算法,快速最近鄰逼近搜索函數(shù)庫(Fast Approximate Nearest Neighbor Seach Library)
代碼展示
//FLAAN快速匹配
int minHessian = 400;
Ptr<SURF> detector = SURF::create(minHessian);
vector<KeyPoint> keypoint_obj;
vector<KeyPoint> keypoint_scene;
Mat descriptor_obj, descriptor_scene;
detector->detectAndCompute(src1, Mat(), keypoint_obj, descriptor_obj);
detector->detectAndCompute(src2, Mat(), keypoint_scene, descriptor_scene);
FlannBasedMatcher matcher;
vector<DMatch> matches;
matcher.match(descriptor_obj, descriptor_scene, matches);
double minDist = 1000;
double maxDist = 0;
for (int i = 0; i < descriptor_obj.rows; i++)
{
double dist = matches[i].distance;
if (dist > maxDist)
{
maxDist = dist;
}
if (dist < minDist)
{
minDist = dist;
}
}
printf("max distance : %f\n", maxDist);
printf("min distance : %f\n", minDist);
vector<DMatch> goodMatches;
for (int i = 0; i < descriptor_obj.rows; i++)
{
double dist = matches[i].distance;
if (dist < max(50 * minDist, 0.02))
{
goodMatches.push_back(matches[i]);
}
}
Mat matchesImg;
drawMatches(src1, keypoint_obj, src2, keypoint_scene, goodMatches, matchesImg, Scalar::all(-1),
Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("Flann Matching Result", matchesImg);
平面對(duì)象識(shí)別
對(duì)象形變與位置變換
- findHomography:發(fā)現(xiàn)兩個(gè)平面的透視變換花盐, 生成變換矩陣
- perspectiveTransform:透視變換
代碼示例
//透視變換
vector<Point2f> obj;
vector<Point2f> objInScene;
for (size_t t = 0; t < goodMatches.size(); t++)
{
obj.push_back(keypoint_obj[goodMatches[t].queryIdx].pt);
objInScene.push_back(keypoint_scene[goodMatches[t].trainIdx].pt);
}
Mat H = findHomography(obj, objInScene, RANSAC);
vector<Point2f> obj_corners(4);
vector<Point2f> scene_corners(4);
obj_corners[0] = Point(0, 0);
obj_corners[1] = Point(src1.cols, 0);
obj_corners[2] = Point(src1.cols, src1.rows);
obj_corners[3] = Point(0, src1.rows);
perspectiveTransform(obj_corners, scene_corners, H);
line(matchesImg, scene_corners[0] + Point2f(src1.cols, 0),
scene_corners[1] + Point2f(src1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(matchesImg, scene_corners[1] + Point2f(src1.cols, 0),
scene_corners[2] + Point2f(src1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(matchesImg, scene_corners[2] + Point2f(src1.cols, 0),
scene_corners[3] + Point2f(src1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(matchesImg, scene_corners[3] + Point2f(src1.cols, 0),
scene_corners[0] + Point2f(src1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(src2, scene_corners[0], scene_corners[1], Scalar(0, 0, 255), 2, 8, 0);
line(src2, scene_corners[1], scene_corners[2], Scalar(0, 0, 255), 2, 8, 0);
line(src2, scene_corners[2], scene_corners[3], Scalar(0, 0, 255), 2, 8, 0);
line(src2, scene_corners[3], scene_corners[0], Scalar(0, 0, 255), 2, 8, 0);
imshow("Find Known Object Demo", matchesImg);
imshow("Draw Object", src2);
AKAZE局部匹配
AKAZE局部匹配介紹
- AOS構(gòu)造尺度空間
- Hessian矩陣特征點(diǎn)檢測(cè)
- 方向指定基于一階微分圖像
- 描述子生成
與SIFT羡滑、SURF比較
- 更加穩(wěn)定
- 非線性尺度空間
- AKAZE速度更加快
- 比較新的算法,只有OpenCV新版本才可以用
這里要注意算芯,在使用AKAZE檢測(cè)下面這張圖的時(shí)候柒昏,我發(fā)現(xiàn)找不到KeyPoint,而其他KAZE,SURF,SIFT相比之下都有更好的效果熙揍,但是AKAZE速度確實(shí)要比其他算法快很多职祷,在沒有研究源碼的前提下,我猜測(cè)AKAZE可能是犧牲了檢測(cè)的準(zhǔn)確性届囚,提高了計(jì)算速度
代碼演示
//AKAZE Detection
Ptr<AKAZE> detector = AKAZE::create();
vector<KeyPoint> keypoints;
double t1 = getTickCount();
detector->detect(src, keypoints, Mat());
double t2 = getTickCount();
double tkaze = 1000 * (t2 - t1) / getTickFrequency();
printf("KAZE Time Consume(ms): %f", tkaze);
Mat keypointImg;
drawKeypoints(src, keypoints, keypointImg, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow("kaze key points", keypointImg);
//AKAZE Detection
Ptr<AKAZE> detector = AKAZE::create();
vector<KeyPoint> keypoint_obj;
vector<KeyPoint> keypoint_scene;
Mat descriptor_obj, descriptor_scene;
double t1 = getTickCount();
detector->detectAndCompute(src1, Mat(), keypoint_obj, descriptor_obj);
detector->detectAndCompute(src2, Mat(), keypoint_scene, descriptor_scene);
double t2 = getTickCount();
double tkaze = 1000 * (t2 - t1) / getTickFrequency();
printf("KAZE Time Consume(ms): %f", tkaze);
//Matching
//BFMatcher matcher(NORM_L2);
FlannBasedMatcher matcher(new flann::LshIndexParams(20, 10, 2));
vector<DMatch> matches;
matcher.match(descriptor_obj, descriptor_scene, matches);
//Draw KeyPoints
Mat akazeMatchesImg;
drawKeypoints(src1, keypoint_obj, src1, Scalar::all(-1), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
drawMatches(src1, keypoint_obj, src2, keypoint_scene, matches, akazeMatchesImg,
Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("src1", src1);
imshow("Akaze Match Results", akazeMatchesImg);
AKAZE描述子生成圖像是基于漢明距離的有梆,F(xiàn)lann對(duì)CV_8UC1這種類型不支持,所以當(dāng)適應(yīng)AKAZE+Flann意系,要以以下方式使用Flann
FlannBasedMatcher matcher(new flann::LshIndexParams(20, 10, 2));
BRISK特征檢測(cè)與匹配
BRISK(Binary Robust Invariant Scalable KeyPoints)特征介紹
- 構(gòu)建尺度空間
- 特征點(diǎn)檢測(cè)
- FAST9-16尋找特征點(diǎn)
- 特征點(diǎn)定位
- 關(guān)鍵點(diǎn)描述子
代碼演示
//BRISKDetection
Ptr<Feature2D> detector = BRISK::create();
vector<KeyPoint> keypoints;
double t1 = getTickCount();
detector->detect(src, keypoints, Mat());
double t2 = getTickCount();
double tkaze = 1000 * (t2 - t1) / getTickFrequency();
printf("KAZE Time Consume(ms): %f", tkaze);
Mat keypointImg;
drawKeypoints(src, keypoints, keypointImg, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow("kaze key points", keypointImg);
//BRISK Detection
Ptr<Feature2D> detector = BRISK::create();
vector<KeyPoint> keypoint_obj;
vector<KeyPoint> keypoint_scene;
Mat descriptor_obj, descriptor_scene;
double t1 = getTickCount();
detector->detectAndCompute(src1, Mat(), keypoint_obj, descriptor_obj);
detector->detectAndCompute(src2, Mat(), keypoint_scene, descriptor_scene);
double t2 = getTickCount();
double tkaze = 1000 * (t2 - t1) / getTickFrequency();
printf("KAZE Time Consume(ms): %f", tkaze);
//Matching
//BFMatcher matcher(NORM_L2);
//FlannBasedMatcher matcher(new flann::LshIndexParams(20, 10, 2));
FlannBasedMatcher matcher;
vector<DMatch> matches;
matcher.match(descriptor_obj, descriptor_scene, matches);
//Draw KeyPoints
Mat briskMatchesImg;
drawKeypoints(src1, keypoint_obj, src1, Scalar::all(-1), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
drawMatches(src1, keypoint_obj, src2, keypoint_scene, matches, briskMatchesImg,
Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("src1", src1);
imshow("BRISK Match Results", briskMatchesImg);
BRISK和AKAZE存在著相似的問題泥耀,在檢測(cè)下面這張圖時(shí),檢測(cè)不到KeyPoints蛔添。