mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-13 13:18:58 +00:00
1、集成车牌识别模型,支持车牌检测与识别
2、新增 Milvus 身份验证支持 3、目标检测功能升级:可指定类别及topk 4、支持自定义线程池线程数量
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.20</version>
|
||||
<version>1.0.22</version>
|
||||
</parent>
|
||||
|
||||
<name>smartjavaai-common</name>
|
||||
|
||||
@@ -29,6 +29,11 @@ public class ModelConfig {
|
||||
*/
|
||||
private String batchifier;
|
||||
|
||||
/**
|
||||
* 模型预测器池大小(默认为cpu核心数)
|
||||
*/
|
||||
private int predictorPoolSize;
|
||||
|
||||
/**
|
||||
* 个性化配置(按模型类型动态解析)
|
||||
*/
|
||||
|
||||
@@ -57,6 +57,7 @@ public class R<T> {
|
||||
NO_FACE_DETECTED(3, "未检测到人脸"),
|
||||
PARAM_ERROR(4, "参数错误"),
|
||||
INVALID_VIDEO(5, "视频无效"),
|
||||
NO_OBJECT_DETECTED(6, "未检测到目标"),
|
||||
Unknown(-1, "未知错误");
|
||||
|
||||
private final int code;
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.smartjavaai.common.pool;
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import ai.djl.translate.Translator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.pool2.BasePooledObjectFactory;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
@@ -12,6 +13,7 @@ import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
* @author dwj
|
||||
* @date 2025/4/8
|
||||
*/
|
||||
@Slf4j
|
||||
public class PredictorFactory<I, O> extends BasePooledObjectFactory<Predictor<I, O>> {
|
||||
private final ZooModel<I, O> model;
|
||||
|
||||
@@ -21,6 +23,7 @@ public class PredictorFactory<I, O> extends BasePooledObjectFactory<Predictor<I,
|
||||
|
||||
@Override
|
||||
public Predictor<I, O> create() {
|
||||
log.debug("create predictor");
|
||||
return model.newPredictor();
|
||||
}
|
||||
|
||||
@@ -31,6 +34,7 @@ public class PredictorFactory<I, O> extends BasePooledObjectFactory<Predictor<I,
|
||||
|
||||
@Override
|
||||
public void destroyObject(PooledObject<Predictor<I, O>> p) {
|
||||
log.debug("close predictor");
|
||||
p.getObject().close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import ai.djl.ndarray.NDArray;
|
||||
import cn.smartjavaai.common.entity.DetectionRectangle;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
@@ -364,6 +367,7 @@ public class ImageUtils {
|
||||
int width = metrics.stringWidth(text) + padding * 2 - stroke / 2;
|
||||
int height = metrics.getHeight() + metrics.getDescent();
|
||||
int ascent = metrics.getAscent();
|
||||
y = Math.max(0, y - height);
|
||||
java.awt.Rectangle background = new java.awt.Rectangle(x, y, width, height);
|
||||
g.fill(background);
|
||||
g.setPaint(Color.WHITE);
|
||||
@@ -468,6 +472,33 @@ public class ImageUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在图像上绘制带白色背景、黑色文字的文本
|
||||
*/
|
||||
public static void putTextWithBackground(Mat image, String text, org.opencv.core.Point origin, Scalar textColor, Scalar backgroundColor, int padding) {
|
||||
// 默认字体
|
||||
int font = Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX;
|
||||
// 默认字体缩放大小
|
||||
double fontScale = 1.0;
|
||||
//线条粗细
|
||||
int thickness = 2;
|
||||
//获取文字大小
|
||||
int[] baseLine = new int[1];
|
||||
Size textSize = Imgproc.getTextSize(text, font, fontScale, thickness, baseLine);
|
||||
int textWidth = (int) textSize.width;
|
||||
int textHeight = (int) textSize.height;
|
||||
|
||||
//计算带padding的背景框
|
||||
org.opencv.core.Point bgTopLeft = new org.opencv.core.Point(origin.x - padding, origin.y - textHeight - padding);
|
||||
org.opencv.core.Point bgBottomRight = new org.opencv.core.Point(origin.x + textWidth + padding, origin.y + baseLine[0] + padding);
|
||||
|
||||
//绘制背景矩形
|
||||
Imgproc.rectangle(image, bgTopLeft, bgBottomRight, backgroundColor, Imgproc.FILLED);
|
||||
|
||||
//绘制文字(黑色)
|
||||
Imgproc.putText(image, text, origin, font, fontScale, textColor, thickness);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package cn.smartjavaai.common.utils;
|
||||
|
||||
import ai.djl.modality.cv.util.NDImageUtils;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
import ai.djl.ndarray.index.NDIndex;
|
||||
import ai.djl.ndarray.types.DataType;
|
||||
import ai.djl.ndarray.types.Shape;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 按比例缩放,剩余空间用指定颜色填充
|
||||
* @author dwj
|
||||
*/
|
||||
public class LetterBoxUtils {
|
||||
|
||||
public enum PaddingPosition {
|
||||
CENTER, LEFT_TOP, RIGHT_BOTTOM
|
||||
}
|
||||
|
||||
public static class ResizeResult {
|
||||
public NDArray image;
|
||||
public float r;
|
||||
public int left;
|
||||
public int top;
|
||||
}
|
||||
|
||||
public static ResizeResult letterboxWithMeta(NDArray paddingImg, float r, int left, int top) {
|
||||
// ... letterbox 逻辑不变
|
||||
ResizeResult result = new ResizeResult();
|
||||
result.image = paddingImg;
|
||||
result.r = r;
|
||||
result.left = left;
|
||||
result.top = top;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 按比例缩放 + padding
|
||||
*
|
||||
* @param img 原图 NDArray HWC
|
||||
* @param targetW 目标宽度
|
||||
* @param targetH 目标高度
|
||||
* @param padColor padding 填充颜色,RGB 归一化 0-1
|
||||
* @param position padding 位置:CENTER / LEFT_TOP / RIGHT_BOTTOM
|
||||
* @return 处理后的 NDArray
|
||||
*/
|
||||
public static ResizeResult letterbox(NDManager manager, NDArray img, int targetW, int targetH, float padColor, PaddingPosition position) {
|
||||
long origH = img.getShape().get(0);
|
||||
long origW = img.getShape().get(1);
|
||||
|
||||
float r = Math.min(targetW / (float) origW, targetH / (float) origH);
|
||||
int newW = Math.round(origW * r);
|
||||
int newH = Math.round(origH * r);
|
||||
|
||||
img = NDImageUtils.resize(img, newW, newH); // HWC 0~1
|
||||
|
||||
// NDArray paddingImg = manager
|
||||
// .full(new Shape(targetW, targetH, 3), padColor, DataType.UINT8);
|
||||
|
||||
NDArray paddingImg = manager.zeros(new Shape(targetW, targetH, 3), DataType.FLOAT32);
|
||||
paddingImg = paddingImg.add(114);
|
||||
|
||||
int padW = targetW - newW;
|
||||
int padH = targetH - newH;
|
||||
int top = 0, left = 0;
|
||||
|
||||
switch (position) {
|
||||
case CENTER:
|
||||
left = padW / 2;
|
||||
top = padH / 2;
|
||||
break;
|
||||
case LEFT_TOP:
|
||||
left = 0;
|
||||
top = 0;
|
||||
break;
|
||||
case RIGHT_BOTTOM:
|
||||
left = padW;
|
||||
top = padH;
|
||||
break;
|
||||
}
|
||||
paddingImg.set(new NDIndex(String.format("%d:%d,%d:%d", top, top + newH, left, left + newW)), img);
|
||||
return letterboxWithMeta(paddingImg, r, left, top);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 恢复缩放后的 box
|
||||
* @param boxes
|
||||
* @param scaleRatio
|
||||
* @param left
|
||||
* @param top
|
||||
* @param keypointStart
|
||||
* @param keypointDim
|
||||
* @return
|
||||
*/
|
||||
public static NDArray restoreBox(NDArray boxes, float scaleRatio, float left, float top, int keypointStart, int keypointDim) {
|
||||
// 处理 bbox
|
||||
NDArray x1 = boxes.get(":, 0").sub(left).div(scaleRatio);
|
||||
NDArray y1 = boxes.get(":, 1").sub(top).div(scaleRatio);
|
||||
NDArray x2 = boxes.get(":, 2").sub(left).div(scaleRatio);
|
||||
NDArray y2 = boxes.get(":, 3").sub(top).div(scaleRatio);
|
||||
|
||||
boxes.set(new NDIndex(":, 0"), x1);
|
||||
boxes.set(new NDIndex(":, 1"), y1);
|
||||
boxes.set(new NDIndex(":, 2"), x2);
|
||||
boxes.set(new NDIndex(":, 3"), y2);
|
||||
|
||||
if (keypointDim > 0) {
|
||||
for (int i = 0; i < keypointDim; i += 2) {
|
||||
int xIdx = keypointStart + i;
|
||||
int yIdx = keypointStart + i + 1;
|
||||
NDArray keyX = boxes.get(":, " + xIdx).sub(left).div(scaleRatio);
|
||||
NDArray keyY = boxes.get(":, " + yIdx).sub(top).div(scaleRatio);
|
||||
boxes.set(new NDIndex(":, " + xIdx), keyX);
|
||||
boxes.set(new NDIndex(":, " + yIdx), keyY);
|
||||
}
|
||||
}
|
||||
return boxes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.smartjavaai.common.utils;
|
||||
|
||||
import ai.djl.ndarray.NDArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/7/23
|
||||
*/
|
||||
public class NMSUtils {
|
||||
|
||||
/**
|
||||
* 通用 NMS 方法,输入 NDArray 形式的 boxes 和 scores,返回保留的索引列表
|
||||
*
|
||||
* @param boxes NDArray 形状为 (N, 4),格式为 [x1, y1, x2, y2]
|
||||
* @param scores NDArray 形状为 (N,) 或 (N,1),每个 box 的置信度
|
||||
* @param iouThreshold IOU 阈值,超过该阈值则认为有重叠
|
||||
* @return 保留框的索引列表
|
||||
*/
|
||||
public static int[] nms(NDArray boxes, NDArray scores, float iouThreshold) {
|
||||
if (boxes.isEmpty()) {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
NDArray x1 = boxes.get(":, 0");
|
||||
NDArray y1 = boxes.get(":, 1");
|
||||
NDArray x2 = boxes.get(":, 2");
|
||||
NDArray y2 = boxes.get(":, 3");
|
||||
|
||||
NDArray areas = x2.sub(x1).add(1).mul(y2.sub(y1).add(1));
|
||||
|
||||
// 按照置信度降序排序
|
||||
NDArray order = scores.argSort().flip(0);
|
||||
|
||||
List<Integer> keep = new ArrayList<>();
|
||||
|
||||
while (order.size() > 0) {
|
||||
int idx = (int)order.getLong(0);
|
||||
keep.add(idx);
|
||||
|
||||
if (order.size() == 1) break;
|
||||
|
||||
NDArray currentBox = boxes.get(idx);
|
||||
NDArray others = boxes.get(order);
|
||||
|
||||
NDArray xx1 = x1.get(order).maximum(x1.get(idx));
|
||||
NDArray yy1 = y1.get(order).maximum(y1.get(idx));
|
||||
NDArray xx2 = x2.get(order).minimum(x2.get(idx));
|
||||
NDArray yy2 = y2.get(order).minimum(y2.get(idx));
|
||||
|
||||
NDArray w = xx2.sub(xx1).add(1).maximum(0);
|
||||
NDArray h = yy2.sub(yy1).add(1).maximum(0);
|
||||
NDArray inter = w.mul(h);
|
||||
|
||||
NDArray remAreas = areas.get(order);
|
||||
NDArray union = remAreas.add(areas.get(idx)).sub(inter);
|
||||
NDArray iou = inter.div(union);
|
||||
|
||||
NDArray mask = iou.lte(iouThreshold);
|
||||
order = order.get(mask);
|
||||
}
|
||||
return keep.stream().mapToInt(i -> i).toArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -124,4 +124,20 @@ public class OpenCVUtils {
|
||||
mat.put(0, 0, data);
|
||||
return mat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 透视变换
|
||||
*
|
||||
* @param src
|
||||
* @param srcPoints
|
||||
* @param dstPoints
|
||||
* @return
|
||||
*/
|
||||
public static Mat perspectiveTransform(Mat src, Mat srcPoints, Mat dstPoints) {
|
||||
Mat dst = src.clone();
|
||||
Mat warp_mat = Imgproc.getPerspectiveTransform(srcPoints, dstPoints);
|
||||
Imgproc.warpPerspective(src, dst, warp_mat, dst.size());
|
||||
warp_mat.release();
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package cn.smartjavaai.common.utils;
|
||||
|
||||
import ai.djl.modality.cv.output.Point;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
*/
|
||||
public class PointUtils {
|
||||
|
||||
/**
|
||||
* 对 4 个关键点进行排序,顺序为:
|
||||
* 左上、右上、右下、左下
|
||||
*/
|
||||
public static List<Point> orderPoints(List<Point> points) {
|
||||
if (points == null || points.size() != 4) {
|
||||
throw new IllegalArgumentException("必须提供 4 个点");
|
||||
}
|
||||
|
||||
// 按 X 坐标升序排列
|
||||
points.sort(Comparator.comparingDouble(Point::getX));
|
||||
|
||||
List<Point> left = points.subList(0, 2);
|
||||
List<Point> right = points.subList(2, 4);
|
||||
|
||||
// 左侧两点按 Y 排序:上为 tl,下为 bl
|
||||
Point tl = left.get(0).getY() < left.get(1).getY() ? left.get(0) : left.get(1);
|
||||
Point bl = left.get(0).getY() >= left.get(1).getY() ? left.get(0) : left.get(1);
|
||||
|
||||
// 右侧两点按 Y 排序:上为 tr,下为 br
|
||||
Point tr = right.get(0).getY() < right.get(1).getY() ? right.get(0) : right.get(1);
|
||||
Point br = right.get(0).getY() >= right.get(1).getY() ? right.get(0) : right.get(1);
|
||||
|
||||
return Arrays.asList(tl, tr, br, bl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 欧式距离计算
|
||||
*
|
||||
* @param point1
|
||||
* @param point2
|
||||
* @return
|
||||
*/
|
||||
public static float distance(float[] point1, float[] point2) {
|
||||
float disX = point1[0] - point2[0];
|
||||
float disY = point1[1] - point2[1];
|
||||
float dis = (float) Math.sqrt(disX * disX + disY * disY);
|
||||
return dis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 欧式距离计算
|
||||
*
|
||||
* @param point1
|
||||
* @param point2
|
||||
* @return
|
||||
*/
|
||||
public static float distance(Point point1, Point point2) {
|
||||
double disX = point1.getX() - point2.getX();
|
||||
double disY = point1.getY() - point2.getY();
|
||||
float dis = (float) Math.sqrt(disX * disX + disY * disY);
|
||||
return dis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user