From 940fb3c8b28fabb8b90da4818061947fc6931baf Mon Sep 17 00:00:00 2001 From: divenswu Date: Tue, 29 Nov 2022 22:51:45 +0800 Subject: [PATCH] =?UTF-8?q?update:=E4=BF=AE=E6=94=B9PCN=E7=9A=84=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E6=8E=A8=E7=90=86=E4=BB=A3=E7=A0=81=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=AD=98=E5=9C=A8=E7=9A=84=E5=8F=AF=E8=83=BD=E5=86=85?= =?UTF-8?q?=E5=AD=98=E6=B3=84=E9=9C=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../face/search/core/domain/ImageMat.java | 8 +- .../visual/face/search/core/domain/Mats.java | 44 ++ .../core/models/PcnNetworkFaceDetection.java | 588 ++++++++++-------- .../face/search/core/utils/ReleaseUtil.java | 98 +++ .../images/faces/debug/debug_0005.jpg | Bin 0 -> 19668 bytes 5 files changed, 465 insertions(+), 273 deletions(-) create mode 100644 face-search-core/src/main/java/com/visual/face/search/core/domain/Mats.java create mode 100644 face-search-core/src/main/java/com/visual/face/search/core/utils/ReleaseUtil.java create mode 100644 face-search-core/src/test/resources/images/faces/debug/debug_0005.jpg diff --git a/face-search-core/src/main/java/com/visual/face/search/core/domain/ImageMat.java b/face-search-core/src/main/java/com/visual/face/search/core/domain/ImageMat.java index 2588dad..b61f459 100755 --- a/face-search-core/src/main/java/com/visual/face/search/core/domain/ImageMat.java +++ b/face-search-core/src/main/java/com/visual/face/search/core/domain/ImageMat.java @@ -681,8 +681,12 @@ public class ImageMat implements Serializable { */ public void release(){ if(this.mat != null){ - this.mat.release(); - this.mat = null; + try { + this.mat.release(); + this.mat = null; + }catch (Exception e){ + e.printStackTrace(); + } } } } diff --git a/face-search-core/src/main/java/com/visual/face/search/core/domain/Mats.java b/face-search-core/src/main/java/com/visual/face/search/core/domain/Mats.java new file mode 100644 index 0000000..eeaf94a --- /dev/null +++ b/face-search-core/src/main/java/com/visual/face/search/core/domain/Mats.java @@ -0,0 +1,44 @@ +package com.visual.face.search.core.domain; + +import org.opencv.core.Mat; + +import java.util.ArrayList; + +public class Mats extends ArrayList { + + public static Mats build(){ + return new Mats(); + } + + public Mats append(Mat mat){ + this.add(mat); + return this; + } + + public Mats clone(){ + Mats mats = new Mats(); + for(Mat mat : this){ + mats.add(mat.clone()); + } + return mats; + } + + public void release(){ + if(this.isEmpty()){ + return; + } + for(Mat mat : this){ + if(null != mat){ + try { + mat.release(); + }catch (Exception e){ + e.printStackTrace(); + }finally { + mat = null; + } + } + } + this.clear(); + } + +} diff --git a/face-search-core/src/main/java/com/visual/face/search/core/models/PcnNetworkFaceDetection.java b/face-search-core/src/main/java/com/visual/face/search/core/models/PcnNetworkFaceDetection.java index 9b6c328..44e3e9a 100755 --- a/face-search-core/src/main/java/com/visual/face/search/core/models/PcnNetworkFaceDetection.java +++ b/face-search-core/src/main/java/com/visual/face/search/core/models/PcnNetworkFaceDetection.java @@ -8,6 +8,8 @@ import com.visual.face.search.core.base.BaseOnnxInfer; import com.visual.face.search.core.base.FaceDetection; import com.visual.face.search.core.domain.FaceInfo; import com.visual.face.search.core.domain.ImageMat; +import com.visual.face.search.core.domain.Mats; +import com.visual.face.search.core.utils.ReleaseUtil; import org.opencv.core.*; import org.opencv.imgproc.Imgproc; @@ -60,114 +62,116 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect public List inference(ImageMat image, float scoreTh, float iouTh, Map params) { Mat mat = null; Mat imgPad = null; + Mat resizeMat = null; ImageMat imageMat = image.clone(); try { mat = imageMat.toCvMat(); - imgPad = pad_img_not_release_mat(mat); + float scale = (float) ((mat.size().height > mat.size().width) ? mat.size().width/512: mat.size().height/512); + resizeMat = resize_img_release_mat(mat.clone(), scale); + imgPad = pad_img_release_mat(resizeMat.clone()); float[] iouThs = iouTh <= 0 ? defIouThs : new float[]{iouTh, iouTh, 0.3f}; float[] scoreThs = scoreTh <= 0 ? defScoreThs : new float[]{0.375f * scoreTh, 0.5f * scoreTh, scoreTh}; - List willis = detect(this.getSessions(), mat, imgPad, scoreThs, iouThs); - return trans_window(mat, imgPad, willis); + List willis = detect_release_mat(this.getSessions(), resizeMat.clone(), imgPad.clone(), scoreThs, iouThs); + return trans_window(resizeMat, imgPad, willis, scale); } catch (Exception e) { throw new RuntimeException(e); }finally { - if(null != mat){ - mat.release(); - } - if(null != imgPad){ - imgPad.release(); - } - if(null != imageMat){ - imageMat.release(); - } + ReleaseUtil.release(imgPad, resizeMat, mat); + ReleaseUtil.release(imageMat); } } /********************************分割线*************************************/ - private static Mat pad_img_not_release_mat(Mat mat){ - int row = Math.min((int)(mat.size().height * 0.2), 100); - int col = Math.min((int)(mat.size().width * 0.2), 100); - Mat dst = new Mat(); - Core.copyMakeBorder(mat, dst, row, row, col, col, Core.BORDER_CONSTANT); - return dst; + private static Mat pad_img_release_mat(Mat mat){ + try { + int row = Math.min((int)(mat.size().height * 0.2), 100); + int col = Math.min((int)(mat.size().width * 0.2), 100); + Mat dst = new Mat(); + Core.copyMakeBorder(mat, dst, row, row, col, col, Core.BORDER_CONSTANT); + return dst; + }finally { + ReleaseUtil.release(mat); + } } - private static Mat resize_img(Mat mat, float scale){ - double h = mat.size().height; - double w = mat.size().width; - int h_ = (int) (h / scale); - int w_ = (int) (w / scale); - Mat matF32 = new Mat(); - if(mat.type() != CvType.CV_32FC3){ - mat.convertTo(matF32, CvType.CV_32FC3); - }else{ - mat.copyTo(matF32); + private static Mat resize_img_release_mat(Mat mat, float scale){ + Mat matF32 = null; + try{ + double h = mat.size().height; + double w = mat.size().width; + int h_ = (int) (h / scale); + int w_ = (int) (w / scale); + matF32 = new Mat(); + if(mat.type() != CvType.CV_32FC3){ + mat.convertTo(matF32, CvType.CV_32FC3); + }else{ + mat.copyTo(matF32); + } + Mat dst = new Mat(); + Imgproc.resize(matF32, dst, new Size(w_, h_), 0,0, Imgproc.INTER_NEAREST); + return dst; + }finally { + ReleaseUtil.release(matF32, mat); } - Mat dst = new Mat(); - Imgproc.resize(matF32, dst, new Size(w_, h_), 0,0, Imgproc.INTER_NEAREST); - mat.release(); - matF32.release(); - return dst; } - private static Mat preprocess_img(Mat mat, int dim){ - Mat matTmp = new Mat(); - if(dim > 0){ - Imgproc.resize(mat, matTmp, new Size(dim, dim), 0, 0, Imgproc.INTER_NEAREST); - }else{ - mat.copyTo(matTmp); + private static Mat preprocess_img_release_mat(Mat mat, int dim){ + Mat matTmp = null; + Mat matF32 = null; + try { + //resize + matTmp = new Mat(); + if(dim > 0){ + Imgproc.resize(mat, matTmp, new Size(dim, dim), 0, 0, Imgproc.INTER_NEAREST); + }else{ + mat.copyTo(matTmp); + } + //格式转化 + matF32 = new Mat(); + if(mat.type() != CvType.CV_32FC3){ + matTmp.convertTo(matF32, CvType.CV_32FC3); + }else{ + matTmp.copyTo(matF32); + } + //减法运算 + Mat dst = new Mat(); + Core.subtract(matF32, new Scalar(104, 117, 123), dst); + return dst; + }finally { + ReleaseUtil.release(matF32, matTmp, mat); } - //格式转化 - Mat matF32 = new Mat(); - if(mat.type() != CvType.CV_32FC3){ - matTmp.convertTo(matF32, CvType.CV_32FC3); - }else{ - matTmp.copyTo(matF32); - } - - Mat dst = new Mat(); - Core.subtract(matF32, new Scalar(104, 117, 123), dst); - - mat.release(); - matTmp.release(); - matF32.release(); - - return dst; } - private static OnnxTensor set_input(Mat mat){ + private static OnnxTensor set_input_release_mat(Mat mat){ Mat dst = null; try { dst = new Mat(); mat.copyTo(dst); return ImageMat.fromCVMat(dst).to4dFloatOnnxTensorAndDoReleaseMat(true); }finally { - if(null != dst){ - dst.release(); - } + ReleaseUtil.release(dst, mat); } } - private static OnnxTensor set_input(List mats){ - float[][][][] arrays = new float[mats.size()][][][]; - for(int i=0; i< mats.size(); i++){ - Mat dst = new Mat(); - mats.get(i).copyTo(dst); - float[][][][] array = ImageMat.fromCVMat(dst).to4dFloatArrayAndDoReleaseMat(true); - arrays[i] = array[0]; - dst.release(); - } + private static OnnxTensor set_input_release_mat(Mats mats){ try { + float[][][][] arrays = new float[mats.size()][][][]; + for(int i=0; i< mats.size(); i++){ + float[][][][] array = ImageMat.fromCVMat(mats.get(i)).to4dFloatArrayAndDoReleaseMat(true); + arrays[i] = array[0]; + } return OnnxTensor.createTensor(OrtEnvironment.getEnvironment(), arrays); }catch (Exception e){ throw new RuntimeException(e); + }finally { + ReleaseUtil.release(mats); } } - private static boolean legal(int x, int y, Mat mat){ - if(0 <= x && x < mat.size().width && 0 <= y && y< mat.size().height){ + private static boolean legal(int x, int y, Size size){ + if(0 <= x && x < size.width && 0 <= y && y< size.height){ return true; }else{ return false; @@ -191,87 +195,106 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect } private static List NMS(List winlist, boolean local, float threshold){ - if(winlist==null || winlist.isEmpty()){ - return new ArrayList<>(); - } - //排序 - Collections.sort(winlist); - int [] flag = new int[winlist.size()]; - for(int i=0; i< winlist.size(); i++){ - if(flag[i] > 0){ - continue; + try{ + if(winlist==null || winlist.isEmpty()){ + return new ArrayList<>(); } - for(int j=i+1; j EPS){ + //排序 + Collections.sort(winlist); + int [] flag = new int[winlist.size()]; + for(int i=0; i< winlist.size(); i++){ + if(flag[i] > 0){ continue; } - if(IoU(winlist.get(i), winlist.get(j)) > threshold){ - flag[j] = 1; + for(int j=i+1; j EPS){ + continue; + } + if(IoU(winlist.get(i), winlist.get(j)) > threshold){ + flag[j] = 1; + } } } - } - List list = new ArrayList<>(); - for(int i=0; i< flag.length; i++){ - if(flag[i] == 0){ - list.add(winlist.get(i)); + List list = new ArrayList<>(); + for(int i=0; i< flag.length; i++){ + if(flag[i] == 0){ + list.add(winlist.get(i)); + } + } + return list; + }finally { + if(null != winlist && !winlist.isEmpty()){ + winlist.clear(); } } - return list; } private static List deleteFP(List winlist){ - if (winlist == null || winlist.isEmpty()) { - return new ArrayList<>(); - } - //排序 - Collections.sort(winlist); - int [] flag = new int[winlist.size()]; - for(int i=0; i< winlist.size(); i++){ - if(flag[i] > 0){ - continue; + try { + if(null == winlist || winlist.isEmpty()) { + return new ArrayList<>(); } - for(int j=i+1; j 0){ + continue; + } + for(int j=i+1; j list = new ArrayList<>(); - for(int i=0; i< flag.length; i++){ - if(flag[i] == 0){ - list.add(winlist.get(i)); + List list = new ArrayList<>(); + for(int i=0; i< flag.length; i++){ + if(flag[i] == 0){ + list.add(winlist.get(i)); + } + } + return list; + }finally { + if(null != winlist && !winlist.isEmpty()){ + winlist.clear(); } } - return list; } - private static List trans_window(Mat img, Mat imgPad, List winlist){ - int row = (imgPad.size(0) - img.size(0)) / 2; - int col = (imgPad.size(1) - img.size(1)) / 2; + private static List trans_window(Mat img, Mat imgPad, List winlist, float scale){ List ret = new ArrayList<>(); - for(Window2 win : winlist){ - if( win.w > 0 && win.h > 0){ - int x1 = win.x - col; - int y1 = win.y - row; - int x2 = win.x - col + win.w; - int y2 = win.y - row + win.w; - int angle = (win.angle + 360) % 360; - //扩展人脸高度 - float rw = 0f; - float rh = 0.1f; - int w = Math.abs(x2 - x1); - int h = Math.abs(y2 - y1); - x1 = Math.max(x1 - (int)(w * rw), 1); - y1 = Math.max(y1 - (int)(h * rh), 1); - x2 = Math.min(x2 + (int)(w * rw), img.size(1)-1); - y2 = Math.min(y2 + (int)(h * rh), img.size(0)-1); - //构建人脸信息 - FaceInfo faceInfo = FaceInfo.build(win.conf, angle, FaceInfo.FaceBox.build(x1, y1, x2, y2), FaceInfo.Points.build()); - ret.add(faceInfo); + try { + int row = (imgPad.size(0) - img.size(0)) / 2; + int col = (imgPad.size(1) - img.size(1)) / 2; + for(Window2 win : winlist){ + if( win.w > 0 && win.h > 0){ + int x1 = win.x - col; + int y1 = win.y - row; + int x2 = win.x - col + win.w; + int y2 = win.y - row + win.w; + int angle = (win.angle + 360) % 360; + //扩展人脸高度 + float rw = 0f; + float rh = 0.1f; + int w = Math.abs(x2 - x1); + int h = Math.abs(y2 - y1); + x1 = Math.max(Float.valueOf((x1 - (int)(w * rw)) * scale).intValue(), 1); + y1 = Math.max(Float.valueOf((y1 - (int)(h * rh)) * scale).intValue(), 1); + x2 = Math.min(Float.valueOf((x2 + (int)(w * rw)) * scale).intValue(), Float.valueOf((img.size(1)) * scale).intValue()-1); + y2 = Math.min(Float.valueOf((y2 + (int)(h * rh)) * scale).intValue(), Float.valueOf((img.size(0)) * scale).intValue()-1); + //构建人脸信息 + FaceInfo faceInfo = FaceInfo.build(win.conf, angle, FaceInfo.FaceBox.build(x1, y1, x2, y2), FaceInfo.Points.build()); + ret.add(faceInfo); + } + } + return ret; + }finally { + if(null != winlist && !winlist.isEmpty()){ + winlist.clear(); } } - return ret; + } /** @@ -284,34 +307,30 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect * @throws OrtException * @throws IOException */ - private static List stage1(Mat img, Mat imgPad, OrtSession net, float thres) throws RuntimeException { - int netSize = 24; - float curScale = minFace_ / netSize; - int row = (int) ((imgPad.size().height - img.size().height) / 2); - int col = (int) ((imgPad.size().width - img.size().width) / 2); - + private static List stage1_release_mat(Mat img, Mat imgPad, OrtSession net, float thres) throws RuntimeException { Mat img_resized = null; OnnxTensor net_input1 = null; OrtSession.Result output = null; List winlist = new ArrayList<>(); try { - img_resized = resize_img(img.clone(), curScale); + //获取必要参数 + int netSize = 24; + float curScale = minFace_ / netSize; + int row = (int) ((imgPad.size().height - img.size().height) / 2); + int col = (int) ((imgPad.size().width - img.size().width) / 2); + img_resized = resize_img_release_mat(img.clone(), curScale); + //循环处理 while(Math.min(img_resized.size().height, img_resized.size().width) >= netSize){ - img_resized = preprocess_img(img_resized, 0); - net_input1 = set_input(img_resized); + img_resized = preprocess_img_release_mat(img_resized, 0); + net_input1 = set_input_release_mat(img_resized.clone()); + //推理网络 output = net.run(Collections.singletonMap(net.getInputNames().iterator().next(), net_input1)); float[][][][] cls_prob = (float[][][][]) output.get(0).getValue(); float[][][][] rotate = (float[][][][]) output.get(1).getValue(); float[][][][] bbox = (float[][][][]) output.get(2).getValue(); //关闭对象 - if(null != net_input1){ - net_input1.close(); - net_input1 = null; - } - if(null != output){ - output.close(); - output = null; - } + ReleaseUtil.release(output); output = null; + ReleaseUtil.release(net_input1); net_input1 = null; //计算业务逻辑 float w = netSize * curScale; for(int i=0; i< cls_prob[0][0].length; i++){ @@ -323,7 +342,7 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect int rx = (int)(j * curScale * stride_ - 0.5 * sn * w + sn * xn * w + 0.5 * w) + col; int ry = (int)(i * curScale * stride_ - 0.5 * sn * w + sn * yn * w + 0.5 * w) + row; int rw = (int)(w * sn); - if (legal(rx, ry, imgPad) && legal(rx + rw - 1, ry + rw - 1, imgPad)){ + if (legal(rx, ry, imgPad.size()) && legal(rx + rw - 1, ry + rw - 1, imgPad.size())){ if (rotate[0][1][i][j] > 0.5){ winlist.add(new Window2(rx, ry, rw, rw, 0, curScale, cls_prob[0][1][i][j])); }else{ @@ -333,21 +352,15 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect } } } - img_resized = resize_img(img_resized, scale_); + img_resized = resize_img_release_mat(img_resized, scale_); curScale = (float) (img.size().height / img_resized.size().height); } }catch (Exception e){ throw new RuntimeException(e); }finally { - if(null != net_input1){ - net_input1.close(); - } - if(null != output){ - output.close(); - } - if(null != img_resized){ - img_resized.release(); - } + ReleaseUtil.release(output); + ReleaseUtil.release(net_input1); + ReleaseUtil.release(img_resized, imgPad, img); } //返回 return winlist; @@ -364,56 +377,58 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect * @return * @throws OrtException */ - private static List stage2(Mat img, Mat img180, OrtSession net, float thres, int dim, List winlist) throws OrtException { + private static List stage2_release_mat(Mat img, Mat img180, OrtSession net, float thres, int dim, List winlist) throws OrtException { if(winlist==null || winlist.isEmpty()){ return new ArrayList<>(); } + //逻辑处理 + float[][] bbox; + float[][] rotate; + float[][] cls_prob; + Mats datalist = null; + OnnxTensor input = null; + OrtSession.Result output = null; + Size size = img.size(); int height = img.size(0); - List datalist = new ArrayList<>(); - for(Window2 win : winlist){ - if(Math.abs(win.angle) < EPS){ - Mat cloneMat = img.clone(); - Mat corp = new Mat(cloneMat, new Rect(win.x, win.y, win.w, win.h)); - Mat corp1 = preprocess_img(corp, dim); - datalist.add(corp1); - if(null != corp){ - corp.release(); - } - if(null != cloneMat){ - cloneMat.release(); - } - }else{ - int y2 = win.y + win.h - 1; - int y = height - 1 - y2; - - Mat cloneMat = img180.clone(); - Mat corp = new Mat(cloneMat, new Rect(win.x, y, win.w, win.h)); - Mat corp1 = preprocess_img(corp, dim); - datalist.add(corp1); - if(null != corp){ - corp.release(); - } - if(null != cloneMat){ - cloneMat.release(); + try { + datalist = Mats.build(); + for(Window2 win : winlist){ + if(Math.abs(win.angle) < EPS){ + Mat corp = null; + try { + corp = new Mat(img, new Rect(win.x, win.y, win.w, win.h)); + Mat preprocess = preprocess_img_release_mat(corp.clone(), dim); + datalist.add(preprocess); + }finally { + ReleaseUtil.release(corp); + } + }else{ + Mat corp = null; + try { + int y2 = win.y + win.h - 1; + int y = height - 1 - y2; + corp = new Mat(img180, new Rect(win.x, y, win.w, win.h)); + Mat preprocess = preprocess_img_release_mat(corp.clone(), dim); + datalist.add(preprocess); + }finally { + ReleaseUtil.release(corp); + } } } + //模型推理 + input = set_input_release_mat(datalist.clone()); + output = net.run(Collections.singletonMap(net.getInputNames().iterator().next(), input)); + cls_prob = (float[][]) output.get(0).getValue(); + rotate = (float[][]) output.get(1).getValue(); + bbox = (float[][]) output.get(2).getValue(); + }finally { + ReleaseUtil.release(datalist); + ReleaseUtil.release(output); + ReleaseUtil.release(input); + ReleaseUtil.release(img180, img); + output = null; input = null; } - - OnnxTensor net_input = set_input(datalist); - OrtSession.Result output = net.run(Collections.singletonMap(net.getInputNames().iterator().next(), net_input)); - float[][] cls_prob = (float[][]) output.get(0).getValue(); - float[][] rotate = (float[][]) output.get(1).getValue(); - float[][] bbox = (float[][]) output.get(2).getValue(); - //关闭对象 - for(Mat mat : datalist){ - mat.release(); - } - if(null != net_input){ - net_input.close(); - } - if(null != output){ - output.close(); - } + //再次后处理数据 List ret = new ArrayList<>(); for(int i=0; i thres){ @@ -439,7 +454,7 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect } } - if(legal(x, y, img) && legal(x + w - 1, y + w - 1, img)){ + if(legal(x, y, size) && legal(x + w - 1, y + w - 1, size)){ int angle = 0; if(Math.abs(winlist.get(i).angle) < EPS){ if(maxRotateIndex == 0){ @@ -479,49 +494,79 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect * @return * @throws OrtException */ - private static List stage3(Mat imgPad, Mat img180, Mat img90, Mat imgNeg90, OrtSession net, float thres, int dim, List winlist) throws OrtException { + private static List stage3_release_mat(Mat imgPad, Mat img180, Mat img90, Mat imgNeg90, OrtSession net, float thres, int dim, List winlist) throws OrtException { if (winlist == null || winlist.isEmpty()) { return new ArrayList<>(); } + //逻辑处理 + float[][] bbox; + float[][] rotate; + float[][] cls_prob; + Mats datalist = null; + OnnxTensor input = null; + OrtSession.Result output = null; + int height = imgPad.size(0); int width = imgPad.size(1); - List datalist = new ArrayList<>(); - for(Window2 win : winlist){ - if(Math.abs(win.angle) < EPS){ - Mat corp = new Mat(imgPad, new Rect(win.x, win.y, win.w, win.h)); - datalist.add(preprocess_img(corp, dim)); - }else if(Math.abs(win.angle - 90) < EPS){ - Mat corp = new Mat(img90, new Rect(win.y, win.x, win.h, win.w)); - datalist.add(preprocess_img(corp, dim)); - }else if(Math.abs(win.angle + 90) < EPS){ - int x = win.y; - int y = width - 1 - (win.x + win.w - 1); - Mat corp = new Mat(imgNeg90, new Rect(x, y, win.w, win.h)); - datalist.add(preprocess_img(corp, dim)); - }else{ - int y2 = win.y + win.h - 1; - int y = height - 1 - y2; - Mat corp = new Mat(img180, new Rect(win.x, y, win.w, win.h)); - datalist.add(preprocess_img(corp, dim)); + Size imgPadSize = imgPad.size(); + Size img180Size = img180.size(); + Size img90Size = img90.size(); + Size imgNeg90Size = imgNeg90.size(); + + try { + datalist = Mats.build(); + for(Window2 win : winlist){ + if(Math.abs(win.angle) < EPS){ + Mat corp = null; + try { + corp = new Mat(imgPad, new Rect(win.x, win.y, win.w, win.h)); + datalist.add(preprocess_img_release_mat(corp.clone(), dim)); + }finally { + ReleaseUtil.release(corp); + } + }else if(Math.abs(win.angle - 90) < EPS){ + Mat corp = null; + try { + corp = new Mat(img90, new Rect(win.y, win.x, win.h, win.w)); + datalist.add(preprocess_img_release_mat(corp.clone(), dim)); + }finally { + ReleaseUtil.release(corp); + } + }else if(Math.abs(win.angle + 90) < EPS){ + Mat corp = null; + try { + int x = win.y; + int y = width - 1 - (win.x + win.w - 1); + corp = new Mat(imgNeg90, new Rect(x, y, win.w, win.h)); + datalist.add(preprocess_img_release_mat(corp.clone(), dim)); + }finally { + ReleaseUtil.release(corp); + } + }else{ + Mat corp = null; + try { + int y2 = win.y + win.h - 1; + int y = height - 1 - y2; + corp = new Mat(img180, new Rect(win.x, y, win.w, win.h)); + datalist.add(preprocess_img_release_mat(corp.clone(), dim)); + }finally { + ReleaseUtil.release(corp); + } + } } + input = set_input_release_mat(datalist); + output = net.run(Collections.singletonMap(net.getInputNames().iterator().next(), input)); + cls_prob = (float[][]) output.get(0).getValue(); + rotate = (float[][]) output.get(1).getValue(); + bbox = (float[][]) output.get(2).getValue(); + }finally { + ReleaseUtil.release(datalist); + ReleaseUtil.release(output); + ReleaseUtil.release(input); + ReleaseUtil.release(imgPad, img180, img90, imgNeg90); + output = null; input = null; } - OnnxTensor net_input = set_input(datalist); - OrtSession.Result output = net.run(Collections.singletonMap(net.getInputNames().iterator().next(), net_input)); - float[][] cls_prob = (float[][]) output.get(0).getValue(); - float[][] rotate = (float[][]) output.get(1).getValue(); - float[][] bbox = (float[][]) output.get(2).getValue(); - - //关闭对象 - for(Mat mat : datalist){ - mat.release(); - } - if(null != net_input){ - net_input.close(); - } - if(null != output){ - output.close(); - } - + //模型后处理 List ret = new ArrayList<>(); for(int i=0; i thres) { @@ -531,24 +576,24 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect float cropX = winlist.get(i).x; float cropY = winlist.get(i).y; float cropW = winlist.get(i).w; - Mat img_tmp = imgPad; + Size img_tmp_size = imgPadSize; if (Math.abs(winlist.get(i).angle - 180) < EPS) { cropY = height - 1 - (cropY + cropW - 1); - img_tmp = img180; + img_tmp_size = img180Size; }else if (Math.abs(winlist.get(i).angle - 90) < EPS) { cropX = winlist.get(i).y; cropY = winlist.get(i).x; - img_tmp = img90; + img_tmp_size = img90Size; }else if (Math.abs(winlist.get(i).angle + 90) < EPS) { cropX = winlist.get(i).y; cropY = width - 1 - (winlist.get(i).x + winlist.get(i).w - 1); - img_tmp = imgNeg90; + img_tmp_size = imgNeg90Size; } int w = (int) (sn * cropW); int x = (int) (cropX - 0.5 * sn * cropW + cropW * sn * xn + 0.5 * cropW); int y = (int) (cropY - 0.5 * sn * cropW + cropW * sn * yn + 0.5 * cropW); int angle = (int)(angleRange_ * rotate[i][0]); - if(legal(x, y, img_tmp) && legal(x + w - 1, y + w - 1, img_tmp)){ + if(legal(x, y, img_tmp_size) && legal(x + w - 1, y + w - 1, img_tmp_size)){ if(Math.abs(winlist.get(i).angle) < EPS){ ret.add(new Window2(x, y, w, w, angle, winlist.get(i).scale, cls_prob[i][1])); }else if(Math.abs(winlist.get(i).angle - 180) < EPS){ @@ -573,32 +618,33 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect * @throws OrtException * @throws IOException */ - private static List detect(OrtSession[] sessions, Mat img, Mat imgPad, float[] scoreThs, float iouThs[]) throws OrtException, IOException { - Mat img180 = new Mat(); - Core.flip(imgPad, img180, 0); - - Mat img90 = new Mat(); - Core.transpose(imgPad, img90); - - Mat imgNeg90 = new Mat(); - Core.flip(img90, imgNeg90, 0); - - List winlist = stage1(img, imgPad, sessions[0], scoreThs[0]); - winlist = NMS(winlist, true, iouThs[0]); - - winlist = stage2(imgPad, img180, sessions[1], scoreThs[1], 24, winlist); - winlist = NMS(winlist, true, iouThs[1]); - - winlist = stage3(imgPad, img180, img90, imgNeg90, sessions[2], scoreThs[2], 48, winlist); - winlist = NMS(winlist, false, iouThs[2]); - - winlist = deleteFP(winlist); - - img90.release(); - img180.release(); - imgNeg90.release(); - - return winlist; + private static List detect_release_mat(OrtSession[] sessions, Mat img, Mat imgPad, float[] scoreThs, float iouThs[]) throws OrtException, IOException { + Mat img180 = null; + Mat img90 = null; + Mat imgNeg90 = null; + try { + //前置处理 + img180 = new Mat(); + Core.flip(imgPad, img180, 0); + img90 = new Mat(); + Core.transpose(imgPad, img90); + imgNeg90 = new Mat(); + Core.flip(img90, imgNeg90, 0); + //第一步 + List winlist = stage1_release_mat(img.clone(), imgPad.clone(), sessions[0], scoreThs[0]); + winlist = NMS(winlist, true, iouThs[0]); + //第二步 + winlist = stage2_release_mat(imgPad.clone(), img180.clone(), sessions[1], scoreThs[1], 24, winlist); + winlist = NMS(winlist, true, iouThs[1]); + //第三步 + winlist = stage3_release_mat(imgPad.clone(), img180.clone(), img90.clone(), imgNeg90.clone(), sessions[2], scoreThs[2], 48, winlist); + winlist = NMS(winlist, false, iouThs[2]); + //后处理 + winlist = deleteFP(winlist); + return winlist; + }finally { + ReleaseUtil.release(imgNeg90, img90, img180, imgPad, img); + } } /** @@ -642,7 +688,7 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect ", angle=" + angle + ", scale=" + scale + ", conf=" + conf + - '}' +"\n"; + "}" +"\n"; } } } diff --git a/face-search-core/src/main/java/com/visual/face/search/core/utils/ReleaseUtil.java b/face-search-core/src/main/java/com/visual/face/search/core/utils/ReleaseUtil.java new file mode 100644 index 0000000..29fe0e2 --- /dev/null +++ b/face-search-core/src/main/java/com/visual/face/search/core/utils/ReleaseUtil.java @@ -0,0 +1,98 @@ +package com.visual.face.search.core.utils; + +import ai.onnxruntime.OnnxTensor; +import ai.onnxruntime.OrtSession; +import com.visual.face.search.core.domain.ImageMat; +import com.visual.face.search.core.domain.Mats; +import org.opencv.core.Mat; + +public class ReleaseUtil { + + public static void release(Mat ...mats){ + for(Mat mat : mats){ + if(null != mat){ + try { + mat.release(); + }catch (Exception e){ + e.printStackTrace(); + }finally { + mat = null; + } + } + } + } + + public static void release(Mats mats){ + if(null == mats || mats.isEmpty()){ + return; + } + try { + mats.release(); + }catch (Exception e){ + e.printStackTrace(); + }finally { + mats = null; + } + } + + public static void release(ImageMat ...imageMats){ + for(ImageMat imageMat : imageMats){ + if(null != imageMat){ + try { + imageMat.release(); + }catch (Exception e){ + e.printStackTrace(); + }finally { + imageMat = null; + } + } + } + } + + public static void release(OnnxTensor ...tensors){ + if(null == tensors || tensors.length == 0){ + return; + } + try { + for(OnnxTensor tensor : tensors){ + try { + if(null != tensor){ + tensor.close(); + } + }catch (Exception e) { + e.printStackTrace(); + }finally { + tensor = null; + } + } + }catch (Exception e){ + e.printStackTrace(); + }finally { + tensors = null; + } + } + + public static void release(OrtSession.Result ...results){ + if(null == results || results.length == 0){ + return; + } + try { + for(OrtSession.Result result : results){ + try { + if(null != result){ + result.close(); + } + }catch (Exception e) { + e.printStackTrace(); + }finally { + result = null; + } + } + }catch (Exception e){ + e.printStackTrace(); + }finally { + results = null; + } + } + +} diff --git a/face-search-core/src/test/resources/images/faces/debug/debug_0005.jpg b/face-search-core/src/test/resources/images/faces/debug/debug_0005.jpg new file mode 100644 index 0000000000000000000000000000000000000000..31cdb6108f09c7975b3deb0dab1cee39ccee36c7 GIT binary patch literal 19668 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 literal 0 HcmV?d00001