有一个需要,在UI自动化中,我们需要匹配某个元素在app中的位置,如何获取该元素的位置呢?一般可以通过Automation ID或者XPath,但是,有些控件或者元素,它无法通过这种方法定位,所以,我们把问题抽象成在一张图片中,框出目标元素的位置。
模板匹配的工作方式跟直方图的反向投影基本一样,大致过程是这样的:通过在输入图像上滑动图像块对实际的图像块和输入图像进行匹配。
假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
(1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
(2)用临时图像和模板图像进行对比,对比结果记为c;
(3)对比结果c,就是结果图像(0,0)处的像素值;
(4)切割输入图像从(0,1)至(10,11)的临时图像,对比,并记录到结果图像;
(5)重复(1)~(4)步直到输入图像的右下角。
大家可以看到,直方图反向投影对比的是直方图,而模板匹配对比的是图像的像素值;模板匹配比直方图反向投影速度要快一些,但是我个人认为直方图反向投影的鲁棒性会更好。
在OpenCv和EmguCv中支持以下6种对比方式:
CV_TM_SQDIFF 平方差匹配法:该方法采用平方差来进行匹配;最好的匹配值为0;匹配越差,匹配值越大。
CV_TM_CCORR 相关匹配法:该方法采用乘法操作;数值越大表明匹配程度越好。
CV_TM_CCOEFF 相关系数匹配法:1表示完美的匹配;-1表示最差的匹配。
CV_TM_SQDIFF_NORMED 归一化平方差匹配法
CV_TM_CCORR_NORMED 归一化相关匹配法
CV_TM_CCOEFF_NORMED 归一化相关系数匹配法
根据我的测试结果来看,上述几种匹配方式需要的计算时间比较接近(跟《学习OpenCV》书上说的不同),我们可以选择一个能适应场景的匹配方式。
1.对于分辨率不同的图片,它无法正常匹配。在实际的任务中,我们的模板图片可能发生分辨率的改变。
2.对于些许形变的图片,它也无法正常匹配。
3.截图内容无法匹配。
关于自适应屏幕显示:
#include
#include
using namespace cv;
using namespace std;
void Show_Picture() {
Mat src = imread("C:/Users/wenhaofu/Desktop/Multi-Scale-Template-Matching-master/test3.png");
if (src.empty()) {
printf("could not load image...\n");
return;
}
namedWindow("input", WINDOW_NORMAL);
imshow("input", src);
waitKey(0);
return;
}
int main(int artc, char** argv) {
Show_Picture();
return 0;
}
但是为了完整得显示图片,多次尝试发现,flag参数为WINDOW_NORMAL时才可以在手动调整窗口的条件下显示完整的图片。
#include
#include
#include
#include
#include "histogram.h"
#include "GrayMatching.h"
struct Best_Rect
{
Point best_point;
Mat best_mat;
};
using namespace std;
using namespace cv;
int match_method = CV_TM_SQDIFF_NORMED;
void template_match_demo(Mat& test1, Mat& test2);
void histogram(string& s1,string& s2);
//C:/Users/wenhaofu/Desktop/Multi-Scale-Template-Matching-master/test1.png
//C:/Users/wenhaofu/Desktop/Multi-Scale-Template-Matching-master/button.png
int main(int argc, char* argv)
{
string s1 = "C:/Users/wenhaofu/Desktop/Multi-Scale-Template-Matching-master/test3.png";
string s2 = "C:/Users/wenhaofu/Desktop/Multi-Scale-Template-Matching-master/button.png";
//histogram(s1,s2);
clock_t start_time = clock();
graymatching(s1, s2);
clock_t end_time = clock();
cout << (double)(end_time - start_time) / CLOCKS_PER_SEC <<"s" << endl;
return 0;
}
void histogram(string& s1, string& s2) {
Mat test, temp;
test = imread(s1);//待检测图像
temp = imread(s2);//模板图像
if (test.empty() || temp.empty())
{
cout << "could not load image...\n" << endl;
return;
}
template_match_demo(test, temp);
waitKey(0);
return;
}
void template_match_demo(Mat& test1, Mat& temp)
{
map<double, Best_Rect> record;
for (int i = 0; i < 15; i++) {
Mat test = test1.clone();
int new_rows = test1.rows - i * 0.05 * test1.rows;
int new_cols = test1.cols - i * 0.05 * test1.cols;
resize(test, test, Size(new_cols, new_rows));
int result_rows = test.rows - temp.rows + 1;
int result_cols = test.cols - temp.cols + 1;
Mat result(result_rows, result_cols, CV_32FC1);
//模板匹配
matchTemplate(test, temp, result, match_method);
//归一化
normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
//寻找模板匹配的最大最小匹配值
Point minLoc;
Point maxLoc;
Point match_loc;
double min_value, max_value;
minMaxLoc(result, &min_value, &max_value, &minLoc, &maxLoc, Mat());
if (match_method == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED)
{
match_loc = minLoc;
}
else
{
match_loc = maxLoc;
}
Rect rect(match_loc.x, match_loc.y, temp.cols, temp.rows);
Mat result_img = test.clone();
Mat result_img_roi = result_img(rect);
double his_temp = histogram(temp, result_img_roi);
Best_Rect best_rect = { match_loc,test };
record.insert(make_pair(his_temp, best_rect));
}
//将待检测图中与模板匹配出框起来
auto it = record.rbegin();
/*for (it; it != record.end(); it++) {
cout << it->first << endl;
}*/
rectangle(it->second.best_mat, Rect(it->second.best_point.x, it->second.best_point.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2, CV_AA);
namedWindow("result", WINDOW_NORMAL);
imshow("result", it->second.best_mat);
return;
}
histogram.h
//直方图比较
#include
#include
#include
#include
using namespace cv;
using namespace std;
double histogram(Mat &test1,Mat &test2){
Mat hsvtest1, hsvtest2;
if (!test1.data || !test2.data)
{
printf("could not load image...\n");
return -1;
}
//步骤一:从RGB空间转换到HSV空间
cvtColor(test1, hsvtest1, CV_BGR2HSV);
cvtColor(test2, hsvtest2, CV_BGR2HSV);
//步骤二:计算直方图与归一化
int h_bins = 50;
int s_bins = 60;
int histsize[] = { h_bins,s_bins };
//hue varies from 0 to 179,saturation from 0 to 255
float h_ranges[] = { 0,180 };
float s_ranges[] = { 0,256 };
const float* histRanges[] = { h_ranges,s_ranges };
//use the 0-th and 1-st channels
int channels[] = { 0,1 };
MatND hist_test1;
MatND hist_test2;
//计算直方图
calcHist(&hsvtest1, 1, channels, Mat(), hist_test1, 2, histsize, histRanges, true, false);
calcHist(&hsvtest2, 1, channels, Mat(), hist_test2, 2, histsize, histRanges, true, false);
//归一化
normalize(hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat());
normalize(hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat());
//步骤三:比较直方图,并返回值
double basetest2 = compareHist(hist_test1, hist_test2, CV_COMP_CORREL);
return basetest2;
}
大致思路:
给定的模板,它已经是分辨率比较低的了,我们只能改变截图,根据,每次缩放截图,我们把结果用map存起来,map的key为直方图相似度,value为Point和Rect(该点坐标和对比的图),通过,map自动排序。
#!/usr/bin/env python
# Python 2/3 compatibility
from __future__ import print_function
import numpy as np
import cv2
def init_feature():
# SIFT匹配
detector = cv2.SIFT_create(700)
# BRISK匹配
#cv2.BRISK_create()
# 归一化
norm = cv2.NORM_L2
#cv2.NORM_L2
# 特征值全匹配
matcher = cv2.BFMatcher(norm)
return detector, matcher
def filter_matches(kp1, kp2, matches, ratio = 0.75):
mkp1, mkp2 = [], []
for m in matches:
if len(m) == 2 and m[0].distance < m[1].distance * ratio:
m = m[0]
mkp1.append( kp1[m.queryIdx] )
mkp2.append( kp2[m.trainIdx] )
p1 = np.float32([kp.pt for kp in mkp1])
p2 = np.float32([kp.pt for kp in mkp2])
kp_pairs = zip(mkp1, mkp2)
return p1, p2, kp_pairs
def explore_match(win, img1, img2, kp_pairs, status = None, H = None):
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
vis = np.zeros((max(h1, h2), w1+w2), np.uint8)
vis[:h1, :w1] = img1
vis[:h2, w1:w1+w2] = img2
vis = cv2.cvtColor(vis, cv2.COLOR_GRAY2BGR)
if H is not None:
corners = np.float32([[0, 0], [w1, 0], [w1, h1], [0, h1]])
corners = np.int32( cv2.perspectiveTransform(corners.reshape(1, -1, 2), H).reshape(-1, 2) + (w1, 0) )
cv2.polylines(vis, [corners], True, (0, 0, 255))
cv2.imshow(win, vis)
return vis
if __name__ == '__main__':
img1 = cv2.imread('button.png', 0)
img2 = cv2.imread('test1.png', 0)
smaller_img1 = cv2.resize(img1, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
smaller_img2 = cv2.resize(img2, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
detector, matcher = init_feature()
kp1, desc1 = detector.detectAndCompute(smaller_img1, None)
kp2, desc2 = detector.detectAndCompute(smaller_img2, None)
raw_matches = matcher.knnMatch(desc1, trainDescriptors = desc2, k = 2)
p1, p2, kp_pairs = filter_matches(kp1, kp2, raw_matches)
if len(p1) >= 4:
H, status = cv2.findHomography(p1, p2, cv2.RANSAC, 5.0)
print('%d / %d inliers/matched' % (np.sum(status), len(status)))
vis = explore_match('find_obj', smaller_img1, smaller_img2, kp_pairs, status, H)
cv2.waitKey()
cv2.destroyAllWindows()
else:
print('%d matches found, not enough for homography estimation' % len(p1))
通过,缩放和SIFT。其实际效果,有时候并不好。
GrayMatching.h
#include
#include
#include
#include
using namespace cv;
using namespace std;
extern int match_method;
struct Best_Mat
{
Rect best_rect;
Mat best_mat;
};
//相似度匹配算法之灰度值方差匹配法:
double get_variance(Mat& a, Mat& b)
{
if (a.rows != b.rows || a.cols != b.cols || a.channels() != b.channels())
{
printf("not the same size!\n");
return 0;
}
//处理图像相似度
//1.求出每一行到灰度值均值,加入容器,作为特征值;
//2.求出灰度值总平均值与每行平均值的方差;
//3.行行比较与模版方差的接近程度
vector<double> variance_a;
vector<double> variance_b;
double var_a = 0;
double var_b = 0;
double sum_a = 0;
double sum_b = 0;
double mean_a;
double mean_b;
double sum_variance = 0.0;
//将每行灰度值均值存入容器
for (int i = 0; i < a.rows; i++) {
mean_a = 0;
mean_b = 0;
for (int j = 0; j < a.cols; j++) {
mean_a += a.at<uchar>(i, j);
mean_b += b.at<uchar>(i, j);
}
mean_a /= (double)(a.rows * a.cols);
mean_b /= (double)(a.rows * a.cols);
sum_a += mean_a;
sum_b += mean_b;
variance_a.push_back(mean_a);
variance_b.push_back(mean_b);
}
//全图灰度值均值
mean_a = sum_a / (double)variance_a.size();
mean_b = sum_b / (double)variance_b.size();
//灰度值方差之差累加
for (int i = 0; i < variance_a.size(); i++) {
var_a = (variance_a[i] - mean_a) * (variance_a[i] - mean_a);
var_b = (variance_b[i] - mean_b) * (variance_b[i] - mean_b);
sum_variance += abs(var_a - var_b);
}
return sum_variance;
}
void graymatching(string& s1,string& s2)
{
//加载图像
Mat org = imread(s1);
Mat my_template = imread(s2);
if (org.empty() || my_template.empty())
{
cout << "could not load image...\n" << endl;
return;
}
cvtColor(org, org, CV_RGB2GRAY);
cvtColor(my_template, my_template, CV_RGB2GRAY);
map<double, Best_Mat> arr;
//循环缩放,当前模版为最大尺寸,每次循环缩小5%,循环10次
for (int index = 0; index < 10; index++)
{
//获得缩放后的模版
Mat temp_template = org.clone();
int new_rows = org.rows - index * 0.05 * org.rows;
int new_cols = org.cols - index * 0.05 * org.cols;
resize(temp_template, temp_template, Size(new_cols, new_rows));
//模版匹配
Mat result;
result.create(temp_template.dims, temp_template.size, temp_template.type());
matchTemplate(temp_template, my_template, result, 0);
//获取模版匹配得到的rect
Point minPoint;
Point maxPoint;
double minVal;
double maxVal;
minMaxLoc(result, &minVal, &maxVal, &minPoint, &maxPoint);
Rect rect(minPoint.x, minPoint.y, my_template.cols, my_template.rows);
//获取匹配部分的roi图像
Mat result_img = temp_template.clone();
Mat result_img_roi = result_img(rect);
//相似度比较部分:
//比较相似度的算法很多,各有所长,这里用的是一个灰度值方差的相似度比较
//variance_diff表示灰度值方差,方差越小,相似度越高;
double variance_diff = get_variance(result_img_roi, my_template);
Best_Mat best_mat = { rect ,temp_template };
arr.insert(make_pair(variance_diff, best_mat));
}
auto it = arr.begin();
rectangle(it->second.best_mat, it->second.best_rect, Scalar(255, 0, 0), 3);
namedWindow("result", WINDOW_NORMAL);
imshow("result", it->second.best_mat);
waitKey(0);
return;
}