mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-10 03:28:49 +00:00
【通用视觉】集成 OpenAI CLIP 模型,支持以图搜图、以文搜图、以图搜文等功能
【通用视觉】新增 YOLO 图像分类模型支持 【ASR/TTS】集成 Sherpa TTS(语音合成)与 ASR(语音识别)模块,支持中文、粤语、方言、英文等多种语言 【目标检测】优化视频目标检测功能
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加个性化配置项
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user