【通用视觉】集成 OpenAI CLIP 模型,支持以图搜图、以文搜图、以图搜文等功能

【通用视觉】新增 YOLO 图像分类模型支持
【ASR/TTS】集成 Sherpa TTS(语音合成)与 ASR(语音识别)模块,支持中文、粤语、方言、英文等多种语言
【目标检测】优化视频目标检测功能
This commit is contained in:
dengwenjie
2025-10-24 11:22:39 +08:00
parent 248398a46d
commit 8502b4dc79
68 changed files with 4463 additions and 44 deletions

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.25</version>
<version>1.0.26</version>
</parent>
<name>common</name>

View File

@@ -45,6 +45,12 @@ public class ModelConfig {
return clazz.cast(value);
}
public <T> T getCustomParam(String key, Class<T> clazz, T defaultValue) {
Object value = customParams.getOrDefault(key, defaultValue);
return clazz.cast(value);
}
/**
* 添加个性化配置项
*/

View File

@@ -0,0 +1,13 @@
package cn.smartjavaai.common.enums;
/**
* @author dwj
* @date 2025/5/31
*/
public enum SimilarityType {
IP, // 内积 (Inner Product)
L2, // 欧氏距离 (Euclidean Distance)
COSINE // 余弦相似度 (Cosine Similarity)
}

View File

@@ -573,4 +573,36 @@ public class BufferedImageUtils {
}
}
/**
* 绘制检测框
* @param sourceImage
* @param detectionResponse
* @throws IOException
*/
public static void drawFaceSearchResult(Graphics2D graphics, DetectionInfo detectionInfo, String text) {
graphics.setColor(Color.RED);// 边框颜色
graphics.setStroke(new BasicStroke(2)); // 线宽2像素
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
int stroke = 2;
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
graphics.setColor(Color.RED);// 边框颜色
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
//绘制人脸关键点
//人脸查询结果
if(detectionInfo.getFaceInfo().getFaceSearchResults() != null){
for (FaceSearchResult faceSearchResult : detectionInfo.getFaceInfo().getFaceSearchResults()){
if(StringUtils.isNotBlank(faceSearchResult.getMetadata())){
JsonObject metadata = GsonUtils.parseToJsonObject(faceSearchResult.getMetadata());
JsonElement nameElement = metadata.get("name");
if(metadata.has("name")){
Graphics2DUtils.drawText(graphics, nameElement.getAsString(), rectangle.getX(), rectangle.getY(), stroke, 4);
}
}
}
}
graphics.dispose();
}
}

View File

@@ -11,6 +11,7 @@ import org.opencv.core.Mat;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -19,6 +20,16 @@ import java.util.Objects;
*/
public class DJLCommonUtils {
private static final List<String> SUPPORTED_PROTOCOLS = Arrays.asList(
"file://",
"http://",
"https://",
"jar://",
"djl://",
"s3://",
"hdfs://"
);
/**
* 检查模型目录中是否存在 "serving.properties" 文件
*
@@ -158,5 +169,17 @@ public class DJLCommonUtils {
return new DetectedObjects(classNames, probabilities, boxes);
}
/**
* 判断路径是否以已知协议开头
* @param path 模型路径
* @return 是否以支持的协议开头
*/
public static boolean hasSupportedProtocol(String path) {
if (path == null || path.isEmpty()) {
return false;
}
return SUPPORTED_PROTOCOLS.stream().anyMatch(path::startsWith);
}
}

View File

@@ -1,6 +1,9 @@
package cn.smartjavaai.common.utils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 文件操作工具类
@@ -28,4 +31,132 @@ public class FileUtils {
File file = new File(path);
return file.exists() && file.isDirectory();
}
/**
* 查找指定目录下指定后缀的文件
*
* @param dir 目录
* @param suffix 文件后缀,例如 ".txt"、".wav"
* @param recursive 是否递归子目录
* @return 文件列表
*/
public static List<File> findFilesWithSuffix(File dir, String suffix, boolean recursive) {
List<File> result = new ArrayList<>();
if (dir == null || !dir.exists() || !dir.isDirectory()) {
return result;
}
searchFiles(dir, suffix, recursive, result);
return result;
}
// 搜索方法
private static void searchFiles(File dir, String suffix, boolean recursive, List<File> result) {
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isDirectory()) {
if(file.getName().endsWith(suffix)){
result.add(file);
continue;
}
if (recursive) {
searchFiles(file, suffix, true, result);
}
} else if (file.isFile() && file.getName().endsWith(suffix)) {
result.add(file);
}
}
}
/**
* 查找指定目录下指定文件名的文件
*
* @param dir 目录
* @param fileName 文件名(精确匹配)
* @param recursive 是否递归子目录
* @return 文件列表
*/
public static List<File> findFilesByName(File dir, String fileName, boolean recursive) {
List<File> result = new ArrayList<>();
if (dir == null || !dir.exists() || !dir.isDirectory() || fileName == null) {
return result;
}
searchByName(dir, fileName, recursive, result);
return result;
}
// 递归搜索方法
private static void searchByName(File dir, String fileName, boolean recursive, List<File> result) {
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isDirectory()) {
if (file.getName().equals(fileName)){
result.add(file);
continue;
}
if (recursive) {
searchByName(file, fileName, true, result);
}
} else if (file.isFile() && file.getName().equals(fileName)) {
result.add(file);
}
}
}
/**
* 将文件列表转换为绝对路径字符串
*
* @param files 文件列表
* @return 绝对路径字符串,用逗号分隔
*/
public static String joinAbsolutePaths(List<File> files) {
if (files == null || files.isEmpty()) {
return "";
}
return files.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(","));
}
/**
* 在指定目录中查找文件名包含指定关键字的文件,可选指定后缀。
*
* @param dirPath 要搜索的目录路径
* @param keyword 文件名包含的关键字(可为 null
* @param extension 文件后缀名(例如 ".wav",可为 null
* @param recursive 是否递归搜索子目录
* @return 匹配的文件列表
*/
public static List<File> searchFiles(String dirPath, String keyword, String extension, boolean recursive) {
List<File> result = new ArrayList<>();
File dir = new File(dirPath);
if (!dir.exists() || !dir.isDirectory()) {
System.err.println("目录不存在或不是目录:" + dirPath);
return result;
}
File[] files = dir.listFiles();
if (files == null) return result;
for (File file : files) {
if (file.isDirectory() && recursive) {
// 递归子目录
result.addAll(searchFiles(file.getAbsolutePath(), keyword, extension, true));
} else if (file.isFile()) {
String name = file.getName().toLowerCase();
boolean matchKeyword = (keyword == null || name.contains(keyword.toLowerCase()));
boolean matchExt = (extension == null || name.endsWith(extension.toLowerCase()));
if (matchKeyword && matchExt) {
result.add(file);
}
}
}
return result;
}
}

View File

@@ -0,0 +1,130 @@
package cn.smartjavaai.common.utils;
import cn.smartjavaai.common.enums.SimilarityType;
/**
* 特征相似度计算工具类
* 支持三种计算方式IP内积、L2欧氏距离、COSINE余弦相似度
* 所有计算结果归一化到[0,1]范围
*/
public class SimilarityUtil {
/**
* 计算特征相似度
* @param features1 特征向量1
* @param features2 特征向量2
* @param similarityType 计算类型 (IP, L2, COSINE)
* @param normalizeScore 是否归一化结果到 [0,1]
* @return 相似度
*/
public static float calculate(float[] features1, float[] features2,
SimilarityType similarityType,
boolean normalizeScore) {
validateInput(features1, features2);
switch (similarityType) {
case IP:
return innerProductSimilarity(features1, features2, normalizeScore);
case L2:
return euclideanSimilarity(features1, features2, normalizeScore);
case COSINE:
return cosineSimilarity(features1, features2, normalizeScore);
default:
throw new IllegalArgumentException("不支持的相似度计算类型: " + similarityType);
}
}
// ================ 私有计算方法 ================
/**
* 计算内积相似度(归一化到[0,1]
* 适用于归一化向量(结果范围[-1,1] -> [0,1]
*/
private static float innerProductSimilarity(float[] v1, float[] v2, boolean normalize) {
float dot = dotProduct(v1, v2);
return normalize ? (dot + 1.0f) / 2.0f : dot;
}
/**
* 计算欧氏距离相似度(归一化到[0,1]
* 距离越小相似度越高距离为0时相似度为1
*/
private static float euclideanSimilarity(float[] v1, float[] v2, boolean normalize) {
float dist = euclideanDistance(v1, v2);
return normalize ? 1.0f / (1.0f + dist) : dist;
}
/**
* 计算余弦相似度(归一化到[0,1]
* 适用于非归一化向量(结果范围[-1,1] -> [0,1]
*/
private static float cosineSimilarity(float[] v1, float[] v2, boolean normalize) {
float dot = dotProduct(v1, v2);
float norm1 = vectorNorm(v1);
float norm2 = vectorNorm(v2);
if (norm1 <= 0 || norm2 <= 0) {
return 0.0f;
}
float cosine = dot / (norm1 * norm2);
return normalize ? (cosine + 1.0f) / 2.0f : cosine;
}
// ================ 基础向量操作 ================
/**
* 计算点积(内积)
*/
public static float dotProduct(float[] v1, float[] v2) {
float sum = 0.0f;
for (int i = 0; i < v1.length; i++) {
sum += v1[i] * v2[i];
}
return sum;
}
/**
* 计算欧氏距离
*/
public static float euclideanDistance(float[] v1, float[] v2) {
float sumSquaredDiff = 0.0f;
for (int i = 0; i < v1.length; i++) {
float diff = v1[i] - v2[i];
sumSquaredDiff += diff * diff;
}
return (float) Math.sqrt(sumSquaredDiff);
}
/**
* 计算向量模长
*/
public static float vectorNorm(float[] vector) {
float sum = 0.0f;
for (float v : vector) {
sum += v * v;
}
return (float) Math.sqrt(sum);
}
// ================ 输入验证 ================
/**
* 验证输入向量
*/
private static void validateInput(float[] v1, float[] v2) {
if (v1 == null || v2 == null) {
throw new IllegalArgumentException("特征向量不能为null");
}
if (v1.length == 0 || v2.length == 0) {
throw new IllegalArgumentException("特征向量不能为空");
}
if (v1.length != v2.length) {
throw new IllegalArgumentException("特征向量长度不一致: " +
v1.length + " vs " + v2.length);
}
}
}