Compare commits
3 Commits
main
...
splicing_t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7451b5e60a | ||
|
|
6725634e8e | ||
|
|
c7f9482883 |
@ -5,6 +5,7 @@ import com.zhangy.skyeye.common.extend.anno.IgnoreAuth;
|
||||
import com.zhangy.skyeye.common.extend.enums.EnumUtil;
|
||||
import com.zhangy.skyeye.common.extend.exception.ServiceException;
|
||||
import com.zhangy.skyeye.common.extend.util.DateUtil;
|
||||
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
|
||||
import com.zhangy.skyeye.common.pojo.result.Result;
|
||||
import com.zhangy.skyeye.jm.consts.JmJobModeEnum;
|
||||
import com.zhangy.skyeye.jm.dto.JmJobDTO;
|
||||
@ -77,6 +78,10 @@ public class JmJobController {
|
||||
@PostMapping("/save")
|
||||
public Object insert(@Valid @RequestBody JmJobDTO e) {
|
||||
JmJobModeEnum mode = EnumUtil.parseEx(JmJobModeEnum.class, e.getMode());
|
||||
if (mode != JmJobModeEnum.CRUISE && ObjectUtil.isEmpty(e.getPointList())) {
|
||||
throw ServiceException.noLog("任务点不能为空");
|
||||
}
|
||||
|
||||
clearId(e);
|
||||
// 默认执行一次性任务
|
||||
if (e.getType() == null) {
|
||||
|
||||
@ -52,4 +52,14 @@ public class JmAirlineStatusDTO {
|
||||
|
||||
// 前一张右上、右下
|
||||
private Double[] beforeRight;
|
||||
|
||||
/** 航线终点经度 */
|
||||
private Double endLon;
|
||||
|
||||
/** 航线终点纬度 */
|
||||
private Double endLat;
|
||||
|
||||
private Integer headingDiff;
|
||||
|
||||
private double[] values;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.zhangy.skyeye.jm.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.zhangy.skyeye.jm.consts.JmJobModeEnum;
|
||||
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
|
||||
import com.zhangy.skyeye.publics.consts.ExecStatusEnum;
|
||||
import lombok.Data;
|
||||
@ -62,6 +63,9 @@ public class JmUavStatusDTO {
|
||||
/** sar 图片亮度 */
|
||||
private volatile Integer sarImageLight;
|
||||
|
||||
/** 任务模式 */
|
||||
private JmJobModeEnum jobMode;
|
||||
|
||||
/**
|
||||
* 获取sar当前状态
|
||||
*
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.zhangy.skyeye.jm.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.zhangy.skyeye.publics.dto.GeoTiffDTO;
|
||||
import com.zhangy.skyeye.py.dto.PyImageParamDTO;
|
||||
import lombok.Getter;
|
||||
@ -14,13 +15,16 @@ import java.util.List;
|
||||
@Getter
|
||||
@Setter
|
||||
public class JmImage extends GeoTiffDTO {
|
||||
|
||||
|
||||
/** 主键 */
|
||||
private Long id;
|
||||
|
||||
|
||||
/** 任务执行ID */
|
||||
private Long jobId;
|
||||
|
||||
/** 任务配置ID */
|
||||
private Long jobConfId;
|
||||
|
||||
/** 无人机 */
|
||||
private Long uavId;
|
||||
|
||||
@ -39,19 +43,32 @@ public class JmImage extends GeoTiffDTO {
|
||||
/** 归一化前最大值 */
|
||||
private Float max;
|
||||
|
||||
/** 航线的图片序号 */
|
||||
/** 层级 */
|
||||
private Integer tailLevel;
|
||||
|
||||
/** 图片序号 */
|
||||
private Integer imageNo;
|
||||
|
||||
/** 起始帧 */
|
||||
private Integer frameFrom;
|
||||
|
||||
/** 结束帧 */
|
||||
private Integer frameTo;
|
||||
|
||||
/** 旋转角 */
|
||||
private Double rotateAngle;
|
||||
|
||||
|
||||
/*
|
||||
非数据库字段
|
||||
*/
|
||||
|
||||
/** 文件名 */
|
||||
private String fileName;
|
||||
|
||||
|
||||
/** 图像类型 */
|
||||
private Integer fileType;
|
||||
|
||||
|
||||
/** 存储路径 */
|
||||
private String filePath;
|
||||
|
||||
@ -69,7 +86,7 @@ public class JmImage extends GeoTiffDTO {
|
||||
|
||||
/** 无人机名称 */
|
||||
private String uavName;
|
||||
|
||||
|
||||
/**
|
||||
* 文件上传时间
|
||||
* sar实时影像:航线的第一张图片在服务器生成的时间
|
||||
@ -85,7 +102,7 @@ public class JmImage extends GeoTiffDTO {
|
||||
/** 图像亮度 */
|
||||
private Integer brightness;
|
||||
|
||||
/** 图像识别结果 */
|
||||
/** 识别结果 */
|
||||
private List<JmImageItem> itemList;
|
||||
|
||||
/** 转图像识别算法参数 */
|
||||
|
||||
@ -45,6 +45,15 @@ public interface JmImageMapper {
|
||||
*/
|
||||
List<JmImage> selectByAirline(@Param("type") Integer fileType, @Param("array") Long... airlineExecId);
|
||||
|
||||
/**
|
||||
* 查询航线最后一张图
|
||||
*
|
||||
* @param fileType 航线执行ID,必需
|
||||
* @param airlineExecId 文件类型,非必需
|
||||
* @return
|
||||
*/
|
||||
JmImage selectLastByAirline(@Param("type") Integer fileType, @Param("array") Long... airlineExecId);
|
||||
|
||||
/**
|
||||
* 按主键查询
|
||||
*/
|
||||
|
||||
@ -41,6 +41,11 @@ public interface JmImageService {
|
||||
*/
|
||||
List<JmImage> selectLowByAirline(Long airlineExecId);
|
||||
|
||||
/**
|
||||
* 查询航线最后一张图
|
||||
*/
|
||||
JmImage selectLastByAirline(FileTypeEnum fileType, Long airlineExecId);
|
||||
|
||||
/**
|
||||
* 按主键查询详情
|
||||
*/
|
||||
|
||||
@ -119,6 +119,11 @@ public class JmImageServiceImpl implements JmImageService {
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JmImage selectLastByAirline(FileTypeEnum fileType, Long airlineExecId) {
|
||||
return imageMapper.selectLastByAirline(fileType.getValue(), airlineExecId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JmImage> selectById(Long... id) {
|
||||
return imageMapper.selectById(id);
|
||||
|
||||
@ -67,4 +67,16 @@ public class GeoTiffDTO implements Serializable {
|
||||
right2Lon = right1Lon;
|
||||
right2Lat = left2Lat;
|
||||
}
|
||||
|
||||
public void updateCoord(double left1Lon, double left1Lat, double left2Lon, double left2Lat,
|
||||
double right1Lon, double right1Lat, double right2Lon, double right2Lat) {
|
||||
this.left1Lon = left1Lon;
|
||||
this.left1Lat = left1Lat;
|
||||
this.left2Lon = left2Lon;
|
||||
this.left2Lat = left2Lat;
|
||||
this.right1Lon = right1Lon;
|
||||
this.right1Lat = right1Lat;
|
||||
this.right2Lon = right2Lon;
|
||||
this.right2Lat = right2Lat;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,11 +9,11 @@ import java.nio.ByteOrder;
|
||||
public class ChecksumUtil {
|
||||
|
||||
/**
|
||||
* 计算校验和(表8、2),排除最后8个字节
|
||||
* 表4,排除最后8个字节
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static int checksum8(byte[] data) {
|
||||
public static int checksum32(byte[] data) {
|
||||
long checksum = 0;
|
||||
ByteBuffer checksumBuffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
|
||||
int u32Count = data.length / 4;
|
||||
@ -23,6 +23,21 @@ public class ChecksumUtil {
|
||||
return (int) (checksum & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 表14,排除最后8个字节
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static int checksum8(byte[] data) {
|
||||
int checksum = 0;
|
||||
ByteBuffer checksumBuffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
|
||||
int u32Count = data.length;
|
||||
for (int i = 0; i < u32Count - 8; i++) { // 不检查校验和、帧尾
|
||||
checksum += (Byte.toUnsignedInt(checksumBuffer.get()));
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断校验和(表9),排除第13字节
|
||||
* @param data 如果分包数据不足1472,有效长度后面的是前一个分包留下的数据,并非是0
|
||||
|
||||
@ -4,6 +4,7 @@ import com.zhangy.skyeye.common.extend.exception.ServiceException;
|
||||
import com.zhangy.skyeye.common.extend.util.FileUtil;
|
||||
import com.zhangy.skyeye.jm.dto.JmImageRotateDTO;
|
||||
import com.zhangy.skyeye.publics.dto.GeoTiffDTO;
|
||||
import com.zhangy.skyeye.sar.dto.SarBackImageFrameDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.geotools.api.geometry.Bounds;
|
||||
import org.geotools.coverage.grid.GridCoverage2D;
|
||||
@ -38,15 +39,18 @@ public class ImageUtil {
|
||||
public static Mat afterCreate(Mat image, JmImageRotateDTO rotateDTO) {
|
||||
if (rotateDTO != null) {
|
||||
OpenCVUtil.flip(image, rotateDTO.getType());
|
||||
Mat rgbaImage = OpenCVUtil.rotate(image, rotateDTO.getAngle() * -1);
|
||||
image.release();
|
||||
image = rgbaImage;
|
||||
}
|
||||
int lightRate = rotateDTO.getLightRate();
|
||||
if (lightRate != 0) {
|
||||
//OpenCVUtil.multiply(image, lightRate);
|
||||
OpenCVUtil.enhanceContrast(image, lightRate, 0);
|
||||
double angle = rotateDTO.getAngle();
|
||||
if (angle != 0) {
|
||||
Mat rgbaImage = OpenCVUtil.rotate(image, angle * -1);
|
||||
image.release();
|
||||
image = rgbaImage;
|
||||
}
|
||||
}
|
||||
// int lightRate = rotateDTO.getLightRate();
|
||||
// if (lightRate != 0) {
|
||||
// //OpenCVUtil.multiply(image, lightRate);
|
||||
// OpenCVUtil.enhanceContrast(image, lightRate, 0);
|
||||
// }
|
||||
return image;
|
||||
}
|
||||
|
||||
@ -69,6 +73,39 @@ public class ImageUtil {
|
||||
return OpenCVUtil.write(outPath, image, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载回传图像,先转置再转为Mat
|
||||
*
|
||||
* @param frameData 图像帧数据,包含参数信息和未转置的8位灰度图像数据
|
||||
* @param imageFrame 图像帧对象
|
||||
* @return 转置后的4通道图像Mat
|
||||
*/
|
||||
public static Mat loadCurrImageMat(byte[] frameData, SarBackImageFrameDTO imageFrame) {
|
||||
Mat currImage = null;
|
||||
int offset = imageFrame.IMAGE_OFFSET;
|
||||
|
||||
switch (imageFrame.getImgType()) {
|
||||
case 0: // tif
|
||||
currImage = ImageUtil.createImage(frameData, offset, imageFrame.getWidth(),
|
||||
imageFrame.getHeight(), imageFrame.getImageBitDeep());
|
||||
break;
|
||||
case 1: // jpg,先去掉参数信息,只保留图像数据
|
||||
case 4: { // png
|
||||
byte[] validData = new byte[frameData.length - offset];
|
||||
System.arraycopy(frameData, offset, validData, 0, validData.length);
|
||||
currImage = ImageUtil.createImageFromJpg(validData);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log.error("不支持的图片类型imgType=" + imageFrame.getImgType() + ",丢弃图片");
|
||||
}
|
||||
if (currImage != null && currImage.empty()) {
|
||||
log.error("图片数据是空的,数据长度:" + frameData.length);
|
||||
currImage.release();
|
||||
}
|
||||
return currImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建图像对象
|
||||
* @param rawData 原始像素数据
|
||||
@ -213,12 +250,11 @@ public class ImageUtil {
|
||||
* @param currGray 新接收的单通道灰度图(右部分),会被释放资源
|
||||
* @return 拼接后的4通道Mat矩阵
|
||||
*/
|
||||
public static Mat join(Mat baseMat, Integer baseNo, Mat currGray, int currNo) {
|
||||
public static Mat join(Mat baseMat, Integer baseNo, Mat currGray, int currNo, boolean isFirst) {
|
||||
int width = currGray.cols();
|
||||
|
||||
// 将当前灰度图转换为4通道(BGRA),支持16位
|
||||
Mat curr4 = convertToRGBA(currGray);
|
||||
if (baseNo == null || baseMat == null) {
|
||||
if (baseNo == null || baseMat == null || baseMat.empty()) { // 首图无需拼接,直接返回
|
||||
return curr4;
|
||||
}
|
||||
// 统一数据类型:如果基准图是8位而当前是16位,需要转换
|
||||
@ -241,14 +277,13 @@ public class ImageUtil {
|
||||
Mat result = null;
|
||||
// 检查帧号是否连续
|
||||
int missingFrames = currNo - baseNo - 1;
|
||||
if (missingFrames > 0) {
|
||||
log.info("--------> 帧" + currNo +"开始拼接,填充" + missingFrames + ",基准图:" + baseMat.rows() + "," + baseMat.type() + "," + baseMat.dims() +
|
||||
";当前图:" + curr4.rows() + "," + curr4.type() + "," + curr4.dims());
|
||||
if (!isFirst && missingFrames > 0) {
|
||||
// 创建缺失的透明图像(4通道)
|
||||
Mat transparent = new Mat(baseHeight, width, baseMat.type(), new Scalar(0, 0, 0, 0));
|
||||
|
||||
// 准备要拼接的图像列表
|
||||
log.info("当前帧" + currNo +"准备拼接图片,基准图:" + baseMat.rows() + "," + baseMat.type() + "," + baseMat.dims() +
|
||||
";填充图:" + transparent.rows() + "," + transparent.type() + "," + transparent.dims() +
|
||||
";当前图:" + curr4.rows() + "," + curr4.type() + "," + curr4.dims());
|
||||
List<Mat> imagesToConcat = new ArrayList<>();
|
||||
imagesToConcat.add(baseMat);
|
||||
|
||||
@ -272,12 +307,10 @@ public class ImageUtil {
|
||||
imagesToConcat.add(curr4);
|
||||
|
||||
result = new Mat();
|
||||
log.info("当前帧" + currNo +"准备拼接图片,基准图:" + baseMat.rows() + "," + baseMat.type() + "," + baseMat.dims() +
|
||||
";当前图:" + curr4.rows() + "," + curr4.type() + "," + curr4.dims());
|
||||
Core.hconcat(imagesToConcat, result);
|
||||
baseMat.release();
|
||||
}
|
||||
log.info("当前帧" + currNo +"拼接完成");
|
||||
log.info("<-------- 帧" + currNo +"拼接完成");
|
||||
// 释放资源,若missingFrames < 0 则说明当前图是前面的图迟到了,无法拼接,按丢图处理
|
||||
curr4.release();
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.zhangy.skyeye.sar.dto;
|
||||
|
||||
import com.zhangy.skyeye.publics.utils.ChecksumUtil;
|
||||
import lombok.Data;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@ -26,8 +27,12 @@ public class SarBackFramePackDTO {
|
||||
/** 帧尾标志 0x7EFFDC02 */
|
||||
private int tailFlag;
|
||||
|
||||
public SarBackFramePackDTO() {
|
||||
|
||||
}
|
||||
|
||||
public boolean check() {
|
||||
return headFlag == 0x7EFFDC01;
|
||||
return headFlag == 0x7EFFDC01 && tailFlag == 0x7EFFDC02;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,23 +62,31 @@ public class SarBackFramePackDTO {
|
||||
* @param data 实际接收的帧数据
|
||||
* @param hasLastPacket 是否包含尾包
|
||||
*/
|
||||
public SarBackFramePackDTO(byte[] data, boolean hasLastPacket) {
|
||||
public static SarBackFramePackDTO parse(byte[] data, boolean hasLastPacket) {
|
||||
SarBackFramePackDTO dto = new SarBackFramePackDTO();
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
// 首包包含的头校验信息
|
||||
this.headFlag = buffer.getInt();
|
||||
this.size = buffer.getInt();
|
||||
dto.headFlag = buffer.getInt();
|
||||
if (dto.headFlag != 0x7EFFDC01) {
|
||||
return null;
|
||||
}
|
||||
dto.size = buffer.getInt();
|
||||
|
||||
// 图像数据 = 帧数据 - 头尾校验数据
|
||||
int frameSize = data.length - 8 - (hasLastPacket ? 8 : 0);
|
||||
this.frameData = new byte[frameSize];
|
||||
buffer.get(this.frameData, 0, frameSize);
|
||||
dto.frameData = new byte[frameSize];
|
||||
buffer.get(dto.frameData, 0, frameSize);
|
||||
|
||||
// 尾包包含的尾校验信息
|
||||
if (hasLastPacket) {
|
||||
this.checksum = buffer.getInt();
|
||||
this.tailFlag = buffer.getInt();
|
||||
dto.checksum = (int) (buffer.getInt() & 0xFFFFFFFFL);
|
||||
dto.tailFlag = buffer.getInt();
|
||||
if (dto.tailFlag != 0x7EFFDC02 || ChecksumUtil.checksum8(data) != dto.checksum) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -50,7 +50,7 @@ public class SarControlPackDTO {
|
||||
buffer.put(sarControl.toBytes());
|
||||
// 尾
|
||||
byte[] checkData = buffer.array();
|
||||
this.checksum = ChecksumUtil.checksum8(checkData);
|
||||
this.checksum = ChecksumUtil.checksum32(checkData);
|
||||
buffer.putInt(checksum);
|
||||
buffer.putInt(endFlag);
|
||||
return buffer.array();
|
||||
|
||||
@ -15,32 +15,32 @@ import java.util.zip.CRC32;
|
||||
public class SarImagePacketDTO extends SarBackPacketDTO {
|
||||
|
||||
/**
|
||||
* 图像帧ID,随机不连续 uint32_t
|
||||
* 图像帧ID,随机不连续
|
||||
*/
|
||||
long sessionId;
|
||||
|
||||
/**
|
||||
* 帧序号,从0开始 uint32_t
|
||||
* 帧序号,从0开始
|
||||
*/
|
||||
int packetNo;
|
||||
|
||||
/**
|
||||
* 帧数据大小(字节) uint32_t
|
||||
* 帧数据大小(字节)
|
||||
*/
|
||||
int size;
|
||||
|
||||
/**
|
||||
* 是否为最后一个包 uint8_t
|
||||
* 是否为最后一个包
|
||||
*/
|
||||
boolean isLast;
|
||||
|
||||
/**
|
||||
* 校验值 uint32_t
|
||||
* 校验值
|
||||
*/
|
||||
long crc32;
|
||||
|
||||
/**
|
||||
* 包数据 std::vector<char>
|
||||
* 包数据
|
||||
*/
|
||||
byte[] data;
|
||||
|
||||
@ -55,12 +55,11 @@ public class SarImagePacketDTO extends SarBackPacketDTO {
|
||||
bb.order(ByteOrder.BIG_ENDIAN); // 网络字节序
|
||||
|
||||
// 解析数据包头部
|
||||
pkt.sessionId = bb.getInt() & 0xFFFFFFFFL; // uint32_t
|
||||
pkt.packetNo = bb.getInt(); // uint32_t
|
||||
pkt.size = bb.getInt(); // uint32_t
|
||||
pkt.isLast = bb.get() != 0; // uint8_t -> boolean
|
||||
pkt.crc32 = bb.getInt() & 0xFFFFFFFFL; // uint32_t
|
||||
|
||||
pkt.sessionId = bb.getInt() & 0xFFFFFFFFL;
|
||||
pkt.packetNo = bb.getInt();
|
||||
pkt.size = bb.getInt();
|
||||
pkt.isLast = bb.get() != 0;
|
||||
pkt.crc32 = bb.getInt() & 0xFFFFFFFFL;
|
||||
// 提取数据部分
|
||||
byte[] pktData = new byte[pkt.size];
|
||||
bb.get(pktData);
|
||||
@ -68,10 +67,9 @@ public class SarImagePacketDTO extends SarBackPacketDTO {
|
||||
// CRC校验
|
||||
long crcCalc = calculateCrc32(pktData);
|
||||
if (pkt.crc32 != crcCalc) {
|
||||
log.warn("CRC不匹配,序号=" + pkt.packetNo + ",丢弃");
|
||||
log.warn("CRC不匹配,序号=" + pkt.packetNo + ",丢弃包");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建Packet对象
|
||||
pkt.data = pktData;
|
||||
return pkt;
|
||||
|
||||
@ -31,6 +31,9 @@ public class SarImagePacketGroupDTO extends SarBackPacketGroupDTO<SarImagePacket
|
||||
/** 分包数据 */
|
||||
private Map<Integer, SarImagePacketDTO> packets = new HashMap<>();
|
||||
|
||||
/** 整包数据 */
|
||||
private byte[] groupData;
|
||||
|
||||
public SarImagePacketDTO getLastPacket() {
|
||||
if (packets.size() == 0) {
|
||||
return null;
|
||||
@ -68,7 +71,7 @@ public class SarImagePacketGroupDTO extends SarBackPacketGroupDTO<SarImagePacket
|
||||
SarImagePacketGroupDTO group = new SarImagePacketGroupDTO();
|
||||
group.setSourceIp(sourceIp);
|
||||
group.setSessionId(packet.getSessionId());
|
||||
// group.setPacketNum(packet.getPacketNum());
|
||||
// group.setPacketNum(packet.getPacketNum());
|
||||
group.setLastArrayTime(System.currentTimeMillis());
|
||||
group.put(packet);
|
||||
group.setMaxNo(packet.getPacketNo());
|
||||
|
||||
@ -0,0 +1,152 @@
|
||||
package com.zhangy.skyeye.sar.listen;
|
||||
|
||||
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
|
||||
import com.zhangy.skyeye.jm.service.JmJobStatusService;
|
||||
|
||||
import com.zhangy.skyeye.sar.dto.SarImagePacketDTO;
|
||||
import com.zhangy.skyeye.sar.dto.SarImagePacketGroupDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 接收udp和处理异步进行,可靠传输
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class SarAsynAbstractUdpProcessor extends SarAbstractUdpProcessor<SarImagePacketGroupDTO, SarImagePacketDTO> {
|
||||
|
||||
// 任务队列容量
|
||||
private final int QUEUE_CAPACITY = 1000;
|
||||
|
||||
// 任务队列
|
||||
private BlockingQueue<SarImagePacketGroupDTO> packetQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
|
||||
|
||||
// 异步线程
|
||||
private Thread processThread = new Thread(this::startProcessing);
|
||||
|
||||
@Autowired
|
||||
private JmJobStatusService jobStatusService;
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public SarAsynAbstractUdpProcessor() {
|
||||
super();
|
||||
running = true;
|
||||
processThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步处理
|
||||
* @param group
|
||||
*/
|
||||
protected abstract void afterJoin(SarImagePacketGroupDTO group);
|
||||
|
||||
/**
|
||||
* 启动处理线程从队列消费数据
|
||||
*/
|
||||
private void startProcessing() {
|
||||
while (running) {
|
||||
try {
|
||||
SarImagePacketGroupDTO group = packetQueue.poll(100, TimeUnit.MILLISECONDS);
|
||||
if (group != null) {
|
||||
try {
|
||||
afterJoin(group);
|
||||
} catch (Exception ex) {
|
||||
log.error(getText() + "异步线程处理图像包错误:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.error(getText() + "异步线程处理异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以合并回图帧,首包不能丢
|
||||
* @param group
|
||||
* @return
|
||||
*/
|
||||
public boolean canJoin(SarImagePacketGroupDTO group) {
|
||||
Map<Integer, SarImagePacketDTO> packets = group.getPackets();
|
||||
//log.info("定时判断合并udp:" + group.getSessionId() + "," + packets.keySet());
|
||||
// 首包包含图片格式信息,不能丢,否则图片无法打开
|
||||
return packets.get(0) != null && group.getLastPacket().isLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void expireProcess(SarImagePacketGroupDTO group) {
|
||||
// 定时判断:超过 PACKET_TIMEOUT 时间未收到新包且符合生成条件则合成图
|
||||
if (canJoin(group)) {
|
||||
log.info("[UDP] sesionid="+ group.getSessionId() + "超时合并!");
|
||||
join(group);
|
||||
this.packetQueue.offer(group);
|
||||
} else { // 超时且无法合并,丢弃
|
||||
Map<Integer, SarImagePacketDTO> packets = group.getPackets();
|
||||
if (packets.size() == 1 && packets.values().iterator().next().isLast()) {
|
||||
// 雷达传图有bug,每帧的尾包会多发一个,丢弃且不打印日志
|
||||
} else {
|
||||
log.warn(getText() + "-移除超时帧: {}", group.getSessionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将upd分包数据合并为完整帧(帧数据 + 头尾校验),因为无法确定分包大小,分包缺失无法补充0
|
||||
* 合并后删除frameMap的分包数据
|
||||
* 使用ByteBuffer替代ByteArrayOutputStream,以减少内存分配。ByteArrayOutputStream 的 toByteArray()底层用Arrays.copyOf实现,
|
||||
* 有性能开销;ByteBuffer的array()无需拷贝数组
|
||||
* @param group
|
||||
* @return
|
||||
*/
|
||||
private void join(SarImagePacketGroupDTO group) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(group.getTotalSize());
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
Map<Integer, SarImagePacketDTO> packets = group.getPackets();
|
||||
for (int i = 0; i <= group.getLastPacket().getPacketNo(); i++) {
|
||||
SarImagePacketDTO packet = packets.get(i);
|
||||
if (packet != null) {
|
||||
buffer.put(packet.getData(), 0, packet.getSize());
|
||||
}
|
||||
}
|
||||
group.setGroupData(buffer.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SarImagePacketGroupDTO putPacket(String sourceIp, SarImagePacketDTO packet) {
|
||||
// 1.包放入缓冲区
|
||||
long sessionId = packet.getSessionId();
|
||||
SarImagePacketGroupDTO group;
|
||||
if (contains(sourceIp, sessionId)) {
|
||||
group = get(sourceIp, sessionId);
|
||||
group.put(packet);
|
||||
} else {
|
||||
group = SarImagePacketGroupDTO.init(sourceIp, packet);
|
||||
// 将帧与航线绑定,当航线2开机但航线1的图片未传完时,用航线ID判断生成的图片是属于哪条航线
|
||||
JmAirlineStatusDTO currAirline = jobStatusService.getCurrAirline(sourceIp);
|
||||
if (currAirline == null) {
|
||||
return null; // 没有在执行的任务或航线
|
||||
}
|
||||
group.setAirlineExecId(currAirline.getExecId());
|
||||
put(sourceIp, sessionId, group);
|
||||
}
|
||||
//System.out.println(packet);
|
||||
// 2.判断是否全部包到达,是则合并
|
||||
SarImagePacketDTO last = group.getLastPacket();
|
||||
// 若所有包已收到则合并
|
||||
if (last != null && last.isLast() && group.getPackets().size() == group.getMaxNo() + 1) {
|
||||
log.info("[UDP] sesionid="+ packet.getSessionId() + "全部到达合并!");
|
||||
join(group);
|
||||
this.packetQueue.offer(group);
|
||||
remove(sourceIp, sessionId);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ package com.zhangy.skyeye.sar.listen;
|
||||
|
||||
import com.zhangy.skyeye.common.extend.util.JsonUtil;
|
||||
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
|
||||
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
|
||||
import com.zhangy.skyeye.jm.entity.JmImage;
|
||||
import com.zhangy.skyeye.jm.service.JmJobStatusService;
|
||||
import com.zhangy.skyeye.sar.dto.SarBackFramePackDTO;
|
||||
@ -9,8 +10,10 @@ import com.zhangy.skyeye.sar.dto.SarBackImageFrameDTO;
|
||||
import com.zhangy.skyeye.sar.dto.SarImagePacketDTO;
|
||||
import com.zhangy.skyeye.sar.dto.SarImagePacketGroupDTO;
|
||||
import com.zhangy.skyeye.sar.service.ISarImageService;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -24,7 +27,7 @@ import java.util.Map;
|
||||
@Primary
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SarImageUdpProcessor extends SarAbstractUdpProcessor<SarImagePacketGroupDTO, SarImagePacketDTO> {
|
||||
public class SarImageUdpProcessor extends SarAsynAbstractUdpProcessor {
|
||||
|
||||
@Autowired
|
||||
private ISarImageService sarImageService;
|
||||
@ -32,62 +35,52 @@ public class SarImageUdpProcessor extends SarAbstractUdpProcessor<SarImagePacket
|
||||
@Autowired
|
||||
private JmJobStatusService jobStatusService;
|
||||
|
||||
protected void afterJoin(byte[] framePacketData, SarImagePacketGroupDTO group) {
|
||||
@Setter
|
||||
@Value("${ld.sar.image.default-level:15}")
|
||||
private Integer defaultTailLevel;
|
||||
|
||||
@Override
|
||||
protected void afterJoin(SarImagePacketGroupDTO group) {
|
||||
boolean hasFirstPacket = group.getPackets().get(0) != null;// 如果有首包,则加头校验8字节
|
||||
boolean hasLastPacket = group.getLastPacket().isLast(); // 如果有尾包,则加尾校验8字节
|
||||
SarBackFramePackDTO framePack = new SarBackFramePackDTO(framePacketData, hasLastPacket);
|
||||
byte[] frameData = framePack.getFrameData();
|
||||
SarBackImageFrameDTO imageFram = new SarBackImageFrameDTO(frameData);
|
||||
if (!imageFram.check()) {
|
||||
log.warn("图像帧 " + group.getSessionId() + " 已丢弃,因为图像信息不完整。数据:" + imageFram);
|
||||
if (!hasLastPacket) {
|
||||
log.warn("帧(sessionId=" + group.getSessionId() + ")缺失尾包,丢弃帧");
|
||||
}
|
||||
|
||||
byte[] framePacketData = group.getGroupData();
|
||||
SarBackFramePackDTO framePack = SarBackFramePackDTO.parse(framePacketData, hasLastPacket);
|
||||
if (framePack == null) {
|
||||
log.warn("图像帧 " + group.getSessionId() + " 已丢弃,帧数据校验失败");
|
||||
return;
|
||||
}
|
||||
log.debug("图像帧数据:" + imageFram);
|
||||
|
||||
// 生成图片并更新数据库
|
||||
JmImage imageInfo = sarImageService.parseImage(group.getSourceIp(), group.getAirlineExecId(), frameData, imageFram);
|
||||
// 推送,仅当前有任务时,jobId 和 uavId 不能为空
|
||||
if (imageInfo != null) {
|
||||
log.info("推送图片:" + JsonUtil.toString(imageInfo));
|
||||
sarBackWsService.sendImg(imageInfo);
|
||||
byte[] frameData = framePack.getFrameData();
|
||||
SarBackImageFrameDTO frameDTO = new SarBackImageFrameDTO(frameData);
|
||||
if (!frameDTO.check()) {
|
||||
log.warn("图像帧 " + group.getSessionId() + " 已丢弃,因为图像信息不完整。数据:" + frameDTO);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.debug("图像帧数据:" + frameDTO);
|
||||
|
||||
/**
|
||||
* 判断是否可以合并回图帧,首包不能丢
|
||||
*
|
||||
* @param group
|
||||
* @return
|
||||
*/
|
||||
public boolean canJoin(SarImagePacketGroupDTO group) {
|
||||
Map<Integer, SarImagePacketDTO> packets = group.getPackets();
|
||||
//log.info("定时判断合并udp:" + group.getSessionId() + "," + packets.keySet());
|
||||
// 首包包含图片格式信息,不能丢,否则图片无法打开
|
||||
return packets.get(0) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将upd分包数据合并为完整帧(帧数据 + 头尾校验),因为无法确定分包大小,分包缺失无法补充0
|
||||
* 合并后删除frameMap的分包数据
|
||||
* 使用ByteBuffer替代ByteArrayOutputStream,以减少内存分配。ByteArrayOutputStream 的 toByteArray()底层用Arrays.copyOf实现,
|
||||
* 有性能开销;ByteBuffer的array()无需拷贝数组
|
||||
*
|
||||
* @param group
|
||||
* @return
|
||||
*/
|
||||
private byte[] join(SarImagePacketGroupDTO group) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(group.getTotalSize());
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
Map<Integer, SarImagePacketDTO> packets = group.getPackets();
|
||||
// 使用ByteBuffer的批量操作,减少系统调用
|
||||
for (int i = 0; i <= group.getLastPacket().getPacketNo(); i++) {
|
||||
SarImagePacketDTO packet = packets.get(i);
|
||||
if (packet != null) {
|
||||
buffer.put(packet.getData(), 0, packet.getSize());
|
||||
}
|
||||
// 1.取航线
|
||||
Long airlineExecId = group.getAirlineExecId();
|
||||
if (airlineExecId == null) {
|
||||
return;
|
||||
}
|
||||
JmUavStatusDTO uav = jobStatusService.getCurrUav(group.getSourceIp());
|
||||
if (uav == null) {
|
||||
return;
|
||||
}
|
||||
// 查询图像对应的航线,不能查当前航线,因为可能是前一航线没传完的图
|
||||
JmAirlineStatusDTO currAirline = uav.getAirline(airlineExecId);
|
||||
if (currAirline == null) {
|
||||
return;
|
||||
}
|
||||
// 2.拼接图片
|
||||
JmImage base = sarImageService.parseImage(uav, currAirline, airlineExecId, frameData, frameDTO);
|
||||
if (base == null) {
|
||||
return;
|
||||
}
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -95,58 +88,9 @@ public class SarImageUdpProcessor extends SarAbstractUdpProcessor<SarImagePacket
|
||||
return "雷达回图";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SarImagePacketGroupDTO putPacket(String sourceIp, SarImagePacketDTO packet) {
|
||||
// 1.包放入缓冲区
|
||||
long sessionId = packet.getSessionId();
|
||||
SarImagePacketGroupDTO group;
|
||||
if (contains(sourceIp, sessionId)) {
|
||||
group = get(sourceIp, sessionId);
|
||||
group.put(packet);
|
||||
} else {
|
||||
group = SarImagePacketGroupDTO.init(sourceIp, packet);
|
||||
// 将帧与航线绑定,当航线2开机但航线1的图片未传完时,用航线ID判断生成的图片是属于哪条航线
|
||||
JmAirlineStatusDTO currAirline = jobStatusService.getCurrAirline(sourceIp);
|
||||
if (currAirline == null) {
|
||||
return null; // 没有在执行的任务或航线
|
||||
}
|
||||
group.setAirlineExecId(currAirline.getExecId());
|
||||
put(sourceIp, sessionId, group);
|
||||
}
|
||||
// 2.判断是否全部包到达,是则合并
|
||||
SarImagePacketDTO last = group.getLastPacket();
|
||||
// 若所有包已收到则合并
|
||||
if (last != null && last.isLast() && group.getPackets().size() == group.getMaxNo() + 1) {
|
||||
log.info("[UDP] sesionid=" + packet.getSessionId() + "全部到达合并!");
|
||||
byte[] framePacketData = join(group);
|
||||
afterJoin(framePacketData, group);
|
||||
remove(sourceIp, sessionId);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String getProcessorName() {
|
||||
return "sarImageProcessor";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void expireProcess(SarImagePacketGroupDTO group) {
|
||||
// 定时判断:超过 PACKET_TIMEOUT 时间未收到新包且符合生成条件则合成图
|
||||
if (canJoin(group)) {
|
||||
log.info("[UDP] sesionid: {} 超时合并!", group.getSessionId());
|
||||
byte[] framePacketData = join(group);
|
||||
afterJoin(framePacketData, group);
|
||||
} else {
|
||||
// 超时且无法合并,丢弃
|
||||
Map<Integer, SarImagePacketDTO> packets = group.getPackets();
|
||||
if (packets.size() == 1 && packets.values().iterator().next().isLast()) {
|
||||
// 雷达传图有bug,每帧的尾包会多发一个,丢弃且不打印日志
|
||||
} else {
|
||||
log.warn("{}-移除超时帧: {}", getText(), group.getSessionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.zhangy.skyeye.sar.service;
|
||||
|
||||
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
|
||||
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
|
||||
import com.zhangy.skyeye.jm.entity.JmImage;
|
||||
import com.zhangy.skyeye.sar.dto.SarBackImageFrameDTO;
|
||||
|
||||
@ -15,5 +17,5 @@ public interface ISarImageService {
|
||||
* @param frameData 图像帧数据
|
||||
* @param imageFrame 图像帧
|
||||
*/
|
||||
JmImage parseImage(String sourceIp, Long airlineExecId, byte[] frameData, SarBackImageFrameDTO imageFrame);
|
||||
JmImage parseImage(JmUavStatusDTO uav, JmAirlineStatusDTO currAirline, Long airlineExecId, byte[] frameData, SarBackImageFrameDTO imageFrame);
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ public class SarBackWsServiceImpl implements ISarBackWsService {
|
||||
|
||||
@Override
|
||||
public void sendImg(JmImage imageInfo) {
|
||||
log.info("===send image back ======");
|
||||
simpMessageingTemplate.convertAndSend(WebSocketKey.SAR_BACK_IMAGE, imageInfo);
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.zhangy.skyeye.sar.service.impl;
|
||||
|
||||
import com.zhangy.skyeye.common.extend.util.MathUtil;
|
||||
import com.zhangy.skyeye.jm.consts.JmJobModeEnum;
|
||||
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
|
||||
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
|
||||
import com.zhangy.skyeye.redis.utils.RedisUtil;
|
||||
@ -14,6 +15,7 @@ import com.zhangy.skyeye.publics.service.SysFileTypeService;
|
||||
import com.zhangy.skyeye.publics.utils.ImageUtil;
|
||||
import com.zhangy.skyeye.publics.utils.OpenCVUtil;
|
||||
import com.zhangy.skyeye.sar.dto.SarBackImageFrameDTO;
|
||||
import com.zhangy.skyeye.sar.service.ISarBackWsService;
|
||||
import com.zhangy.skyeye.sar.service.ISarImageService;
|
||||
import com.zhangy.skyeye.sar.service.SarWsAsyncService;
|
||||
import com.zhangy.skyeye.sar.util.RadarDisplayOptions;
|
||||
@ -44,20 +46,15 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
|
||||
@Autowired
|
||||
private SarWsAsyncService sarWsAsyncService;
|
||||
@Autowired
|
||||
protected ISarBackWsService sarBackWsService;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
// 图片最大宽度,前端说和电脑有关,4096保险点,一般是4096 8192 16384
|
||||
@Value("${skyeye.sar.image.max:4096}")
|
||||
@Setter
|
||||
private int IMG_MAX_WITH;
|
||||
// 起始帧号
|
||||
private final String CACHE_FIELD_START_FRAME_NO = "startFrameNo";
|
||||
// 当前帧号
|
||||
private final String CACHE_FIELD_CURR_FRAME_NO = "currFrameNo";
|
||||
// 缓存超时(秒)
|
||||
private final long CACHE_EXPIRE_SECOND = 24 * 3600;
|
||||
// 拼图数量
|
||||
@Value("${skyeye.sar.image.count:5}")
|
||||
private long count;
|
||||
|
||||
/**
|
||||
* 获取基准图像信息
|
||||
@ -65,91 +62,40 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
* @param airlineId 航线执行ID
|
||||
* @param singleWidth 单条图片宽度
|
||||
* @param frameNo 当前帧号
|
||||
* @return 返回非空的图像信息,其字段 imageNo 一定有值
|
||||
* @return 返回非空的图像信息
|
||||
*/
|
||||
private JmImage getBaseImage(Long airlineId, int singleWidth, int frameNo) {IMG_MAX_WITH=1;
|
||||
List<JmImage> imageList = imageService.selectLowByAirline(airlineId);
|
||||
private JmImage getBaseImage(JmUavStatusDTO uav, Long airlineId, int singleWidth, int frameNo) {
|
||||
JmImage last = imageService.selectLastByAirline(FileTypeEnum.SAR_IMAGE_LOW, airlineId);
|
||||
|
||||
// last = null;
|
||||
|
||||
String cacheKey = "jmImgJoin-" + airlineId;
|
||||
JmImage base = null;
|
||||
// 情况1:航线第一张图
|
||||
if (ObjectUtil.isEmpty(imageList)) {
|
||||
if (last == null) {
|
||||
base = new JmImage();
|
||||
base.setImageNo(1);
|
||||
redisUtil.hset(cacheKey, CACHE_FIELD_START_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);
|
||||
base.setFrameFrom(frameNo);
|
||||
return base;
|
||||
} else if (uav.getJobMode() == JmJobModeEnum.CRUISE) { // 手动模式不拼接
|
||||
base = new JmImage();
|
||||
base.setImageNo(last.getImageNo() + 1);
|
||||
base.setFrameFrom(frameNo);
|
||||
return base;
|
||||
}
|
||||
// 情况2:如果最后一张还能拼图,则直接返回继续拼
|
||||
JmImage last = imageList.get(imageList.size() - 1);
|
||||
Integer startFrameNo = (Integer) redisUtil.hget(cacheKey, CACHE_FIELD_START_FRAME_NO);
|
||||
int currWidth = startFrameNo == null ? 0 : singleWidth * (frameNo - startFrameNo + 1); // 图宽(当前图+基准图)
|
||||
int surplusNum = (IMG_MAX_WITH - currWidth) / singleWidth; // 还可以拼图片数
|
||||
Integer baseNo = (Integer) redisUtil.hget("jmImgJoin-" + airlineId, CACHE_FIELD_CURR_FRAME_NO);
|
||||
if (startFrameNo == null || currWidth < IMG_MAX_WITH ||
|
||||
baseNo == null || (frameNo - baseNo + 1 <= surplusNum)) { // 当前图+填充 不能超过允许拼接数
|
||||
log.info("当前宽度:" + currWidth + " < " + IMG_MAX_WITH + " 可以继续拼接");
|
||||
File lastFile = new File(last.getFilePath());
|
||||
if (frameNo - last.getFrameFrom() < count) { // 当前图+填充 不能超过允许拼接数
|
||||
log.info("当前帧:" + last.getFrameFrom() + "," + frameNo + " 可以继续拼接");
|
||||
return last;
|
||||
}
|
||||
// 情况3:已经拼接到最大数量,或者当前图+填充数量超过允许拼接数量,创建新图像文件
|
||||
log.info("当前宽度:" + currWidth + " > " + IMG_MAX_WITH + " 重新拼接,当前帧号" + frameNo + "作为首帧");
|
||||
log.info("当前帧:" + last.getFrameFrom() + "," + frameNo + " 重新拼接,当前帧号" + frameNo + "作为首帧");
|
||||
base = new JmImage();
|
||||
redisUtil.hset(cacheKey, CACHE_FIELD_START_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);
|
||||
base.setImageNo(last.getImageNo() + 1);
|
||||
base.setFrameFrom(frameNo);
|
||||
return base;
|
||||
}
|
||||
|
||||
private void modCoord(SarBackImageFrameDTO imageFrame, boolean lostImage, JmAirlineStatusDTO currAirline) {
|
||||
JmImageRotateDTO rotateDTO = JmImageRotateDTO.rotate(currAirline.getTargetHeading(), currAirline.getDirection());
|
||||
Double[] before = currAirline.getBeforeRight();
|
||||
boolean isFirst = before == null;
|
||||
if (isFirst) {
|
||||
before = new Double[4];
|
||||
currAirline.setBeforeRight(before);
|
||||
}
|
||||
// 使用前一张图的右侧坐标作为后一张图的左侧,前提是没丢图
|
||||
if (!isFirst && !lostImage) {
|
||||
/*imageFrame.setLon1(before[0]);
|
||||
imageFrame.setLat1(before[1]);
|
||||
imageFrame.setLon4(before[2]);
|
||||
imageFrame.setLat4(before[3]);*/
|
||||
}
|
||||
before[0] = imageFrame.getLon5();
|
||||
before[1] = imageFrame.getLat5();
|
||||
before[2] = imageFrame.getLon8();
|
||||
before[3] = imageFrame.getLat8();
|
||||
/*switch (rotateDTO.getType()) {
|
||||
case 0:
|
||||
case 1:
|
||||
// 使用前一张图的右侧坐标作为后一张图的左侧,前提是没丢图
|
||||
if (!isFirst && !lostImage) {
|
||||
imageFrame.setLon1(before[0]);
|
||||
imageFrame.setLat1(before[1]);
|
||||
imageFrame.setLon4(before[2]);
|
||||
imageFrame.setLat4(before[3]);
|
||||
} else if (before == null) {
|
||||
before = new Double[4];
|
||||
currAirline.setBeforeRight(before);
|
||||
}
|
||||
before[0] = imageFrame.getLon5();
|
||||
before[1] = imageFrame.getLat5();
|
||||
before[2] = imageFrame.getLon8();
|
||||
before[3] = imageFrame.getLat8();
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
if (!isFirst && !lostImage) {
|
||||
imageFrame.setLon5(before[0]);
|
||||
imageFrame.setLat5(before[1]);
|
||||
imageFrame.setLon8(before[2]);
|
||||
imageFrame.setLat8(before[3]);
|
||||
}
|
||||
before[0] = imageFrame.getLon1();
|
||||
before[1] = imageFrame.getLat1();
|
||||
before[2] = imageFrame.getLon4();
|
||||
before[3] = imageFrame.getLat4();
|
||||
break;
|
||||
} */
|
||||
}
|
||||
|
||||
/**
|
||||
* 将原始图像数据保存为dat文件,图像转png并将路径推送给前端
|
||||
*
|
||||
@ -159,107 +105,59 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
* @param imageFrame 图像帧
|
||||
*/
|
||||
@Override
|
||||
public JmImage parseImage(String sourceIp, Long airlineExecId, byte[] frameData, SarBackImageFrameDTO imageFrame) {
|
||||
if (airlineExecId == null) {
|
||||
return null;
|
||||
}
|
||||
public JmImage parseImage(JmUavStatusDTO uav, JmAirlineStatusDTO currAirline, Long airlineExecId, byte[] frameData, SarBackImageFrameDTO imageFrame) {
|
||||
int frameNo = imageFrame.getFrameNo();
|
||||
JmUavStatusDTO uav = jobStatusService.getCurrUav(sourceIp);
|
||||
// 若载荷任务结束,sar仍然回传图像则 uav为空。此场景仅限sardemo,实际sar不会发生
|
||||
if (uav == null) {
|
||||
return null;
|
||||
}
|
||||
// 查询图像对应的航线,不能查当前航线,因为可能是前一航线没传完的图
|
||||
JmAirlineStatusDTO currAirline = uav.getAirline(airlineExecId);
|
||||
if (currAirline == null) {
|
||||
return null;
|
||||
}
|
||||
Long jobExecId = uav.getJobExecId();
|
||||
//----------------------------------- 处理图像 --------------------------------------------------------
|
||||
long start = System.currentTimeMillis();
|
||||
// 1.保存dat(异步)
|
||||
sarWsAsyncService.saveImageDat(jobExecId, airlineExecId + "-" + frameNo + ".dat", frameData);
|
||||
|
||||
// 2.生成当前图像矩阵
|
||||
Mat currImage = loadCurrImageMat(frameData, imageFrame);
|
||||
Mat currImage = ImageUtil.loadCurrImageMat(frameData, imageFrame);
|
||||
if (currImage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3.保存图像png,用航线ID+序号命名
|
||||
JmImage base = getBaseImage(airlineExecId, currImage.width(), imageFrame.getFrameNo());
|
||||
String imageName = airlineExecId + "-" + base.getImageNo() +".png";
|
||||
String[] imagePath = sysFileTypeService.getFilePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, imageName);
|
||||
String currPath = imagePath[0];
|
||||
JmImage base = getBaseImage(uav, airlineExecId, currImage.width(), imageFrame.getFrameNo());
|
||||
String baseName = airlineExecId + "-" + base.getImageNo() +"-base.png";
|
||||
String[] basePath = sysFileTypeService.getFilePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, baseName);
|
||||
|
||||
System.out.println("帧:" + frameNo);
|
||||
// 4.保存基准图(同步),并拼接
|
||||
Integer baseNo = base.getFrameTo();
|
||||
|
||||
// 4.保存基准图(同步),用于下次拼接
|
||||
Integer baseNo = (Integer) redisUtil.hget("jmImgJoin-" + airlineExecId, CACHE_FIELD_CURR_FRAME_NO);
|
||||
boolean lostImage = baseNo != null && (frameNo - baseNo > 1); // 判断是否丢图
|
||||
String basePath = sysFileTypeService.getAbsolutePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId,
|
||||
airlineExecId + "-" + base.getImageNo() +"-base.png");
|
||||
Mat baseMat = generateBaseMat(base, currImage, frameNo, imageFrame.getMax(), basePath, baseNo);
|
||||
Mat baseMat = generateBaseMat(base, currImage, frameNo, imageFrame.getMax(), basePath[0], baseNo);
|
||||
if (baseMat == null) { // 拼接失败 或 基准图生成失败则跳过,按丢图处理
|
||||
return null;
|
||||
}
|
||||
if (lostImage) {
|
||||
log.warn("丢图"+(frameNo - baseNo)+"张!当前帧" + frameNo + ",前帧" + baseNo);
|
||||
}
|
||||
|
||||
//modCoord(imageFrame, lostImage, currAirline);
|
||||
redisUtil.hset("jmImgJoin-" + airlineExecId, CACHE_FIELD_CURR_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);// 更新帧号
|
||||
// ### 亮度调整,用于可靠udp版本图像,固定使用系数0.5
|
||||
// 拆分多张图片,去掉自适应调整
|
||||
if (IMG_MAX_WITH > 20000) {
|
||||
// 更新坐标
|
||||
boolean isFirst = base.getId() == null;
|
||||
// ### 亮度调整,用于可靠udp版本图像,固定使用系数0.5 手动模式不调整
|
||||
// enhance image
|
||||
// if (uav.getJobMode() != JmJobModeEnum.CRUISE) {
|
||||
SarImageToneAdjuster.autoToneWithClipping(baseMat, 0.5f);
|
||||
// RadarDisplayOptions options = new RadarDisplayOptions();
|
||||
// SarImageToneAdjuster.processForDisplayMat(baseMat, options);
|
||||
// SarImageToneAdjuster.applyPseudoColorMat(baseMat, options);
|
||||
}
|
||||
// }
|
||||
|
||||
// 5.保存后处理图(异步),用于前端显示
|
||||
generateAfterMat(currAirline, baseMat, currPath, uav.getSarImageLight());
|
||||
String afterName = airlineExecId + "-" + base.getImageNo() + ".png";
|
||||
String[] afterPath = sysFileTypeService.getFilePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, afterName);
|
||||
|
||||
JmImageRotateDTO rotateDTO = JmImageRotateDTO.rotate(currAirline.getTargetHeading(), currAirline.getDirection());
|
||||
rotateDTO.setLightRate(uav.getSarImageLight());
|
||||
base.setRotateAngle(rotateDTO.getAngle());
|
||||
rotateDTO.setAngle(0); // 不旋转
|
||||
generateAfterMat(baseMat, afterPath[0], rotateDTO);
|
||||
saveImage(uav, airlineExecId, base, afterPath, imageFrame, frameNo, currAirline);
|
||||
// 6.更新基准图坐标和帧号
|
||||
JmImage imageInfo = saveImage(uav, airlineExecId, base, imagePath, imageFrame, frameNo, lostImage);
|
||||
//updateCoord(airlineExecId, currAirline, imageFrame);
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
log.info("生成" + imageFrame.getImageBitDeep()+"位雷达回传图像:帧序号" + frameNo + "," +
|
||||
imageInfo.getRelativePath() + ",耗时" + (end - start)/1000 + "秒");
|
||||
return imageInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载回传图像,先转置再转为Mat
|
||||
*
|
||||
* @param frameData 图像帧数据,包含参数信息和未转置的8位灰度图像数据
|
||||
* @param imageFrame 图像帧对象
|
||||
* @return 转置后的4通道图像Mat
|
||||
*/
|
||||
private Mat loadCurrImageMat(byte[] frameData, SarBackImageFrameDTO imageFrame) {
|
||||
Mat currImage = null;
|
||||
int offset = imageFrame.IMAGE_OFFSET;
|
||||
|
||||
switch (imageFrame.getImgType()) {
|
||||
case 0: // tif
|
||||
currImage = ImageUtil.createImage(frameData, offset, imageFrame.getWidth(),
|
||||
imageFrame.getHeight(), imageFrame.getImageBitDeep());
|
||||
break;
|
||||
case 1: // jpg,先去掉参数信息,只保留图像数据
|
||||
case 4: { // png
|
||||
byte[] validData = new byte[frameData.length - offset];
|
||||
System.arraycopy(frameData, offset, validData, 0, validData.length);
|
||||
currImage = ImageUtil.createImageFromJpg(validData);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log.error("不支持的图片类型imgType=" + imageFrame.getImgType() + ",丢弃图片");
|
||||
}
|
||||
if (currImage != null && currImage.empty()) {
|
||||
log.error("图片数据是空的,数据长度:" + frameData.length);
|
||||
currImage.release();
|
||||
}
|
||||
return currImage;
|
||||
log.info("生成" + imageFrame.getImageBitDeep() + "位雷达回传图像:帧序号" + frameNo + "," +
|
||||
base.getRelativePath() + ",耗时" + (end - start) / 1000 + "秒");
|
||||
// 向前端推送
|
||||
base.setJobId(base.getJobConfId());
|
||||
sarBackWsService.sendImg(base);
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,13 +182,52 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
OpenCVUtil.multiply(currImage , currMax / baseMax);
|
||||
}
|
||||
}
|
||||
Mat baseMat = ImageUtil.join(baseImage, baseNo, currImage, currNo); // 会释放 currImage baseImage 资源
|
||||
Mat baseMat = ImageUtil.join(baseImage, baseNo, currImage, currNo, base.getId() == null); // 会释放 currImage baseImage 资源
|
||||
if (baseMat != null) { // 若拼接成功则回写硬盘,拼接失败按丢图处理,不改动基准图
|
||||
OpenCVUtil.write(imagePath, baseMat, false);
|
||||
}
|
||||
return baseMat;
|
||||
}
|
||||
|
||||
private Mat generateBaseMat2(JmImage base, Mat currImage, int currNo, float currMax, String imagePath, Integer baseNo) {
|
||||
Mat baseImage = OpenCVUtil.read(imagePath);
|
||||
|
||||
// ==============================
|
||||
// 分位数匹配逻辑
|
||||
// ==============================
|
||||
if (base != null && base.getId() != null
|
||||
&& baseImage != null
|
||||
&& currImage != null) {
|
||||
|
||||
double baseP = SarImageToneAdjuster.computePercentile(baseImage, 0.99);
|
||||
double currP = SarImageToneAdjuster.computePercentile(currImage, 0.99);
|
||||
|
||||
if (baseP > 0 && currP > 0) {
|
||||
|
||||
double scale = baseP / currP;
|
||||
|
||||
// 限制比例,防止跳变
|
||||
double MIN_SCALE = 0.7;
|
||||
double MAX_SCALE = 1.5;
|
||||
|
||||
scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
|
||||
|
||||
OpenCVUtil.multiply(currImage, scale);
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// 原拼接逻辑不变
|
||||
// ==============================
|
||||
Mat baseMat = ImageUtil.join(baseImage, baseNo, currImage, currNo, base.getId() == null);
|
||||
|
||||
if (baseMat != null) {
|
||||
OpenCVUtil.write(imagePath, baseMat, false);
|
||||
}
|
||||
|
||||
return baseMat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成后处理图,将拼接好的基准图转置并调整对比度
|
||||
*
|
||||
@ -299,12 +236,45 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
* @param imagePath 后处理图路径,每条航线对应一张图,每次生成会覆盖
|
||||
* @param imageLight 图像亮度倍数,0是不调整
|
||||
*/
|
||||
private void generateAfterMat(JmAirlineStatusDTO currAirline, Mat baseMat, String imagePath, int imageLight) {
|
||||
private void generateAfterMat(Mat baseMat, String imagePath, JmImageRotateDTO rotateDTO) {
|
||||
// 后处理参数
|
||||
JmImageRotateDTO rotateDTO = JmImageRotateDTO.rotate(currAirline.getTargetHeading(), currAirline.getDirection());
|
||||
rotateDTO.setLightRate(imageLight);
|
||||
Mat afterMat = ImageUtil.afterCreate(baseMat, rotateDTO); // 会释放 baseMat 资源
|
||||
sarWsAsyncService.write(imagePath, afterMat, true);
|
||||
OpenCVUtil.write(imagePath, afterMat, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载图像坐标
|
||||
* @param currAirline
|
||||
* @param image
|
||||
* @param imageFrame
|
||||
* @param isFirst
|
||||
*/
|
||||
private void loadCoord(JmJobModeEnum jobMode, JmAirlineStatusDTO currAirline, JmImage image, SarBackImageFrameDTO imageFrame, boolean isFirst) {
|
||||
double[] values = currAirline.getValues();
|
||||
if (isFirst) {
|
||||
if (values == null) {
|
||||
values = new double[9];
|
||||
currAirline.setValues(values);
|
||||
}
|
||||
if (values[4] != image.getImageNo() && image.getImageNo() > 1 && jobMode != JmJobModeEnum.CRUISE) { // 1、4用前一张图5、8坐标
|
||||
// System.out.println("mode:" + jobMode.getText());
|
||||
values[0] = values[5];
|
||||
values[1] = values[6];
|
||||
values[2] = values[7];
|
||||
values[3] = values[8];
|
||||
} else {
|
||||
values[0] = imageFrame.getLon1();
|
||||
values[1] = imageFrame.getLat1();
|
||||
values[2] = imageFrame.getLon4();
|
||||
values[3] = imageFrame.getLat4();
|
||||
}
|
||||
}
|
||||
values[4] = image.getImageNo();
|
||||
values[5] = imageFrame.getLon5();
|
||||
values[6] = imageFrame.getLat5();
|
||||
values[7] = imageFrame.getLon8();
|
||||
values[8] = imageFrame.getLat8();
|
||||
image.updateCoord(values[0], values[1], values[2], values[3], values[5], values[6], values[7], values[8]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -313,24 +283,16 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
* @param airlineId 航线执行ID
|
||||
*/
|
||||
private JmImage saveImage(JmUavStatusDTO uav, Long airlineId, JmImage image, String[] imagePath,
|
||||
SarBackImageFrameDTO imageFrame, int frameNo, boolean lostImage) {
|
||||
SarBackImageFrameDTO imageFrame, int frameNo, JmAirlineStatusDTO currAirline) {
|
||||
boolean isFirst = image.getId() == null;
|
||||
|
||||
// 拼接后的图片的四角坐标
|
||||
double minLon = isFirst ? MathUtil.min(imageFrame.getLon1(), imageFrame.getLon4(), imageFrame.getLon5(), imageFrame.getLon8())
|
||||
: MathUtil.min(imageFrame.getLon1(), imageFrame.getLon4(), imageFrame.getLon5(), imageFrame.getLon8(), image.getLeft1Lon(), image.getRight1Lon());
|
||||
double maxLon = isFirst ? MathUtil.max(imageFrame.getLon1(), imageFrame.getLon4(), imageFrame.getLon5(), imageFrame.getLon8())
|
||||
: MathUtil.max(imageFrame.getLon1(), imageFrame.getLon4(), imageFrame.getLon5(), imageFrame.getLon8(), image.getLeft1Lon(), image.getRight1Lon());
|
||||
double minLat = isFirst ? MathUtil.min(imageFrame.getLat1(), imageFrame.getLat4(), imageFrame.getLat5(), imageFrame.getLat8())
|
||||
: MathUtil.min(imageFrame.getLat1(), imageFrame.getLat4(), imageFrame.getLat5(), imageFrame.getLat8(), image.getLeft1Lat(), image.getLeft2Lat());
|
||||
double maxLat = isFirst ? MathUtil.max(imageFrame.getLat1(), imageFrame.getLat4(), imageFrame.getLat5(), imageFrame.getLat8())
|
||||
: MathUtil.max(imageFrame.getLat1(), imageFrame.getLat4(), imageFrame.getLat5(), imageFrame.getLat8(), image.getLeft1Lat(), image.getLeft2Lat());
|
||||
// 更新坐标和序号
|
||||
image.updateCoord(minLon, maxLon, minLat, maxLat);
|
||||
// loadCoord(uav.getJobMode(), currAirline, image, imageFrame, isFirst);
|
||||
loadCoord(JmJobModeEnum.CREATE, currAirline, image, imageFrame, isFirst);
|
||||
|
||||
Float currMax = !isFirst && image.getMax() > imageFrame.getMax() ? image.getMax() : imageFrame.getMax();
|
||||
image.setMax(currMax);
|
||||
image.setImageTime(imageFrame.getDate());
|
||||
image.setFrameTo(frameNo);
|
||||
image.setBrightness(uav.getSarImageLight());
|
||||
|
||||
if (isFirst) {
|
||||
File file = new File(imagePath[0]);
|
||||
@ -348,11 +310,11 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
imageService.updateNotNull(image);
|
||||
}
|
||||
// 前端需要
|
||||
image.setJobId(uav.getJobId()); // 前端通过任务配置ID关联任务
|
||||
image.setJobConfId(uav.getJobId());
|
||||
image.setJobName(uav.getJobName());
|
||||
image.setUavName(uav.getUavName());
|
||||
image.setPayloadName(uav.getSarName());
|
||||
image.setBrightness(uav.getSarImageLight());
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,18 +2,24 @@ package com.zhangy.skyeye.sar.util;
|
||||
|
||||
import org.opencv.core.*;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.CLAHE;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.photo.Photo;
|
||||
import org.opencv.photo.Tonemap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* SAR图像亮度修正,在拼接图片后食用
|
||||
*/
|
||||
@Slf4j
|
||||
public class SarImageToneAdjuster {
|
||||
|
||||
// 示例
|
||||
@ -643,4 +649,261 @@ public class SarImageToneAdjuster {
|
||||
|
||||
image.put(0, 0, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinhard 局部色调映射
|
||||
*/
|
||||
public static Mat reinhardToneMapping(Mat src,
|
||||
float gamma,
|
||||
float intensity,
|
||||
float lightAdapt,
|
||||
float colorAdapt) {
|
||||
|
||||
Mat srcFloat = new Mat();
|
||||
src.convertTo(srcFloat, CvType.CV_32FC3, 1.0 / 255.0);
|
||||
|
||||
// 注意这里的类型
|
||||
Tonemap tonemap = Photo.createTonemapReinhard(
|
||||
gamma,
|
||||
intensity,
|
||||
lightAdapt,
|
||||
colorAdapt
|
||||
);
|
||||
|
||||
Mat dstFloat = new Mat();
|
||||
tonemap.process(srcFloat, dstFloat);
|
||||
|
||||
Mat dst = new Mat();
|
||||
dstFloat.convertTo(dst, CvType.CV_8UC3, 255.0);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对输入图像做 CLAHE (在 LAB 空间)
|
||||
*/
|
||||
public static Mat applyCLAHEGray(Mat src,
|
||||
double clipLimit,
|
||||
Size tileGridSize) {
|
||||
|
||||
CLAHE clahe = Imgproc.createCLAHE();
|
||||
clahe.setClipLimit(clipLimit);
|
||||
clahe.setTilesGridSize(tileGridSize);
|
||||
|
||||
Mat dst = new Mat();
|
||||
clahe.apply(src, dst);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
public static Mat enhanceSarAuto(Mat src) {
|
||||
|
||||
Mat gray = new Mat();
|
||||
|
||||
// =========================
|
||||
// 转单通道
|
||||
// =========================
|
||||
if (src.channels() == 1) {
|
||||
gray = src;
|
||||
}
|
||||
else if (src.channels() == 3) {
|
||||
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
|
||||
}
|
||||
else if (src.channels() == 4) {
|
||||
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGRA2GRAY);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported channel number");
|
||||
}
|
||||
|
||||
// =========================
|
||||
// 调用增强
|
||||
// =========================
|
||||
return enhanceSarImage(gray);
|
||||
}
|
||||
|
||||
public static Mat enhanceSarImage(Mat src) {
|
||||
|
||||
if (src.channels() != 1) {
|
||||
log.info("====channels: " + src.channels() + "======");
|
||||
throw new IllegalArgumentException("Only single channel supported");
|
||||
}
|
||||
|
||||
Mat floatMat = new Mat();
|
||||
|
||||
// 1️⃣ 转 float
|
||||
if (src.type() == CvType.CV_16U)
|
||||
src.convertTo(floatMat, CvType.CV_32F, 1.0 / 65535.0);
|
||||
else if (src.type() == CvType.CV_8U)
|
||||
src.convertTo(floatMat, CvType.CV_32F, 1.0 / 255.0);
|
||||
else
|
||||
throw new IllegalArgumentException("Unsupported type");
|
||||
|
||||
// 2️⃣ log
|
||||
Core.add(floatMat, Scalar.all(1e-6), floatMat);
|
||||
Core.log(floatMat, floatMat);
|
||||
|
||||
// 3️⃣ 百分位裁剪(稳定)
|
||||
Mat flat = floatMat.reshape(1,1);
|
||||
Mat sorted = flat.clone();
|
||||
Core.sort(sorted, sorted, Core.SORT_ASCENDING);
|
||||
|
||||
int total = sorted.cols();
|
||||
double low = sorted.get(0, (int)(total * 0.01))[0];
|
||||
double high = sorted.get(0, (int)(total * 0.995))[0];
|
||||
|
||||
Core.min(floatMat, new Scalar(high), floatMat);
|
||||
Core.max(floatMat, new Scalar(low), floatMat);
|
||||
|
||||
Core.subtract(floatMat, new Scalar(low), floatMat);
|
||||
Core.divide(floatMat, new Scalar(high - low), floatMat);
|
||||
|
||||
// 4️⃣ 转 8bit
|
||||
Mat img8 = new Mat();
|
||||
floatMat.convertTo(img8, CvType.CV_8U, 255.0);
|
||||
|
||||
// 5️⃣ 去 speckle
|
||||
Imgproc.medianBlur(img8, img8, 3);
|
||||
|
||||
// 6️⃣ 动态 tile 计算(关键)
|
||||
int targetTileSize = 512; // 每块目标大小
|
||||
int tilesX = Math.max(1, src.cols() / targetTileSize);
|
||||
int tilesY = Math.max(1, src.rows() / targetTileSize);
|
||||
|
||||
CLAHE clahe = Imgproc.createCLAHE();
|
||||
|
||||
// 自适应 clip
|
||||
MatOfDouble mean = new MatOfDouble();
|
||||
MatOfDouble std = new MatOfDouble();
|
||||
Core.meanStdDev(img8, mean, std);
|
||||
|
||||
double s = std.get(0,0)[0];
|
||||
double clip;
|
||||
|
||||
if (s < 20)
|
||||
clip = 3.0;
|
||||
else if (s < 40)
|
||||
clip = 2.5;
|
||||
else
|
||||
clip = 2.0;
|
||||
|
||||
clahe.setClipLimit(clip);
|
||||
clahe.setTilesGridSize(new Size(tilesX, tilesY));
|
||||
|
||||
Mat claheOut = new Mat();
|
||||
clahe.apply(img8, claheOut);
|
||||
|
||||
// 7️⃣ 轻锐化(减弱避免放大缝)
|
||||
Mat blur = new Mat();
|
||||
Imgproc.GaussianBlur(claheOut, blur, new Size(0,0), 1.0);
|
||||
|
||||
Mat sharpened = new Mat();
|
||||
Core.addWeighted(claheOut, 1.1, blur, -0.1, 0, sharpened);
|
||||
|
||||
return sharpened;
|
||||
}
|
||||
|
||||
public static Mat enhanceSarIndustrialNoClahe(Mat src) {
|
||||
|
||||
if (src.channels() != 1)
|
||||
throw new IllegalArgumentException("Only single channel supported");
|
||||
|
||||
Mat floatMat = new Mat();
|
||||
|
||||
// 1️⃣ 转 float
|
||||
if (src.type() == CvType.CV_16U)
|
||||
src.convertTo(floatMat, CvType.CV_32F, 1.0 / 65535.0);
|
||||
else if (src.type() == CvType.CV_8U)
|
||||
src.convertTo(floatMat, CvType.CV_32F, 1.0 / 255.0);
|
||||
else
|
||||
throw new IllegalArgumentException("Unsupported type");
|
||||
|
||||
// 2️⃣ log 压缩
|
||||
Core.add(floatMat, Scalar.all(1e-6), floatMat);
|
||||
Core.log(floatMat, floatMat);
|
||||
|
||||
// 3️⃣ 百分位裁剪
|
||||
Mat flat = floatMat.reshape(1,1);
|
||||
Mat sorted = flat.clone();
|
||||
Core.sort(sorted, sorted, Core.SORT_ASCENDING);
|
||||
|
||||
int total = sorted.cols();
|
||||
double low = sorted.get(0, (int)(total * 0.01))[0];
|
||||
double high = sorted.get(0, (int)(total * 0.995))[0];
|
||||
|
||||
Core.min(floatMat, new Scalar(high), floatMat);
|
||||
Core.max(floatMat, new Scalar(low), floatMat);
|
||||
|
||||
Core.subtract(floatMat, new Scalar(low), floatMat);
|
||||
Core.divide(floatMat, new Scalar(high - low), floatMat);
|
||||
|
||||
// ==============================
|
||||
// 4️⃣ 局部自适应对比增强(替代 CLAHE)
|
||||
// ==============================
|
||||
|
||||
Mat localMean = new Mat();
|
||||
double sigma = src.cols() / 30.0;
|
||||
Imgproc.GaussianBlur(floatMat, localMean, new Size(0,0), sigma);
|
||||
|
||||
Mat localContrast = new Mat();
|
||||
Core.divide(floatMat, localMean, localContrast);
|
||||
|
||||
// 压缩避免过增强
|
||||
Core.min(localContrast, new Scalar(3.0), localContrast);
|
||||
Core.max(localContrast, new Scalar(0.3), localContrast);
|
||||
|
||||
// 再归一
|
||||
Core.normalize(localContrast, localContrast, 0, 1, Core.NORM_MINMAX);
|
||||
|
||||
// ==============================
|
||||
// 5️⃣ 软 gamma 提升
|
||||
// ==============================
|
||||
|
||||
Core.pow(localContrast, 0.8, localContrast);
|
||||
|
||||
// ==============================
|
||||
// 6️⃣ 转 8bit
|
||||
// ==============================
|
||||
|
||||
Mat img8 = new Mat();
|
||||
localContrast.convertTo(img8, CvType.CV_8U, 255.0);
|
||||
|
||||
// ==============================
|
||||
// 7️⃣ 轻 speckle 抑制
|
||||
// ==============================
|
||||
|
||||
Imgproc.medianBlur(img8, img8, 3);
|
||||
|
||||
// ==============================
|
||||
// 8️⃣ 轻锐化
|
||||
// ==============================
|
||||
|
||||
Mat blur = new Mat();
|
||||
Imgproc.GaussianBlur(img8, blur, new Size(0,0), 1.0);
|
||||
|
||||
Mat sharpened = new Mat();
|
||||
Core.addWeighted(img8, 1.15, blur, -0.15, 0, sharpened);
|
||||
|
||||
return sharpened;
|
||||
}
|
||||
|
||||
static public double computePercentile(Mat mat, double percentile) {
|
||||
|
||||
if (mat.empty())
|
||||
return 0;
|
||||
|
||||
Mat flat = mat.reshape(1, 1); // 拉平成1行
|
||||
Mat sorted = new Mat();
|
||||
|
||||
Core.sort(flat, sorted, Core.SORT_ASCENDING);
|
||||
|
||||
int total = sorted.cols();
|
||||
int index = (int)(percentile * total);
|
||||
|
||||
index = Math.min(Math.max(index, 0), total - 1);
|
||||
|
||||
double[] value = sorted.get(0, index);
|
||||
|
||||
return value != null ? value[0] : 0;
|
||||
}
|
||||
}
|
||||
@ -61,7 +61,7 @@ mybatis-plus:
|
||||
table-underline: true
|
||||
configuration:
|
||||
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
|
||||
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
call-setters-on-nulls: true
|
||||
# Jasypt 配置
|
||||
jasypt:
|
||||
@ -76,7 +76,9 @@ skyeye:
|
||||
sar:
|
||||
image:
|
||||
type: 1
|
||||
max: 409600000
|
||||
default-level: 15
|
||||
max-level: 18
|
||||
count: 5
|
||||
udp:
|
||||
status:
|
||||
answer-timeout: 2
|
||||
|
||||
@ -98,13 +98,15 @@ CREATE TABLE `jm_image` (
|
||||
`right1_lat` decimal(11,8) DEFAULT NULL COMMENT '右上纬度',
|
||||
`right2_lon` decimal(11,8) DEFAULT NULL COMMENT '右下经度',
|
||||
`right2_lat` decimal(11,8) DEFAULT NULL COMMENT '右下纬度',
|
||||
`frame_no` int DEFAULT NULL COMMENT '图像帧序号',
|
||||
`max` decimal(20,6) DEFAULT NULL COMMENT '归一化前最大值',
|
||||
`image_time` datetime DEFAULT NULL COMMENT '图像生成时间',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '文件上传时间',
|
||||
`tail_level` int DEFAULT NULL COMMENT '层级',
|
||||
`rotate_angle` decimal(11,8) DEFAULT NULL COMMENT '旋转角度',
|
||||
`image_no` int DEFAULT '1' COMMENT '图像编号',
|
||||
`start_frame_no` int DEFAULT NULL,
|
||||
`end_frame_no` int DEFAULT NULL,
|
||||
`frame_from` int DEFAULT NULL COMMENT '起始帧',
|
||||
`frame_to` int DEFAULT NULL COMMENT '结束帧',
|
||||
`brightness` int DEFAULT NULL COMMENT '亮度',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10542 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='sar图像';
|
||||
|
||||
|
||||
@ -3,33 +3,41 @@
|
||||
|
||||
<mapper namespace="com.zhangy.skyeye.jm.mapper.JmImageMapper">
|
||||
<sql id="selectSql">
|
||||
select
|
||||
m.id,
|
||||
m.name,
|
||||
m.job_id, j.name as job_name,
|
||||
m.uav_id,
|
||||
m.payload_id,
|
||||
m.airline_id,
|
||||
m.file_id,
|
||||
m.image_no,
|
||||
m.max,
|
||||
m.left1_lon,
|
||||
m.left1_lat,
|
||||
m.left2_lon,
|
||||
m.left2_lat,
|
||||
m.right1_lon,
|
||||
m.right1_lat,
|
||||
m.right2_lon,
|
||||
m.right2_lat,
|
||||
m.image_time,
|
||||
m.create_time,
|
||||
f.name as file_name,
|
||||
f.type as file_type,
|
||||
f.path as file_path,
|
||||
f.relative_path as relative_path
|
||||
select m.id,
|
||||
m.name,
|
||||
m.image_no,
|
||||
m.frame_from,
|
||||
m.frame_to,
|
||||
m.job_id,
|
||||
j.name as job_name,
|
||||
m.uav_id,
|
||||
m.payload_id,
|
||||
m.airline_id,
|
||||
m.file_id,
|
||||
m.max,
|
||||
m.left1_lon,
|
||||
m.left1_lat,
|
||||
m.left2_lon,
|
||||
m.left2_lat,
|
||||
m.right1_lon,
|
||||
m.right1_lat,
|
||||
m.right2_lon,
|
||||
m.right2_lat,
|
||||
m.image_time,
|
||||
m.create_time,
|
||||
m.tail_level,
|
||||
m.rotate_angle,
|
||||
m.brightness,
|
||||
f.name as file_name,
|
||||
f.type as file_type,
|
||||
f.path as file_path,
|
||||
f.relative_path as relative_path,
|
||||
j.id as job_conf_id,
|
||||
j.name as job_name
|
||||
from jm_image m
|
||||
left join sys_file f on f.id = m.file_id
|
||||
left join jm_job j on j.id = m.job_id
|
||||
left join sys_file f on f.id = m.file_id
|
||||
left join jm_job_exec je on je.id = m.job_id
|
||||
left join jm_job j on j.id = je.conf_id
|
||||
</sql>
|
||||
|
||||
<select id="selectPage" resultType="com.zhangy.skyeye.jm.entity.JmImage">
|
||||
@ -47,11 +55,13 @@
|
||||
<if test="name != null and name != ''">
|
||||
and f.name like concat('%', #{name}, '%')
|
||||
</if>
|
||||
order by j.create_time desc, m.create_time
|
||||
</select>
|
||||
|
||||
<select id="selectByJob" resultType="com.zhangy.skyeye.jm.entity.JmImage">
|
||||
<include refid="selectSql"/>
|
||||
where m.job_id in
|
||||
where
|
||||
m.job_id in
|
||||
<foreach item="item" collection="array" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
@ -64,7 +74,7 @@
|
||||
<include refid="selectSql"/>
|
||||
where m.job_id = #{jobId}
|
||||
<if test="array != null and array.length > 0">
|
||||
and m.uav_id in
|
||||
and m.uav_id in
|
||||
<foreach item="item" collection="array" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
@ -86,6 +96,19 @@
|
||||
order by m.image_no
|
||||
</select>
|
||||
|
||||
<select id="selectLastByAirline" resultType="com.zhangy.skyeye.jm.entity.JmImage">
|
||||
<include refid="selectSql"/>
|
||||
where m.airline_id in
|
||||
<foreach item="item" collection="array" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
<if test="type != null">
|
||||
and f.type = #{type}
|
||||
</if>
|
||||
order by m.image_no desc
|
||||
limit 0,1
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultType="com.zhangy.skyeye.jm.entity.JmImage">
|
||||
<include refid="selectSql"/>
|
||||
where m.id in
|
||||
@ -98,7 +121,7 @@
|
||||
<include refid="selectSql"/>
|
||||
where f.type = #{type}
|
||||
<if test="array != null and array.length > 0">
|
||||
and f.name in
|
||||
and f.name in
|
||||
<foreach item="item" collection="array" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
@ -110,28 +133,33 @@
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
|
||||
insert into jm_image(
|
||||
id,
|
||||
name,
|
||||
job_id,
|
||||
uav_id,
|
||||
payload_id,
|
||||
airline_id,
|
||||
file_id,
|
||||
image_no,
|
||||
max,
|
||||
left1_lon,
|
||||
left1_lat,
|
||||
left2_lon,
|
||||
left2_lat,
|
||||
right1_lon,
|
||||
right1_lat,
|
||||
right2_lon,
|
||||
right2_lat,
|
||||
image_time,
|
||||
create_time
|
||||
id,
|
||||
name,
|
||||
job_id,
|
||||
uav_id,
|
||||
payload_id,
|
||||
airline_id,
|
||||
file_id,
|
||||
rotate_angle,
|
||||
max,
|
||||
left1_lon,
|
||||
left1_lat,
|
||||
left2_lon,
|
||||
left2_lat,
|
||||
right1_lon,
|
||||
right1_lat,
|
||||
right2_lon,
|
||||
right2_lat,
|
||||
tail_level,
|
||||
image_time,
|
||||
create_time,
|
||||
image_no,
|
||||
frame_from,
|
||||
frame_to,
|
||||
brightness
|
||||
) values
|
||||
<foreach item="item" index="index" collection="array" separator=",">
|
||||
(
|
||||
<foreach item="item" index="index" collection="array" separator=",">
|
||||
(
|
||||
#{item.id},
|
||||
#{item.name},
|
||||
#{item.jobId},
|
||||
@ -139,7 +167,7 @@
|
||||
#{item.payloadId},
|
||||
#{item.airlineId},
|
||||
#{item.fileId},
|
||||
#{item.imageNo},
|
||||
#{item.rotateAngle},
|
||||
#{item.max},
|
||||
#{item.left1Lon},
|
||||
#{item.left1Lat},
|
||||
@ -149,9 +177,14 @@
|
||||
#{item.right1Lat},
|
||||
#{item.right2Lon},
|
||||
#{item.right2Lat},
|
||||
#{item.tailLevel},
|
||||
#{item.imageTime},
|
||||
#{item.createTime}
|
||||
)
|
||||
#{item.createTime},
|
||||
#{item.imageNo},
|
||||
#{item.frameFrom},
|
||||
#{item.frameTo},
|
||||
#{item.brightness}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
@ -165,6 +198,7 @@
|
||||
<if test="airlineId != null">airline_id = #{airlineId},</if>
|
||||
<if test="fileId != null">file_id = #{fileId},</if>
|
||||
<if test="max != null">max = #{max},</if>
|
||||
<if test="rotateAngle != null">rotate_angle = #{rotateAngle},</if>
|
||||
<if test="left1Lon != null">left1_lon = #{left1Lon},</if>
|
||||
<if test="left1Lat != null">left1_lat = #{left1Lat},</if>
|
||||
<if test="left2Lon != null">left2_lon = #{left2Lon},</if>
|
||||
@ -175,6 +209,11 @@
|
||||
<if test="right2Lat != null">right2_lat = #{right2Lat},</if>
|
||||
<if test="imageTime != null">image_time = #{imageTime},</if>
|
||||
<if test="createTime != null">create_time = #{createTime},</if>
|
||||
<if test="tailLevel != null">tail_level = #{tailLevel},</if>
|
||||
<if test="imageNo != null">image_no = #{imageNo},</if>
|
||||
<if test="frameFrom != null">frame_from = #{frameFrom},</if>
|
||||
<if test="frameTo != null">frame_to = #{frameTo},</if>
|
||||
<if test="brightness != null">brightness = #{brightness},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
@ -189,6 +228,7 @@
|
||||
airline_id = #{airlineId},
|
||||
file_id = #{fileId},
|
||||
max = #{max},
|
||||
rotate_angle = #{rotateAngle},
|
||||
left1_lon = #{left1Lon},
|
||||
left1_lat = #{left1Lat},
|
||||
left2_lon = #{left2Lon},
|
||||
@ -199,10 +239,12 @@
|
||||
right2_lat = #{right2Lat},
|
||||
image_time = #{imageTime},
|
||||
create_time = #{createTime},
|
||||
tail_level = #{tailLevel},
|
||||
brightness = #{brightness},
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
|
||||
<delete id="delete">
|
||||
delete from jm_image where id in
|
||||
<foreach item="item" collection="array" open="(" separator="," close=")">
|
||||
@ -222,7 +264,7 @@
|
||||
from jm_image img
|
||||
where img.job_id = #{jobId}
|
||||
<if test="array != null and array.length > 0">
|
||||
and img.uav_id in
|
||||
and img.uav_id in
|
||||
<foreach item="item" collection="array" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
window.config = {
|
||||
env: 'offline', //online
|
||||
//api: 'http://127.0.0.1:9116/', // 外网服务器,
|
||||
api: 'http://182.92.203.107:9116',
|
||||
socket: 'http://182.92.203.107:9116', //外网服务器,
|
||||
imagePath: 'http://182.92.203.107:8080/files',
|
||||
api: 'http://127.0.0.1:9116',
|
||||
socket: 'http://127.0.0.1:9116', //外网服务器,
|
||||
imagePath: 'http://127.0.0.1:8080/files',
|
||||
arithmeticPath: 'http://127.0.0.1:18090/ktkx/UavPlanning/SAR',
|
||||
tokenKey: 'accessToken',
|
||||
refreshTokenKey: 'refreshToken',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user