预训练模型是一种已经使用训练数据集进行训练并包含执行模型所需所有参数的机器学习模型。这类模型常用于计算机视觉领域,比如可以在Mendix Studio Pro中导入ONNX模型后,可以在微流程中执行该模型。
本文讲述如何在Mendix应用程序中集成特定的人脸检测模型。其中一个重要步骤是添加所需的预处理和后处理步骤,并以Java代码形式实现。这里提供了详细的Java代码示例:Mendix ML Kit Demo
Face Detection model
我们想要使用的ML模型是ONNX模型库中的一个轻量级人脸检测模型。这里有两个模型可供选择,它们在输入分辨率上有所不同。我们选择了RFB-640模型,它需要一个分辨率为640x480像素的RGB图像。该模型的输出是两个列表,包含了边界框和相应的得分。一个边界框由其左侧、顶部、右侧和底部的边界位置定义。
在Mendix中映射模型
在Mendix文档中已经很好地描述了导入和调用ONNX ML模型的几个简单步骤。面部识别模型为调用模型生成了以下输入和输出映射。
为了在Mendix中调用模型,我们创建一个输入实体的实例,并将其传递给Call ML model 的微流组件。
如我们所见,这里的输入和输出数据需要一种特殊格式。为了转换这些数据,需要进行一些预处理和后处理。
预处理和后处理
对于这个特定问题,Mendix中没有标准的处理方法。因此,这里的处理是通过两个Java动作完成的,这些动作完成了大部分工作。
映射输入数据
图像必须以包含形状为[1,3,480,640]的张量的二进制数据传递。您可以在这里了解更多关于张量和形状的信息:TensorFlow Core中的张量简介
要运行模型,需要进行以下步骤:
- 将输入图像缩放到640x480像素的RGB格式。
- 将图像转换为具有匹配形状和标准化值的矩阵。
- 将矩阵编码成可以传递给ML模型的格式。
图像读取和缩放
为了缩放图像,我们将使用OpenCV库及其Java封装。由于OpenCV需要本地库,我们将遵循与Mendix ML学习套件相同的方法,使用打包有本地库的库。
在我们可以使用任何OpenCV功能之前,我们需要加载共享库。这可以在静态代码段中完成。
// BEGIN EXTRA CODE
static {
nu.pattern.OpenCV.loadShared(); //OpenCV initialization
}
我们可以读取输入图像并使用OpenCV的resize方法进行转换。
// read the image into a matrix
InputStream istream = com.mendix.core.Core.getFileDocumentContent(getContext(), sourceImage.getMendixObject());
MatOfByte matOfByte = new MatOfByte(istream.readAllBytes());
Mat sourceImage = Imgcodecs.imdecode(matOfByte, Imgcodecs.IMREAD_COLOR);
// resize the image to the target size
Mat rim = new Mat();
Imgproc.resize(sourceImage, rim, new Size(640,480));
将图像转换为矩阵
下一步是将图像转换为矩阵并标准化值。人脸检测模型期望输入数据缩放到[-1;1]的范围。这种转换可以使用以下代码完成。
float[][][][] inputArray = new float[1][3][480][640];
for(int i = 0; i < 480; i++) {
for(int j = 0; j < 640; j++) {
for(int k = 0; k <= 2; k++) {
double[] rawValue = rim.get(i, j);
float normalizedValue = ((float) (rawValue[Math.abs(k - 2)] - 127) / 128);
inputArray[0][k][i][j] = normalizedValue;
}
}
}
为处理编码矩阵
现在必须将结果的inputArray矩阵传递给模型。Mendix ML Kit已经提供了一些辅助函数,用于对这些数据进行base64编码。这个Java动作的结果被写入模型的输入对象。
InputStream is = MLKit.toInputStream(inputArray);
String base64data = MLKit.toBase64(is);
映射输出数据(后处理)
模型生成了两个多维矩阵(得分和边界框),它们以base64编码的二进制形式。为了处理这些输出,我们使用一个后处理Java动作。对于解码原始数据,我们可以再次使用MLKit的辅助函数。
float[][][] _box = new float[1][17640][4];
InputStream isb = MLKit.fromBase64(rfb640Output.getBoxes());
MLKit.toArray(isb, _box);
对于解码计算出的得分,使用相同的代码。输出数据的形状是[1,17640,4]。这意味着模型总是生成17640个潜在面孔的候选者。为了识别最终的检测到的面孔列表,需要进行一些额外的过滤,这不是模型本身完成的。
过滤模型输出
后处理包括两个步骤。第一步是根据它们的得分过滤结果,并将候选者限制在较小的集合中。sortedCandidateIdx的结果是指向候选者的一组索引。
List<Integer> sortedCandidateIdx = IntStream.range(0, 17640)
.filter(idx -> score[idx][1] > detectionThreshold)
.boxed().sorted(new Comparator<Integer>() {
@Override
public int compare(Integer idx1, Integer idx2) {
return Float.compare(score[idx2][1] ,score[idx1][1]);
}
}).limit(candidates).collect(Collectors.toList());
得分是指定框中含有面孔的可能性的指示器。这些候选者现在是输入图像中检测到的面孔的有效检测。然而,这些框将有显著的重叠。
如果您不加过滤地使用这些数据,就会有多个框指示图像同一区域中检测到的面孔,如上所示。作为最终输出,我们希望每个面孔有一个单独的框。
非最大抑制
用于过滤这些框的方法称为非最大抑制(NMS),它使用IoU(交集与并集的比率)来确定重叠的框。这个过程的Java等效代码如下:
for (Integer idx1 : sortedCandidateIdx) {
if (!finalCandidates.isEmpty()) {
boolean hasIoUAboveThreshold = false;
for (Integer idx2 : finalCandidates) {
float l = Math.min(box[idx1][0], box[idx2][0]);
float t = Math.min(box[idx1][1], box[idx2][1]);
float r = Math.max(box[idx1][2], box[idx2][2]);
float b = Math.max(box[idx1][3], box[idx2][3]);
if (l>=r || t>=b) continue;
float intersection = (r-l)*(b-t);
float area1 = (box[idx1][2]-box[idx1][0])*(box[idx1][3]-box[idx1][1]);
float area2 = (box[idx2][2]-box[idx2][0])*(box[idx2][3]-box[idx2][1]);
float iou = intersection / (area1 + area2 - intersection + 0.0000001f);
if (iou<iouThreshold) continue;
hasIoUAboveThreshold=true;
break;
}
if (hasIoUAboveThreshold) continue;
}
finalCandidates.add(idx1);
}
采用此方法,最终的候选者现在清晰地分隔成4个不同的部分。
使用结果数据
此时的实际结果数据仍然是一组在归一化空间[0,1]x[0,1]中的框坐标。我们可以使用OpenCV的方法将结果绘制到源图像上,如这里所示,或者我们可以将信息传递给任何连续的过程。
for (Integer idx : finalCandidates) {
Rect rec = new Rect(new Point(box[idx][0] * width, box[idx][1] * height), new Point(box[idx][2] * width, box[idx][3] * height));
Imgproc.rectangle(sourceImage, rec, color, linethickness);
};
结论
通过Mendix ML工具包,导入和运行预训练的ONNX模型变得更加简单。Mendix为映射输入和输出数据到实际模型提供了我们已经习惯的接口类型。
您仍然需要数据预处理和后处理的专业知识。其中一些步骤在类似问题中是通用的,很有可能会有更多可以直接使用的方法。
所需的一些操作是挑战性的,但模型的集成到平台中使得快速进展和在几天内获得工作解决方案变得容易。
参考资料
1. Using ML Kit
2. Mendix ML Kit Demo
3. OpenCV modules
4. GitHub - OpenCV
关于Mendix
作为西门子Xcelerator平台的低代码引擎,Mendix正在迅速成为推动企业数字化发展的首选应用程序开发平台。Mendix让企业能够以前所未有的速度构建应用程序、促进IT团队与业务专家之间开展有意义的协作,并帮助IT团队保持对整个应用程序环境的控制。作为一直被领先的行业分析师视为“领军者和远见者”的低代码平台,Mendix是云原生的、开放的、可扩展的、敏捷的,并且经过实践验证。从人工智能和增强现实,到智能自动化和原生移动,Mendix和西门子Xcelerator已成为“数字优先”企业的中坚力量。Mendix已被46个国家的4,000多家企业采用,并建立了由30多万名开发人员组成的活跃社区,这些开发人员使用该平台创建了20多万款应用程序。