接触图像识别这一块有段时间了,之前也有了解过OpenCV相关知识,但因工作原因一直没时间去整理。最近玩到一款自动化软件,和项目上有提到相机的图像识别这一块,感觉没那么忙就又来折腾了。这里说明一下,第一次接触opencv的同学需要先去了解一下opencv有哪些库,干什么用的,然后再去看下关于Android导入以及使用OpenCV,相关文案太多了,这里就不过多讲了。本文大部分也是借鉴相关代码,再根据自己需求进行了一些修改,代码基本都写了注释用来辅助理解。
之前有做过无障碍服务这一块,里面涉及了许多模拟点击,滑动之类的手势操作相关功能,合理的运用可以给我们节省很多重复性的操作。最近又玩到一块自动化程序,该程序也是通过无障碍服务完成自动化操作,它功能方面都还不错,其中最喜欢的是它的一个通过模板图片模拟点击屏幕内所有相似的图的功能,但也有一定的局限性,还是不能完全的随心所欲操作,而且大部分功能还需要开vip才能使用(vip也才10元永久有效),于是并决定自己写一个。通过查询官方文档和网上相关案例。
本文章主要讲找到源图片中所有与模板相似的图片,其他功能如模拟点击功能涉及无障碍服务相关,如果大家有需求也可以单独拿出来讲。
1.创建匹配结果大小辅助框
2.使用匹配函数
3.获取匹配位置的结果
4.判断匹配结果与目标的匹配度
5.找出匹配结果达到匹配度的目标并绘制辅助框
6.将结果保存图片并输出文件
/**
* 匹配模板图片, 图片须时jpg格式的,否则会出异常
*
* @param source 源图片
* @param target 模板图片
* @param matching 匹配度 0-1 之间
* @return
*/
public Mat matching(Mat source, Mat target, float matching) {
//模板图片
Mat clone = target.clone();
if (source.empty() || target.empty()) {
Log.e(">>>>", "资源为null");
return target;
}
int templatW, templatH, resultH, resultW;
templatW = source.width();
templatH = source.height();
resultH = target.rows() - source.rows() + 1;
resultW = target.cols() - source.cols() + 1;
//匹配结果的大小
Mat result = new Mat(new Size(resultH, resultW), CvType.CV_32FC1);
//是标准相关性系数匹配 值越大越匹配
Imgproc.matchTemplate(clone, source, result, Imgproc.TM_CCOEFF_NORMED);
//匹配结果,最小到最大
Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
if (mmr.maxVal > matching) {
//在原图上的对应模板可能位置画一个绿色矩形
Imgproc.rectangle(target, mmr.maxLoc, new Point(mmr.maxLoc.x + templatW, mmr.maxLoc.y + templatH), new Scalar(0, 255, 0), 2);
Log.e(">>>>", "匹配的值:" + mmr.maxVal + " ------坐标:" + mmr.maxLoc.x + "," + mmr.maxLoc.y);
}
//第几个目标图片
int count = 0;
//找出全部相似度照片
while (mmr.maxVal > matching) {
mmr = getMaxLoc(clone, source, templatW, templatH, mmr.maxLoc);
if (mmr.maxVal > matching) {
count += 1;
//画一个绿色的矩形
Imgproc.rectangle(target, mmr.maxLoc, new Point(mmr.maxLoc.x + templatW, mmr.maxLoc.y + templatH), new Scalar(0, 255, 0), 2);
Imgproc.putText(target, "" + count, new Point(mmr.maxLoc.x, mmr.maxLoc.y), 1, 3, new Scalar(0, 255, 0), 2);
Log.e(">>>>", "匹配的值:" + mmr.maxVal + " ------坐标:" + mmr.maxLoc.x + "," + mmr.maxLoc.y);
} else {
break;
}
}
//将结果输出到对应位置
Imgcodecs.imwrite(getExternalFilesDir("") + /result.jpg", target);
return target;
}
/**
* 获取坐标
* @param clone
* @param result
* @param templatW
* @param templatH
* @param maxLoc
* @return
*/
private Core.MinMaxLocResult getMaxLoc(Mat clone, Mat result, int templatW, int templatH, Point maxLoc) {
int startY, startX, endY, endX;
//计算大矩形的坐标
startY = (int) maxLoc.y;
startX = (int) maxLoc.x;
//计算大矩形的的坐标
endY = (int) maxLoc.y + templatH;
endX = (int) maxLoc.x + templatW;
//将大矩形内部 赋值为最大值 使得 以后找的最小值 不会位于该区域 避免找到重叠的目标
int ch = clone.channels();
//通道数 (灰度: 1, RGB: 3, etc.)
for (int i = startX; i < endX; i++) {
for (int j = startY; j < endY; j++) {
double[] data = clone.get(j, i); //读取像素值,并存储在double数组中
for (int k = 0; k < ch; k++) { //RGB值或灰度值
data[k] = 255; //对每个像素值(灰度值或RGB通道值,取值0~255)进行处理
}
clone.put(j, i, data); //把处理后的像素值写回到Mat
}
}
int resultH = clone.rows() - result.rows() + 1;
int resultW = clone.cols() - result.cols() + 1;
Mat result2 = new Mat(new Size(resultH, resultW), CvType.CV_32FC1);
Imgproc.matchTemplate(clone, result, result2, Imgproc.TM_CCOEFF_NORMED); //是标准相关性系数匹配 值越大越匹配
//查找result中的最大值 及其所在坐标
return Core.minMaxLoc(result2);
}
public void find(View view) {
new Thread(new Runnable() {
@Override
public void run() {
Mat mat = matching(target, source, 0.9f); //findTarget(source,target,getExternalFilesDir("").getAbsolutePath(),Imgproc.TM_CCOEFF);
runOnUiThread(new Runnable() {
@Override
public void run() {
Bitmap resultBitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(mat, resultBitmap);
sourceImg.setImageBitmap(resultBitmap);
}
});
}
}).start();
}
匹配结果
总体使用感受还是,切换了很多不同图片,查找匹配效果都很满意,唯一的缺点就是查找的目标多的时候很慢,不过正常情况下一张图片也就10个以内吧,3-5个的匹配速度还是很快的,测了一下50个的,好慢。目前在测试优化测试,匹配算法上涉及到c++层,虽然很多都有博主将讲很详细,但还是不太好下手。打算从应用层优化,目前想法是将图片无损缩小,然后分割成多张图片,每张图片的大小和源图一样大,空白区域用纯色填充,再利用多线程对每张小图进行查找。当然实际情况可能会更复杂,比如分割图片时还得确保会不会把目标图片也给分割了之类的。