本文共 3585 字,大约阅读时间需要 11 分钟。
在上一课时中,我们已经通过Inference获取到了识别结果,存放在三个数组里:
float[] boxes = new float[100 * 4];float[] scores = new float[100];float[] classes = new float[100];
那么数据的结构是这样的, 第一个识别物体的类别是classes[0], 分数为scores[0], 物体的位置(top,left, bottom,right)为(boxes[0], boxes[1], boxes[2], boxes[3])依次类推。 但是要注意的是物体的位置的值是Normalized的,我们还需要特殊处理一下。
Normalize也叫标准化,正则化, 或者归一化,是机器学习中的一个重要概念, 在这里我们不做更多的深入的探讨,你只需要记住它的作用是把一个数值按照一定算法投影到(0,1] 这个区间里面, 这是一个你在机器学习中会经常碰到的术语。
因为物体位置的数值是Normalized的, 他们都是(0,1]区间里面的小数,而我们输入的图片是300X300的尺寸,所以我们需要把这些值都乘以300来获取它在图片里面的真实位置。那么现在我们可以得出,在输出数据中第i(i从0开始)个识别得物体的数据为:
接下来我们需要把得分低的输出项剔除掉, 这个分数是(0,1]之间的一个数(看,也是Normalized的), 越高越好。如果模型推理出来一个物体是人,但是得分只有0.1, 那么实际上有可能这个物体不是人。 所以我们需要有一个分数的阈值,大于这个阈值的识别结果才被认为是正确的。这个阈值的选择是一个经验问题, 在这里我们选择0.6作为分数的阈值,这样我们就把那些我们认为识别度不够的结果排除了。同时之前我们为100个识别结果分配了存储空间, 如果识别结果中的物体小于100,那么有一些数组元素就是空值(0), 这样的话我们把这些空值也排除掉了。
接下来我们就开始写代码了,在这里我写了一个类来封装识别的结果
public class DetectionResult { private String label; private Float score; private RectF box; public DetectionResult(String label, Float score, RectF location) { this.label = label; this.score = score; this.box = location; } public String getLabel() { return label; } public Float getScore() { return score; } public RectF getBox() { return box; }}
这个类代表一个单独的被识别出来的物体, 接下来我们根据识别结果来生成DetectionResult的List
Listresults = new ArrayList<>();for (int i = 0; i < classes.length; i++) { if (scores[i] > 0.6f) { RectF box = new RectF( boxes[4 * i + 1] * 300, boxes[4 * i] * 300, boxes[4 * i + 3] * 300, boxes[4 * i + 2] * 300); results.add(new DetectionResult(labels.get((int) classes[i]), scores[i], box)); }}
注意RectF构造函数接受的参数是(left, top, right, bottom), 而我们boxes数组中的位置是(top,left, bottom,right), 传参的时候顺序不要弄错了。
我们现在有了识别物体的位置和名称,我们只需要在原始图片的相应位置上面画上边框, 写上名称就可以啦!
首先我们将原始图片Copy一份,然后在这个Copy上面画上边框, 写上名称
final Bitmap copiedImage = originImage.copy(Bitmap.Config.ARGB_8888, true);
获取Canvas对象,设置边框的宽度和颜色,设置显示名称的字体大小和颜色
final Canvas resultCanvas = new Canvas(copiedImage);final Paint paint = new Paint();paint.setColor(Color.RED);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(5.0f);Paint textPaint = new Paint();textPaint.setColor(Color.WHITE);textPaint.setStyle(Paint.Style.FILL);textPaint.setTextSize((float) (0.04 * copiedImage.getWidth()));
注意这里我们根据图片的大小来设置显示名称的字体大小。
在往Canvas上面画图之前,我们不要忘了我们输入给模型的是一个根据原始图片经过转换而得到的300X300的图片,所以我们识别结果里面的位置指的是物体在这个300X300的图片上的位置,如果我们把这个位置画在原图上面,显然是不正确的。
还记得之前我们转换原始图片时是使用一个矩阵(Matrix)来转换的吗? 现在我们只需要把这个矩阵Invert一下,然后再在我们识别出来物体的位置矩形上面用这个矩阵进行转换,就可以得到物体在原始图片上的位置了!代码如下
Matrix inputToOrigin = new Matrix();originToInput.invert(inputToOrigin);
获取Invert的矩阵之后,我们就可以依次把识别结果在原图上用方块标识出来了
for (DetectionResult result : results) { RectF box = result.getBox(); inputToOrigin.mapRect(box); resultCanvas.drawRect(box, paint); resultCanvas.drawText(result.getLabel(), box.left, box.top, textPaint); }
我们使用mapRect方法对矩形框进行转换,同时在框的左上角写上物体的名称,然后我们调用
imageView.setImageBitmap(copiedImage);
将结果显示在ImageView上面
当当!这样我们就完成了我们任务,没有任何机器学习基础的我们在Android上面实现了一个可以识别静态图片中的物体的应用, 鼓掌!
接下来我们Going Deeper!
转载地址:http://csiti.baihongyu.com/