diff --git a/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java b/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java index 7776ebb..86f14f5 100644 --- a/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java +++ b/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java @@ -154,52 +154,68 @@ public class IndustrialCameraController { } + @ApiOperation("直接上传") + @PostMapping("sendWithRetry") + public Result sendWithRetry(@RequestBody IndustrialCameraVO industrialCameraVo) { + kuKouService.sendWithRetry(industrialCameraVo, true,0); + return Result.success("yes"); + } @ApiOperation("站点盘点") @PostMapping("siteInventory") - public Result siteInventory(@RequestBody IndustrialCameraVO industrialCameraVo){ + public Result siteInventory(@RequestBody IndustrialCameraVO industrialCameraVo) { serialPortExample.openLight(1); //2d图片保存 - Result> listResult = pic(); + Result> listResult = pic(); boolean re = false; //sick识别 - String code = SickSocket.readOCR(configProperties.getCameraConfig().getSickIp(), configProperties.getCameraConfig().getSickPort()); + + Integer count = 0; + String code = ""; String pa = ""; - //2d识别 - for(String path:listResult.getData()){ - re = FeatureMatchingExample.matchTemplate( - InventoryService.readImagesInFolder(configProperties.getSavePath().getMediaPath() + "template/" + industrialCameraVo.getTypeMacth()), - configProperties.getSavePath().getMediaPath() +path); - if (re){ - pa = path; - break; + try { + Thread.sleep(400); + + code = SickSocket.readOCR(configProperties.getCameraConfig().getSickIp(), configProperties.getCameraConfig().getSickPort()); + + //2d识别 + for (String path : listResult.getData()) { + re = FeatureMatchingExample.matchTemplate( + InventoryService.readImagesInFolder(configProperties.getSavePath().getMediaPath() + "template/" + industrialCameraVo.getTypeMacth()), + configProperties.getSavePath().getMediaPath() + path); + if (re) { + pa = path; + break; + } } - } - if(pa.equals("")){ - pa = listResult.getData().get(0); - } - //3d pcd保存 - LocalDate currentDate = LocalDate.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + if (pa.equals("")) { + pa = listResult.getData().get(0); + } + //3d pcd保存 + LocalDate currentDate = LocalDate.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - String pathPcd = configProperties.getSavePath().getMediaPath() - +"camera3D/" - +currentDate.format(formatter)+"/" +UUID.randomUUID()+"-"; - int count = inventoryService.match3D(industrialCameraVo.getTypeMacth(),pathPcd+"-"+configProperties.getCameraConfig().getCamera3D()+".pcd",""); - //3d识别 + String pathPcd = configProperties.getSavePath().getMediaPath() + + "camera3D/" + + currentDate.format(formatter) + "/" + UUID.randomUUID() + "-"; + count = inventoryService.match3D(industrialCameraVo.getTypeMacth(), pathPcd + "-" + configProperties.getCameraConfig().getCamera3D() + ".pcd", ""); + + } catch (Exception e) { + log.error("识别异常", e); + }//3d识别 //保存信息 KuKou kuKou = new KuKou(); kuKou.setCreateTime(LocalDateTime.now()); - - kuKou.setCategoryName(industrialCameraVo.getTypeMacth()); - if (re && count==industrialCameraVo.getCount() && code.endsWith(industrialCameraVo.getCode())) { - kuKou.setFlag(1); - }else kuKou.setFlag(0); kuKou.setCount(count); kuKou.setCode(code); kuKou.setWmsCount(industrialCameraVo.getCount()); kuKou.setWmsCode(industrialCameraVo.getCode()); - kuKou.setPath(configProperties.getSavePath().getNetPicPath()+pa+".jpg"); + kuKou.setPath(configProperties.getSavePath().getNetPicPath() + pa + ".jpg"); + kuKou.setCategoryName(industrialCameraVo.getTypeMacth()); + if (re && count == industrialCameraVo.getCount() && code.endsWith(industrialCameraVo.getCode())) { + kuKou.setFlag(1); + } else kuKou.setFlag(0); + kuKouService.save(kuKou); // serialPortExample.openLight(0); return new Result<>(kuKou); diff --git a/web/src/main/java/com/zhehekeji/web/service/KuKouService.java b/web/src/main/java/com/zhehekeji/web/service/KuKouService.java index b25fb51..dee0cff 100644 --- a/web/src/main/java/com/zhehekeji/web/service/KuKouService.java +++ b/web/src/main/java/com/zhehekeji/web/service/KuKouService.java @@ -58,7 +58,7 @@ public class KuKouService extends ServiceImpl implements IS CommandQueue.addCommand(scTransmission); } - private void sendWithRetry(IndustrialCameraVO scTransmission, Boolean flag, int retryCount) { + public void sendWithRetry(IndustrialCameraVO scTransmission, Boolean flag, int retryCount) { try { HttpHeaders headers = new HttpHeaders(); headers.set("Content-Type", "application/json"); diff --git a/web/src/main/java/com/zhehekeji/web/service/RFID/CMD.java b/web/src/main/java/com/zhehekeji/web/service/RFID/CMD.java index b766f44..e4a24ad 100644 --- a/web/src/main/java/com/zhehekeji/web/service/RFID/CMD.java +++ b/web/src/main/java/com/zhehekeji/web/service/RFID/CMD.java @@ -5,6 +5,8 @@ package com.zhehekeji.web.service.RFID; * */ public class CMD { + /**UHF mark to indicate the head of data package */ + public final static byte HEAD = (byte) 0xA0; public final static byte RESET = 0x70; public final static byte SET_UART_BAUDRATE = 0x71; public final static byte GET_FIRMWARE_VERSION = 0x72; diff --git a/web/src/main/java/com/zhehekeji/web/service/RFID/RFIDSocket.java b/web/src/main/java/com/zhehekeji/web/service/RFID/RFIDSocket.java index 8e33031..bb28e3f 100644 --- a/web/src/main/java/com/zhehekeji/web/service/RFID/RFIDSocket.java +++ b/web/src/main/java/com/zhehekeji/web/service/RFID/RFIDSocket.java @@ -111,6 +111,60 @@ public class RFIDSocket { thread.start(); } + + public void readData6C(){ + running = true; + buffer = new LinkedList(); + + Thread thread = new Thread(new Runnable() { + + @SneakyThrows + @Override + public void run() { + while (true){ + if(!running){ + System.out.println("stop"); + break; + } + int i = 0; + try { + //读不到会阻塞 返回-1表示连接已断开 + i = is.read(); + } catch (IOException e) { + log.error("disconnect"); + } + if(i == -1){ + + break; + } + int count = 0; + try { + count = is.available(); + if(count > 0){ + //把之前read的字节嫁过来 + byte[]readBytes = new byte[count+1]; + readBytes[0] = (byte)i; + is.read(readBytes,1,count); + for(byte b:readBytes){ + buffer.offer(b); + } + } + } catch (IOException e) { + log.error(""); + } + while (buffer.size()> 0){ + String code = readTag(); + if(code != null){ + tags.add(code); + } + } + } + + } + }); + thread.start(); + } + private String readTag(){ String tag = null; Byte b = buffer.poll(); @@ -227,8 +281,10 @@ public class RFIDSocket { } + + public static void main(String[] args) throws IOException, InterruptedException { - RFIDSocket rfid = new RFIDSocket("172.16.0.220",4001); + RFIDSocket rfid = new RFIDSocket("127.0.0.1",5002); rfid.startCheck(); rfid.readData(); Thread.sleep(30000); diff --git a/web/src/main/java/com/zhehekeji/web/service/RFID/RFIDSocketNew.java b/web/src/main/java/com/zhehekeji/web/service/RFID/RFIDSocketNew.java new file mode 100644 index 0000000..bcb64e8 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/RFID/RFIDSocketNew.java @@ -0,0 +1,434 @@ +package com.zhehekeji.web.service.RFID; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.*; + +/** + * sick扫码枪 + */ +@Slf4j +public class RFIDSocketNew { + + public byte cmd; + private Socket socket; + + private OutputStream os; + + private InputStream is; + + private Queue buffer; + + private boolean running; + + private int step; + + private int length; + + private int index; + + /** + * 读到的所有code标签 + */ + private Set tags = new HashSet<>(); + + private List byteList = new ArrayList<>(30); + + public Set getTags() { + return tags; + } + + public RFIDSocketNew(String ip, int port, String cmd) { + if (cmd.equals("6C")) { + this.cmd = (byte) CMD.INVENTORY; + } else { + this.cmd = (byte) CMD.REAL_TIME_INVENTORY; + } + socket = new Socket(); + os = null; + is = null; + try { + socket.connect(new InetSocketAddress(ip, port), 3000); + os = socket.getOutputStream(); + is = socket.getInputStream(); + } catch (IOException e) { + log.error("RFIDSocket time out,ip:{},info:{}", ip, e); + + close(); + + + } + } + + public void startCheck() { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + int i = 0; + while (true) { + if (!running) { + break; + } + byte[] antChanges = new byte[6]; + antChanges[0] = (byte) 0xa0; + antChanges[1] = (byte) 0x04; + antChanges[2] = (byte) 0x01; + antChanges[3] = (byte) 0x74; + if (i % 2 == 0) { + //切换天线 使用天线0 + antChanges[4] = (byte) 0x00; + antChanges[5] = (byte) 0xe7; + } else { + //切换天线 使用天线1 + antChanges[4] = (byte) 0x01; + antChanges[5] = (byte) 0xe6; + } + i++; + try { + os.write(antChanges); + Thread.sleep(10); + } catch (InterruptedException | IOException e) { + log.error("send cmd error:{}", e); + } + byte[] bytes = new byte[5]; + bytes[0] = (byte) 0xa0; + bytes[1] = (byte) 0x03; + bytes[2] = (byte) 0x01; + bytes[3] = cmd; + bytes[4] = (byte) 0xac; + try { + os.write(bytes); + Thread.sleep(70); + } catch (InterruptedException | IOException e) { + log.warn("send rfid cmd error:{}", e); + } + } + } + }); + thread.start(); + + } + + public void readData() { + running = true; + buffer = new LinkedList(); + + Thread thread = new Thread(new Runnable() { + + @SneakyThrows + @Override + public void run() { + while (true) { + if (!running) { + System.out.println("stop"); + break; + } + int count = 0; + byte[] header = new byte[1]; + byte[] lenBuf = new byte[1]; + // 1. 读取头部(0xA0) + int headerRead = is.read(header); + if (headerRead == -1) { + System.out.println("连接已关闭或没有更多数据"); + break; + } + + if (header[0] != (byte) 0xA0) { + System.out.println("无效数据包头: 0x" + String.format("%02X", header[0])); + continue; // 跳过非有效数据包 + } + + // 2. 读取长度字段 + int lenRead = is.read(lenBuf); + if (lenRead == -1) { + System.out.println("连接中断,无法读取长度字段"); + break; + } + + int dataLength = lenBuf[0] & 0xFF; // 将byte转为无符号int + + // 3. 读取data部分 + int totalRead = 0; + // 3. 构造完整数据包 buffer:head(1) + len(1) + data(dataLength) + byte[] fullPacket = new byte[1 + 1 + dataLength]; + fullPacket[0] = (byte) 0xA0; + fullPacket[1] = lenBuf[0]; + + // 4. 读取剩余部分(data) + while (totalRead < dataLength) { + int bytesRead = is.read(fullPacket, 2 + totalRead, dataLength - totalRead); + if (bytesRead == -1) { + System.out.println("连接中断,数据未完全读取"); + break; + } + totalRead += bytesRead; + } + + if (totalRead != dataLength) { + System.out.println("数据包不完整,跳过"); + continue; + } + + // 4. 成功读取一个完整数据包,可以进行处理 + tags.add(parse(fullPacket)); + + + + } + } + }); + thread.start(); + } + + + private void clear() { + byteList.clear(); + step = 0; + length = 0; + index = 0; + } + + public void close() { + running = false; + try { + is.close(); + socket.close(); + os.close(); + } catch (IOException e) { + e.printStackTrace(); + log.error("warn rfid close:{}", e); + } + + } + +// +// public static void main(String[] args) throws IOException, InterruptedException { +// RFIDSocketNew rfid = new RFIDSocketNew("127.0.0.1",5002); +// rfid.startCheck(); +// rfid.readData(); +// Thread.sleep(30000); +// rfid.close(); +// System.out.println(rfid.getTags()); +//// 03 01 B0 AC +// +// byte[]bytes = new byte[5]; +// bytes[0] = (byte)0xa0; +// bytes[1] = (byte)0x03; +// bytes[2] = (byte)0x01; +// bytes[3] = (byte)0xb0; +// bytes[4] = (byte) calculateChecksum(bytes); +//// int b = ; +// System.out.println((byte)0xac); +// } + + /** + * 计算与C语言中一致的校验和:补码求和方式 + * + * @param buffer 输入字节数组(不包含 Check 字段) + * @return 校验和(0x00 ~ 0xFF 的无符号值,以 int 形式返回) + */ + public static byte[] calculateChecksum(byte[] buffer) { + // 1. 创建子数组,排除最后一个字节 + byte[] data = new byte[buffer.length - 1]; + System.arraycopy(buffer, 0, data, 0, data.length); + + // 2. 计算校验和 + int sum = 0; + for (byte b : data) { + sum += b & 0xFF; // 转为无符号 int + } + sum &= 0xFF; // 取低8位 + int checksum = (~sum + 1) & 0xFF; // 补码 + 屏蔽高位 + + // 3. 写入校验和到 buffer 最后一个位置 + buffer[buffer.length - 1] = (byte) checksum; + + return buffer; + } + + public static boolean calculateCheck(byte[] buffer) { + int sum = 0; + for (int i = 0; i < buffer.length - 1; i++) { + sum += buffer[ i] & 0xFF; // 将 byte 转换为无符号 int(0~255) + } + sum &= 0xFF; // 取低8位 + int checksum = (~sum + 1) & 0xFF; // 补码 + 屏蔽高位 + return sum == buffer[buffer.length - 1]; + } + + /** + * 解析RFID返回的数据包 + * + * @param packet byte数组,包含完整数据包 + */ + public static String parse6C(byte[] packet) { + if (packet == null || packet.length < 8) { + System.out.println("数据包为空或长度不足"); + return ""; + } + + // Step 1: 检查 Head 是否为 0xA0 + if (packet[0] != (byte) 0xA0) { + System.out.println("无效的数据包头: " + java.lang.String.format("%02X", packet[0])); + return ""; + } + + // Step 2: 获取 Len 字段 + int len = packet[1] & 0xFF; // 不包含 Len 自身 + if (packet.length != 2 + len) { // Head(1) + Len(1) + 数据(len) + Check(1) + System.out.println("数据包长度不匹配"); + return ""; + } + + // Step 3: 验证校验和 + if (!calculateCheck(packet)) { + System.out.println("校验失败,数据可能损坏"); + return ""; + } + + // Step 4: 解析各个字段 + int offset = 2; // Head + Len = 偏移2 + + byte address = packet[offset++]; + byte cmd = packet[offset++]; + byte freqAnt = packet[offset++]; + + // 提取 FreqAnt 的高6位(频点) 和 低2位(天线号) + int freqPoint = (freqAnt & 0xFC) >>> 2; // 高6位 + int antennaNo = (freqAnt & 0x03); // 低2位 + + // Step 5: PC字段(2字节) + byte[] pcBytes = new byte[2]; + System.arraycopy(packet, offset, pcBytes, 0, 2); + offset += 2; + + // Step 6: RSSI(1字节) + byte rssiByte = packet[offset + 1]; + int rssi = rssiByte & 0xFF; // 转无符号int + + // Step 7: EPC(剩余字段) + int epcLength = len - 5; // Len - (Address + Cmd + FreqAnt + PC + RSSI) + byte[] epcBytes = new byte[epcLength]; + System.arraycopy(packet, offset, epcBytes, 0, epcLength); + offset += epcLength; + + // Step 8: 输出解析结果 + System.out.println("Head: " + java.lang.String.format("%02X", packet[0] & 0xFF)); + System.out.println("Len: " + len); + System.out.println("Address: " + java.lang.String.format("%02X", address & 0xFF)); + System.out.println("Cmd: " + java.lang.String.format("%02X", cmd & 0xFF)); + System.out.println("FreqAnt: " + java.lang.String.format("%02X", freqAnt & 0xFF)); + System.out.println(" 频点: " + freqPoint); + System.out.println(" 天线号: " + antennaNo); + System.out.println("PC: " + bytesToHex(pcBytes)); + System.out.println("EPC: " + bytesToHex(epcBytes)); + System.out.println("RSSI: " + rssi + " dBm"); + System.out.println("Check: " + java.lang.String.format("%02X", packet[packet.length - 1] & 0xFF)); + return bytesToHex(epcBytes); + } + + /** + * 校验和验证方法:对除Check外的所有字节求和后补码 + */ + + + /** + * 将 byte[] 转换为 Hex 字符串 + */ + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(java.lang.String.format("%02X ", b & 0xFF)); + } + return sb.toString().trim(); + } + + String parse(byte[] byteArray) { + if (byteArray == null ) { + return ""; + } + + if (cmd == CMD.REAL_TIME_INVENTORY) { + return parse6C(byteArray); + } else { + return parse6B(byteArray); + } + } + + /** + * 解析RFID返回的数据包 + * + * @param packet byte数组,包含完整数据包 + */ + public static String parse6B(byte[] packet) { + if (packet == null || packet.length < 8) { // 最小长度:Head(1)+Len(1)+Address(1)+Cmd(1)+AntID(1)+UID(8)+Check(1)=12 + System.out.println("数据包为空或长度不足"); + return ""; + } + + // Step 1: 检查 Head 是否为 0xA0 + if (packet[0] != (byte) 0xA0) { + System.out.println("无效的数据包头: " + java.lang.String.format("%02X", packet[0])); + return ""; + } + + // Step 2: 获取 Len 字段 + int len = packet[1] & 0xFF; // 不包含 Len 自身 + if (packet.length != 2 + len) { // Head(1) + Len(1) + 数据(len) + Check(1) + System.out.println("数据包长度不匹配"); + return ""; + } + + // Step 3: 验证校验和 +// if (!calculateCheck(packet)) { +// System.out.println("校验失败,数据可能损坏"); +// return ""; +// } + + // Step 4: 解析各个字段 + int offset = 2; // Head + Len = 偏移2 + + byte address = packet[offset++]; + byte cmd = packet[offset++]; + byte antId = packet[offset++]; + + // Step 5: UID(8字节) + byte[] uidBytes = new byte[8]; + System.arraycopy(packet, offset, uidBytes, 0, 8); + offset += 8; + + // Step 6: 输出解析结果 + System.out.println("Head: " + java.lang.String.format("%02X", packet[0] & 0xFF)); + System.out.println("Len: " + len); + System.out.println("Address: " + java.lang.String.format("%02X", address & 0xFF)); + System.out.println("Cmd: " + java.lang.String.format("%02X", cmd & 0xFF)); + System.out.println("AntID: " + java.lang.String.format("%02X", antId & 0xFF)); + System.out.println("UID: " + bytesToHex(uidBytes)); + System.out.println("Check: " + java.lang.String.format("%02X", packet[packet.length - 1] & 0xFF)); + return bytesToHex(uidBytes); + } + + public static void main(String[] args) { +// A0 0A FF 89 05 01 02 03 04 05 01 + + System.out.println(parse6B(calculateChecksum(new byte[]{(byte) 0xA0, 0x0C, (byte) 0xFF, (byte) 0xb0, 0x05, 0x01,0x01, 0x06, 0x01, 0x01, 0x01, 0x01, 0x01,0x00}))); + + RFIDSocketNew rfidSocket = new RFIDSocketNew("127.0.0.1", 6000,"6B"); + +// rfidSocket.startCheck(); + rfidSocket.readData(); + try { + Thread.sleep(30000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + rfidSocket.close(); + System.out.println(rfidSocket.getTags()); + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/algorithm/InventoryService.java b/web/src/main/java/com/zhehekeji/web/service/algorithm/InventoryService.java index b1fcef6..e70f1c5 100644 --- a/web/src/main/java/com/zhehekeji/web/service/algorithm/InventoryService.java +++ b/web/src/main/java/com/zhehekeji/web/service/algorithm/InventoryService.java @@ -23,6 +23,8 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service @Slf4j @@ -81,10 +83,42 @@ public class InventoryService { public int match3D(String category,String path,String configPath){ //拍照 - log.info("3D拍照pcd: "+path); - LxPointCloudSaveImage.saveImage(configProperties.getCameraConfig().getCamera3D(), path,1); + List results = new ArrayList<>(); + + for (int i = 0; i < 3; i++) { + String currentPath = path.replace(".pcd", "_" + i + ".pcd"); // 添加索引以区分不同次拍照的文件路径 + LxPointCloudSaveImage.saveImage(configProperties.getCameraConfig().getCamera3D(), currentPath, 1); + + Integer result = PointCloudProcessor.getBaijiuBox( + currentPath, + configProperties.getSavePath().getCroppingPath(), + configProperties.getSavePath().getWinePreparationPath() + category + ".json" + ); + + results.add(result); + + if (i >= 1 && results.get(i).equals(results.get(i - 1))) { + // 如果当前结果与上一次结果相同,则直接返回 + return results.get(i); + } + + if (i == 2) { + // 第三次拍照后检查是否有两个相同的结果 + + return results.get(2); + } + + + + try { + Thread.sleep(300*(i+1)); // 间隔300ms + } catch (InterruptedException e) { + log.error("线程休眠异常", e); + } + } + return results.get(results.size() - 1); - return PointCloudProcessor.getBaijiuBox(path,configProperties.getSavePath().getCroppingPath(),configProperties.getSavePath().getWinePreparationPath()+category+".json"); +// return PointCloudProcessor.getBaijiuBox(path,configProperties.getSavePath().getCroppingPath(),configProperties.getSavePath().getWinePreparationPath()+category+".json"); } diff --git a/web/src/main/java/com/zhehekeji/web/service/algorithm/PointCloudProcessor.java b/web/src/main/java/com/zhehekeji/web/service/algorithm/PointCloudProcessor.java index 083e2f8..389cc42 100644 --- a/web/src/main/java/com/zhehekeji/web/service/algorithm/PointCloudProcessor.java +++ b/web/src/main/java/com/zhehekeji/web/service/algorithm/PointCloudProcessor.java @@ -17,8 +17,6 @@ import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static com.zhehekeji.web.service.algorithm.PointToImageMapper.drawPointsAndCalculateArea; - @Slf4j public class PointCloudProcessor { @@ -449,7 +447,7 @@ public class PointCloudProcessor { } log.info("读取配置文件:pcdPojo:"+pcdPojo.toString()); - return getLongitudinalType(points, pojo, pcdPojo.getWidth(), pcdPojo.getLength(), pcdPojo.getHigh(),pcdPojo.getArrangeType()); + return PointToImageMapper.getLongitudinalType(points, pojo, pcdPojo.getWidth(), pcdPojo.getLength(), pcdPojo.getHigh(),pcdPojo.getArrangeType()); } @@ -628,6 +626,8 @@ public class PointCloudProcessor { private static int getLongitudinalType(List points, PcdPojo pojo, int l, int w, int height,String type){ //pojo里面的floorHeight为地板的值,以后所有的height都将根据地板值进行计算地板值减去当前点的z轴值,为高度,且当为height的倍数的时候,认为是有效的点,其中1倍的冗余在50mm,每高一层,冗余增加20mm // 计算 + + System.load(new File(System.getProperty("user.dir")+"\\libs\\opencv_java480.dll").getAbsolutePath()); System.out.println("新方法调用"); double baseTolerance = 50; // 初始冗余 50mm double additionalTolerancePerLevel = 20; // 每层增加 20mm 冗余 @@ -691,42 +691,88 @@ public class PointCloudProcessor { } public static void main(String[] args) { - String path ="E:\\泸州\\27\\40016743\\30,31\\d970c134-98ff-4e7a-b951-b2cbdf9acfd4--192.168.56.11.pcd"; - String configPath = "E:\\泸州\\27\\27.json"; - String typeConfPath = "E:\\泸州\\27\\temlent\\40016743.json"; - List points = readPCD(path); - PcdPojo pojo = new PcdPojo(); - PcdPojo pcdPojo = new PcdPojo(); - pojo.setConfigPath(configPath); - ObjectMapper mapper = new ObjectMapper(); - try { - // 读取 JSON 文件并转换为 配置信息 - PcdPojo pojoIn = mapper.readValue(new File(pojo.getConfigPath()), mapper.getTypeFactory().constructType(PcdPojo.class)); - // 打印转换后的实体类列表 - pojo = pojo.setPCDInfo(pojoIn); - // 读取 JSON 文件并转换为 配置信息 - pcdPojo = mapper.readValue(new File(typeConfPath), mapper.getTypeFactory().constructType(PcdPojo.class)); + String configPath = "E:\\工作\\pcd\\27.json"; + File rootFolder = new File("E:\\pr3d\\新建文件夹\\4-2"); // 替换为你自己的路径 + // 调用处理方法 + processAllSubFolders(rootFolder,configPath); + } +// 20 47 16 + /** + * 遍历所有子文件夹,每个子文件夹中只包含一个 .pcd 和一个 .json 文件 + */ + public static void processAllSubFolders(File folder,String configPath) { + if (!folder.exists() || !folder.isDirectory()) { + System.out.println("无效目录: " + folder.getAbsolutePath()); + return; + } - //报错则认为没有配置文件,返回盘点失败 - } catch (JsonMappingException e) { - e.printStackTrace(); + // 获取所有子文件夹和文件 + File[] files = folder.listFiles(); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + if (files == null) return; + + for (File file : files) { + if (file.isDirectory()) { + // 如果是子文件夹,继续递归 + processAllSubFolders(file,configPath); + } } -// int i = getLongitudinalType(points,pojo, pcdPojo.getWidth(), pcdPojo.getLength(), pcdPojo.getHigh(),pcdPojo.getArrangeType()); + // 在当前文件夹中查找 pcd 和 json 文件 + File pcdFile = null; + File jsonFile = null; + + for (File file : files) { + if (file.isFile()) { + if (file.getName().endsWith(".pcd")) { + pcdFile = file; + } else if (file.getName().endsWith(".json")) { + jsonFile = file; + } + } + } - int i = PointToImageMapper.getLongitudinalType(points,pojo, pcdPojo.getWidth(), pcdPojo.getLength(), pcdPojo.getHigh(),pcdPojo.getArrangeType()); + // 如果两个文件都存在,则执行你的逻辑 + if (pcdFile != null && jsonFile != null) { +// System.out.println("原个数"+folder.getName()); - System.out.println(i); + int count = adfadasdf(pcdFile.getAbsolutePath(),jsonFile.getAbsolutePath(),configPath); + if (count != Integer.parseInt(folder.getName().split("_")[1])){ + System.out.println("个数有问题 index:"+folder.getName().split("_")[0]+" 记录数量:"+folder.getName().split("_")[1] + +" 计算数量:"+count+" pcd位置:"+pcdFile.getAbsolutePath()); + } + } else { + // 可选:打印缺失文件的信息 + // System.out.println("缺少 pcd 或 json 文件: " + folder.getAbsolutePath()); + } } + static int adfadasdf(String pcdPath, String jsonPath, String configPath){ + try { + + List points = readPCD(pcdPath); + PcdPojo pojo = new PcdPojo(); + PcdPojo pcdPojo = new PcdPojo(); + + ObjectMapper mapper = new ObjectMapper(); + + // 读取 JSON 配置 + PcdPojo pojoIn = mapper.readValue(new File(configPath), mapper.getTypeFactory().constructType(PcdPojo.class)); + pojo = pojo.setPCDInfo(pojoIn); + + pcdPojo = mapper.readValue(new File(jsonPath), mapper.getTypeFactory().constructType(PcdPojo.class)); + // 调用你的方法 + int result = PointToImageMapper.getLongitudinalType(points, pojo, pcdPojo.getWidth(), pcdPojo.getLength(), pcdPojo.getHigh(), pcdPojo.getArrangeType()); + return result; + } catch (Exception e) { +// System.err.println("处理失败: " + folderPath + ",错误信息: " + e.getMessage()); + e.printStackTrace(); + } + return 0; + } diff --git a/web/src/main/java/com/zhehekeji/web/service/algorithm/PointToImageMapper.java b/web/src/main/java/com/zhehekeji/web/service/algorithm/PointToImageMapper.java index 421482b..db261d1 100644 --- a/web/src/main/java/com/zhehekeji/web/service/algorithm/PointToImageMapper.java +++ b/web/src/main/java/com/zhehekeji/web/service/algorithm/PointToImageMapper.java @@ -1,4 +1,7 @@ package com.zhehekeji.web.service.algorithm; +import org.opencv.core.*; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; import org.springframework.stereotype.Service; import java.awt.*; @@ -31,7 +34,7 @@ public class PointToImageMapper { public static int getLongitudinalType(List points, PcdPojo pojo, int l, int w, int height,String type){ //pojo里面的floorHeight为地板的值,以后所有的height都将根据地板值进行计算地板值减去当前点的z轴值,为高度,且当为height的倍数的时候,认为是有效的点,其中1倍的冗余在50mm,每高一层,冗余增加20mm // 计算 - System.out.println("新方法调用"); +// System.out.println("新方法调用"); double baseTolerance = 50; // 初始冗余 50mm double additionalTolerancePerLevel = 20; // 每层增加 20mm 冗余 Map> map = new HashMap<>(); @@ -78,21 +81,53 @@ public class PointToImageMapper { layersCount +=maxL; } } + int count = 0; +// for (int h =1; h<=5; h++){ +// if (map.containsKey(h) && map.get(h).size()>1000){ +// double area = 0; +// try { +// List pointsLayers = map.get(h); +// if (map.containsKey(h+1)&& map.get(h+1).size()>1000){ +// pointsLayers.addAll(map.get(h+1)); +// } +// +// area = drawPointsAndCalculateArea(pointsLayers, String.valueOf(h),pojo,l,w); +// +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// if (area>0){ +// System.out.println("面积:"+area); +// System.out.println("个数:"+area/(double) (l*w)); +// int i =(int) Math.min(Math.round(area/(double) (l*w)),layersCount); +// if (i==0|| i>=layersCount) { +// count+=layersCount; +// }else { +// return i +count; +// } +// } +// }else { +// +// count+=layersCount; +// } +// } for (int h =5; h>0; h--){ if (map.containsKey(h) && map.get(h).size()>1000){ double area = 0; try { - area = drawPointsAndCalculateArea(map.get(h), String.valueOf(h),pojo); + area = drawPointsAndCalculateArea(map.get(h), String.valueOf(h),pojo,l,w); } catch (Exception e) { throw new RuntimeException(e); } if (area>0){ - System.out.println("面积:"+area); - System.out.println("个数:"+area/(double) (l*w)); - int i =(layersCount*(h-1))+(int) Math.min(Math.round(area/(double) (l*w)),layersCount); - return i; +// System.out.println("面积:"+area); +// System.out.println("个数:"+area/(double) (l*w)); + int i =(int) Math.min(Math.round(area/(double) (l*w)),layersCount); + if (i>0) { + return (layersCount * (h - 1)) + i; + } } } } @@ -104,10 +139,11 @@ public class PointToImageMapper { * 根据点集合生成图像,并计算不同颜色区域的面积 */ public static Double drawPointsAndCalculateArea( - List points,String taskId,PcdPojo pojo + List points,String taskId,PcdPojo pojo, + int l,int w ) throws Exception { - int imageWidth = 600; - int imageHeight = 600; + int imageWidth = 1200; + int imageHeight = 1200; String drawMode= "point"; if (points == null || points.isEmpty()) { @@ -142,7 +178,7 @@ public class PointToImageMapper { g.setBackground(Color.WHITE); g.clearRect(0, 0, imageWidth, imageHeight); - // Step 5: 绘制点或连线 + // Step 5: 绘制点 List mappedPoints = new ArrayList<>(); for (double[] point : points) { @@ -203,8 +239,120 @@ public class PointToImageMapper { // 可选:保存图像 ImageIO.write(image, "PNG", generateImageFilePath().toFile()); +// int targetWidth = (int) (scaleX*l); // 替换为你想放置的矩形宽度(像素) +// int targetHeight = (int) (scaleY*w); // 替换为你想放置的矩形高度(像素) +// +// // 调用纯 Java 实现的矩形检测方法 +// int maxRectangles = detectMaxRectanglesWithRotationJavaOnly(image, targetWidth, targetHeight); +// System.out.println("最大可容纳矩形数量:" + maxRectangles); + // 可选:删除临时文件 + // tempFile.delete(); return area; } + /** + * 使用纯 Java 实现,在给定图像中检测红色区域最多可容纳的目标矩形数量(支持旋转方向判断) + * + * @param bufferedImage 输入的 BufferedImage 图像 + * @param targetWidth 目标矩形宽度(像素) + * @param targetHeight 目标矩形高度(像素) + * @return 可容纳的矩形数量 + */ + public static int detectMaxRectanglesWithRotationJavaOnly(BufferedImage bufferedImage, int targetWidth, int targetHeight) { + int width = bufferedImage.getWidth(); + int height = bufferedImage.getHeight(); + + // Step 1: 提取红色区域(RGB > 200, G/B < 50) + boolean[][] redMask = new boolean[width][height]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = bufferedImage.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + redMask[x][y] = (r > 200 && g < 50 && b < 50); + } + } + + // Step 2: 连通区域标记(8邻域) + int[][] labels = new int[width][height]; + int labelCount = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (redMask[x][y] && labels[x][y] == 0) { + labelCount++; + floodFillJavaOnly(redMask, labels, x, y, labelCount); + } + } + } + + // Step 3: 收集每个区域的边界框 + Map regionBounds = new HashMap<>(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int label = labels[x][y]; + if (label > 0) { + int finalX = x; + int finalY = y; + regionBounds.computeIfAbsent(label, k -> new int[]{finalX, finalY, finalX, finalY}); + int[] bounds = regionBounds.get(label); + bounds[0] = Math.min(bounds[0], x); + bounds[1] = Math.min(bounds[1], bounds[1]); + bounds[2] = Math.max(bounds[2], x); + bounds[3] = Math.max(bounds[3], y); + } + } + } + + // Step 4: 判断每个区域是否能容纳目标矩形 + int count = 0; + for (int[] bounds : regionBounds.values()) { + int regionWidth = bounds[2] - bounds[0]; + int regionHeight = bounds[3] - bounds[1]; +double margin = 0.90; + if ((regionWidth >= targetWidth * margin && regionHeight >= targetHeight * margin) || + (regionHeight >= targetWidth * margin && regionWidth >= targetHeight * margin)) { + + // 引入 margin 控制可用空间(比如留出 5% 边距) + int usableWidth = (int) (Math.min(regionWidth, regionHeight) * margin); + int usableHeight = (int) (Math.max(regionWidth, regionHeight) * margin); + + int numWidth = usableWidth / Math.min(targetWidth, targetHeight); + int numHeight = usableHeight / Math.max(targetWidth, targetHeight); + count += numWidth * numHeight; + } + } + + return count; + } + + /** + * 八邻域连通区域填充 + */ + private static void floodFillJavaOnly(boolean[][] redMask, int[][] labels, int startX, int startY, int label) { + Queue queue = new LinkedList<>(); + queue.offer(new int[]{startX, startY}); + labels[startX][startY] = label; + + while (!queue.isEmpty()) { + int[] point = queue.poll(); + int x = point[0]; + int y = point[1]; + + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + int nx = x + dx; + int ny = y + dy; + if (nx >= 0 && nx < redMask.length && + ny >= 0 && ny < redMask[0].length && + redMask[nx][ny] && labels[nx][ny] == 0) { + labels[nx][ny] = label; + queue.offer(new int[]{nx, ny}); + } + } + } + } + } + /** * 生成 E:/data/pcdImage/yyyy-MM-dd/HHmmssSSS.png 格式的路径 * 并创建父目录(如果不存在)