|
|
|
@ -14,6 +14,14 @@ import com.sourceforge.snap7.moka7.S7Client;
|
|
|
|
import jakarta.annotation.PostConstruct;
|
|
|
|
import jakarta.annotation.PostConstruct;
|
|
|
|
import lombok.Data;
|
|
|
|
import lombok.Data;
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
|
|
|
import org.opencv.core.Mat;
|
|
|
|
|
|
|
|
import org.opencv.core.MatOfPoint2f;
|
|
|
|
|
|
|
|
import org.opencv.core.Point;
|
|
|
|
|
|
|
|
import org.opencv.core.Size;
|
|
|
|
|
|
|
|
import org.opencv.imgcodecs.Imgcodecs;
|
|
|
|
|
|
|
|
import org.opencv.imgproc.Imgproc;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
|
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
|
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
|
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
|
|
@ -50,6 +58,37 @@ import java.util.concurrent.*;
|
|
|
|
@Slf4j
|
|
|
|
@Slf4j
|
|
|
|
public class S7MultiPlcService {
|
|
|
|
public class S7MultiPlcService {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 静态加载OpenCV DLL
|
|
|
|
|
|
|
|
static {
|
|
|
|
|
|
|
|
String[] dllPaths = {
|
|
|
|
|
|
|
|
System.getProperty("user.dir") + "/libs/opencv/opencv_java453.dll",
|
|
|
|
|
|
|
|
System.getProperty("user.dir") + "/out/opencv_java453.dll"
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
boolean loaded = false;
|
|
|
|
|
|
|
|
for (String dllPath : dllPaths) {
|
|
|
|
|
|
|
|
File dllFile = new File(dllPath);
|
|
|
|
|
|
|
|
if (dllFile.exists()) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
System.load(dllFile.getAbsolutePath());
|
|
|
|
|
|
|
|
log.info("OpenCV DLL 加载成功: {}", dllFile.getAbsolutePath());
|
|
|
|
|
|
|
|
loaded = true;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
} catch (UnsatisfiedLinkError e) {
|
|
|
|
|
|
|
|
log.warn("加载失败 {}: {}", dllPath, e.getMessage());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!loaded) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
System.loadLibrary("opencv_java453");
|
|
|
|
|
|
|
|
log.info("OpenCV DLL 从系统路径加载成功");
|
|
|
|
|
|
|
|
loaded = true;
|
|
|
|
|
|
|
|
} catch (UnsatisfiedLinkError e) {
|
|
|
|
|
|
|
|
log.error("OpenCV DLL 加载失败,所有路径都不存在或加载失败");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PLC连接池映射: plcNumber -> 连接池
|
|
|
|
// PLC连接池映射: plcNumber -> 连接池
|
|
|
|
private final Map<String, BlockingQueue<S7Client>> plcConnectionPools = new ConcurrentHashMap<>();
|
|
|
|
private final Map<String, BlockingQueue<S7Client>> plcConnectionPools = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
@ -436,96 +475,90 @@ public class S7MultiPlcService {
|
|
|
|
return null;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
|
|
|
AppConfig.Camera camera = new AppConfig.Camera();
|
|
|
|
|
|
|
|
camera.setCrop(new ArrayList<>());
|
|
|
|
|
|
|
|
List<Integer> crop = new ArrayList<>();
|
|
|
|
|
|
|
|
crop.add(640);
|
|
|
|
|
|
|
|
crop.add(640);
|
|
|
|
|
|
|
|
camera.getCrop().add(List.of(800, 1800));
|
|
|
|
|
|
|
|
camera.getCrop().add( List.of(800, 550));
|
|
|
|
|
|
|
|
camera.getCrop().add(List.of(2100, 550));
|
|
|
|
|
|
|
|
camera.getCrop().add(List.of(2100, 1800));
|
|
|
|
|
|
|
|
camera.setCropSize(crop);
|
|
|
|
|
|
|
|
S7MultiPlcService service = new S7MultiPlcService();
|
|
|
|
|
|
|
|
service.cropAndCompressImage("D:\\data\\media\\2026-05-15\\1\\2026-05-15-01-16-18-640.png.jpg",camera);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* 透视变换裁剪图片并压缩
|
|
|
|
* 透视变换裁剪图片并压缩
|
|
|
|
* @param imagePath 原图路径
|
|
|
|
* @param imagePath 原图路径
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
private void cropAndCompressImage(String imagePath, AppConfig.Camera config) {
|
|
|
|
private void cropAndCompressImage(String imagePath, AppConfig.Camera config) {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
|
|
|
|
|
|
|
|
File imageFile = new File(imagePath);
|
|
|
|
File imageFile = new File(imagePath);
|
|
|
|
if (!imageFile.exists()) {
|
|
|
|
if (!imageFile.exists()) {
|
|
|
|
log.warn("图片文件不存在: {}", imagePath);
|
|
|
|
log.warn("图片文件不存在: {}", imagePath);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BufferedImage originalImage = ImageIO.read(imageFile);
|
|
|
|
|
|
|
|
if (originalImage == null) {
|
|
|
|
|
|
|
|
log.warn("无法读取图片: {}", imagePath);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
List<List<Integer>> crop = config.getCrop();
|
|
|
|
List<List<Integer>> crop = config.getCrop();
|
|
|
|
List<Integer> cropSize = config.getCropSize();
|
|
|
|
List<Integer> cropSize = config.getCropSize();
|
|
|
|
|
|
|
|
|
|
|
|
int targetWidth = cropSize.get(0);
|
|
|
|
int targetWidth = cropSize.get(0);
|
|
|
|
int targetHeight = cropSize.get(1);
|
|
|
|
int targetHeight = cropSize.get(1);
|
|
|
|
|
|
|
|
|
|
|
|
// 源四边形四个顶点 (顺时针: 左上, 右上, 右下, 左下)
|
|
|
|
// crop.get(0) -> 目标左上, crop.get(1) -> 目标右上
|
|
|
|
double x0 = crop.get(0).get(0), y0 = crop.get(0).get(1);
|
|
|
|
// crop.get(2) -> 目标右下, crop.get(3) -> 目标左下
|
|
|
|
double x1 = crop.get(1).get(0), y1 = crop.get(1).get(1);
|
|
|
|
// 源图像中的四个点
|
|
|
|
double x2 = crop.get(2).get(0), y2 = crop.get(2).get(1);
|
|
|
|
Point[] srcArray = new Point[] {
|
|
|
|
double x3 = crop.get(3).get(0), y3 = crop.get(3).get(1);
|
|
|
|
new Point(crop.get(0).get(0), crop.get(0).get(1)),
|
|
|
|
|
|
|
|
new Point(crop.get(1).get(0), crop.get(1).get(1)),
|
|
|
|
// 目标矩形四个顶点 (左上, 右上, 右下, 左下)
|
|
|
|
new Point(crop.get(2).get(0), crop.get(2).get(1)),
|
|
|
|
double u0 = 0, v0 = 0;
|
|
|
|
new Point(crop.get(3).get(0), crop.get(3).get(1))
|
|
|
|
double u1 = targetWidth, v1 = 0;
|
|
|
|
};
|
|
|
|
double u2 = targetWidth, v2 = targetHeight;
|
|
|
|
MatOfPoint2f srcPoints2f = new MatOfPoint2f(srcArray);
|
|
|
|
double u3 = 0, v3 = targetHeight;
|
|
|
|
|
|
|
|
|
|
|
|
// 目标矩形的四个顶点
|
|
|
|
// 创建目标图像
|
|
|
|
Point[] dstArray = new Point[] {
|
|
|
|
BufferedImage croppedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
|
|
|
|
new Point(0, 0),
|
|
|
|
int[] srcPixels = originalImage.getRGB(0, 0, originalImage.getWidth(), originalImage.getHeight(), null, 0, originalImage.getWidth());
|
|
|
|
new Point(targetWidth, 0),
|
|
|
|
int srcWidth = originalImage.getWidth();
|
|
|
|
new Point(targetWidth, targetHeight),
|
|
|
|
int srcHeight = originalImage.getHeight();
|
|
|
|
new Point(0, targetHeight)
|
|
|
|
|
|
|
|
};
|
|
|
|
// 计算透视变换的逆变换系数 (从目标坐标映射到源坐标)
|
|
|
|
MatOfPoint2f dstPoints = new MatOfPoint2f(dstArray);
|
|
|
|
// 使用双线性插值的透视变换
|
|
|
|
|
|
|
|
for (int y = 0; y < targetHeight; y++) {
|
|
|
|
// 计算透视变换矩阵
|
|
|
|
for (int x = 0; x < targetWidth; x++) {
|
|
|
|
Mat transformMatrix = Imgproc.getPerspectiveTransform(srcPoints2f, dstPoints);
|
|
|
|
// 计算相对于目标矩形的归一化坐标
|
|
|
|
|
|
|
|
double mu = (double) x / targetWidth;
|
|
|
|
// 读取源图像
|
|
|
|
double mv = (double) y / targetHeight;
|
|
|
|
Mat srcImage = Imgcodecs.imread(imagePath);
|
|
|
|
|
|
|
|
if (srcImage.empty()) {
|
|
|
|
// 计算透视变换后的源坐标 (使用双线性插值)
|
|
|
|
log.warn("无法读取图片: {}", imagePath);
|
|
|
|
double srcX = bilinearInterpolate(
|
|
|
|
return;
|
|
|
|
bilinearInterpolate(x0, x1, mu),
|
|
|
|
}
|
|
|
|
bilinearInterpolate(x3, x2, mu),
|
|
|
|
|
|
|
|
mv
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
double srcY = bilinearInterpolate(
|
|
|
|
|
|
|
|
bilinearInterpolate(y0, y1, mu),
|
|
|
|
|
|
|
|
bilinearInterpolate(y3, y2, mu),
|
|
|
|
|
|
|
|
mv
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 从源图像采样像素
|
|
|
|
// 创建目标图像并进行透视变换
|
|
|
|
int srcXInt = (int) Math.round(srcX);
|
|
|
|
Mat dstImage = new Mat();
|
|
|
|
int srcYInt = (int) Math.round(srcY);
|
|
|
|
Imgproc.warpPerspective(srcImage, dstImage, transformMatrix, new Size(targetWidth, targetHeight));
|
|
|
|
|
|
|
|
|
|
|
|
if (srcXInt >= 0 && srcXInt < srcWidth && srcYInt >= 0 && srcYInt < srcHeight) {
|
|
|
|
// 保存结果
|
|
|
|
int pixel = srcPixels[srcYInt * srcWidth + srcXInt];
|
|
|
|
Imgcodecs.imwrite(imagePath, dstImage);
|
|
|
|
croppedImage.setRGB(x, y, pixel);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
croppedImage.setRGB(x, y, 0xFFFFFF); // 白色填充
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 压缩并保存
|
|
|
|
// 释放资源
|
|
|
|
compressAndSaveImage(croppedImage, imageFile, config.getCompress());
|
|
|
|
srcImage.release();
|
|
|
|
|
|
|
|
dstImage.release();
|
|
|
|
|
|
|
|
transformMatrix.release();
|
|
|
|
|
|
|
|
srcPoints2f.release();
|
|
|
|
|
|
|
|
dstPoints.release();
|
|
|
|
|
|
|
|
|
|
|
|
log.info("图片透视裁剪完成: {}, 尺寸: {}x{}", imagePath, targetWidth, targetHeight);
|
|
|
|
log.info("图片透视裁剪完成: {}, 尺寸: {}x{}", imagePath, targetWidth, targetHeight);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
} catch (Exception e) {
|
|
|
|
log.error("图片透视裁剪失败: {}", imagePath, e);
|
|
|
|
log.error("图片透视裁剪失败: {}", imagePath, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 双线性插值
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private double bilinearInterpolate(double v0, double v1, double t) {
|
|
|
|
|
|
|
|
return v0 + (v1 - v0) * t;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* 保存为JPEG并覆盖原图
|
|
|
|
* 保存为JPEG并覆盖原图
|
|
|
|
|