diff --git a/web/pom.xml b/web/pom.xml
index 3ceb44d..f4a7149 100644
--- a/web/pom.xml
+++ b/web/pom.xml
@@ -37,6 +37,22 @@
org.springframework.boot
spring-boot-starter-web
+
+
+ MvCameraControlWrapper
+ MvCameraControlWrapper
+ 1.0
+ system
+ ${project.basedir}/src/main/resources/libs/MvCameraControlWrapper.jar
+
+
+
+ org
+ opencv
+ system
+ 1.0
+ ${project.basedir}/src/main/resources/libs/opencv-480.jar
+
org.projectlombok
lombok
@@ -102,6 +118,14 @@
true
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 9
+ 9
+
+
diff --git a/web/src/main/java/com/zhehekeji/web/config/ConfigProperties.java b/web/src/main/java/com/zhehekeji/web/config/ConfigProperties.java
index 680baf9..facc777 100644
--- a/web/src/main/java/com/zhehekeji/web/config/ConfigProperties.java
+++ b/web/src/main/java/com/zhehekeji/web/config/ConfigProperties.java
@@ -1,5 +1,6 @@
package com.zhehekeji.web.config;
+import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@@ -88,6 +89,10 @@ public class ConfigProperties {
private Long delayDownloadMp4;
public Integer verticalAdjustmentTime = 2;
+
+ private String industrialCamera;
+
+ private String camera3D;
}
@Data
diff --git a/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java b/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java
new file mode 100644
index 0000000..68021aa
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java
@@ -0,0 +1,165 @@
+package com.zhehekeji.web.controller;
+
+import com.zhehekeji.core.pojo.Result;
+import com.zhehekeji.web.config.ConfigProperties;
+import com.zhehekeji.web.entity.Street;
+import com.zhehekeji.web.pojo.IndustrialCameraVO;
+import com.zhehekeji.web.pojo.stock.CheckLogSearch;
+import com.zhehekeji.web.service.IndustrialCamera.HikSaveImage;
+import com.zhehekeji.web.service.IndustrialCamera.LxPointCloudSaveImage;
+import com.zhehekeji.web.service.PlcService;
+import com.zhehekeji.web.service.StreetService;
+import com.zhehekeji.web.service.algorithm.FeatureMatchingExample;
+import com.zhehekeji.web.service.algorithm.InventoryService;
+import com.zhehekeji.web.service.algorithm.PcdPojo;
+import com.zhehekeji.web.service.algorithm.PointCloudProcessor;
+import com.zhehekeji.web.service.client.TransmissionPojo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.opencv.core.Mat;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.UUID;
+
+import static com.zhehekeji.web.service.algorithm.FeatureMatchingExample.base642Mat;
+
+@Slf4j
+@Api(tags = "工业相机")
+@RequestMapping("/industrialCamera")
+@RestController
+public class IndustrialCameraController {
+ @Resource
+ HikSaveImage hikSaveImage;
+
+ @Resource
+ StreetService streetService;
+ @Resource
+ PlcService plcService;
+
+
+ @Resource
+ ConfigProperties configProperties;
+
+ @ApiOperation("拍照")
+ @PostMapping("pic")
+ public Result pic(@RequestBody CheckLogSearch checkLogSearch){
+ Street street = streetService.streetById(checkLogSearch.getStreetId());
+ if(configProperties.getCameraConfig().getIndustrialCamera()!=null) {
+ String path = "industrialCamera/" + UUID.randomUUID() + ".jpeg";
+ hikSaveImage.saveImage(configProperties.getCameraConfig().getIndustrialCamera(), configProperties.getSavePath().getMediaPath() + path, "sn");
+ return new Result<>(path);
+ }else {
+ return new Result<>("");
+ }
+ }
+
+ @ApiOperation("识别")
+ @PostMapping("Macth")
+ public Result Macth(@RequestBody IndustrialCameraVO industrialCameraVo){
+ String srcImg = configProperties.getSavePath().getMediaPath() + industrialCameraVo.getPicImg();
+ String srcImgRest = configProperties.getSavePath().getMediaPath() + industrialCameraVo.getPicImg();
+ //识别结果
+ boolean re = FeatureMatchingExample.matchTemplate(
+ InventoryService.readImagesInFolder(configProperties.getSavePath().getMediaPath() + "template/" + industrialCameraVo.getTypeMacth()),
+ srcImg);
+ if (re){
+
+ return new Result<>(industrialCameraVo.getPicImg()+".jpg");
+ }else {
+
+ return new Result<>("");
+ }
+ }
+
+
+ @ApiOperation("识别3D")
+ @PostMapping("Macth3D")
+ public Result Macth3D(@RequestBody IndustrialCameraVO industrialCameraVo){
+ LocalDate currentDate = LocalDate.now();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ //路径
+ String pathPcd = configProperties.getSavePath().getMediaPath()
+ +"camera3D/"
+ +currentDate.format(formatter)+"/" +UUID.randomUUID()+"-";
+ Integer count = inventoryService.match3D(industrialCameraVo.getName(),pathPcd+"-"+configProperties.getCameraConfig().getCamera3D()+".pcd");
+ return Result.success(count);
+ }
+
+ @Resource
+ InventoryService inventoryService;
+//
+// @ApiOperation("匹配所有")
+// @PostMapping("matchFeatures")
+// public Boolean matchFeatures(@RequestBody TransmissionPojo transmissionPojo) {
+// //return true;
+//
+// return null;
+// }
+
+//
+// @ApiOperation("随行")
+// @PostMapping("actionType")
+// public void action(@RequestBody TransmissionPojo transmissionPojo) {
+//
+// Street street = streetService.getStreetByPlcId(transmissionPojo.getStreetNumber());
+//
+//
+// //随行地址
+// String pcdPath1 =configProperties.getSavePath().getMediaPath() + street.getLeft3D()+"\\000\\"
+// +transmissionPojo.getRow()+"-"+transmissionPojo.getColumn()+"-"+transmissionPojo.getDirection()+"-L"+".pcd";
+//
+// String pcdPath2 =configProperties.getSavePath().getMediaPath() + street.getRight3D()+"\\000\\"
+// +transmissionPojo.getRow()+"-"+transmissionPojo.getColumn()+"-"+transmissionPojo.getDirection()+"-R"+".pcd";
+// //拍照
+// System.out.println(street.getLeft3D()+" 111211 "+ pcdPath1);
+// LxPointCloudSaveImage lxPointCloudSaveImage = new LxPointCloudSaveImage();
+// lxPointCloudSaveImage.saveImage(street.getLeft3D(), pcdPath1,1);
+//
+// LxPointCloudSaveImage lxPointCloudSaveImage1 = new LxPointCloudSaveImage();
+// lxPointCloudSaveImage1.saveImage(street.getRight3D(), pcdPath2,2);
+// }
+// @ApiOperation("模板建立")
+// @PostMapping("MacthCreate")
+// public Result MacthCreate(@RequestBody IndustrialCameraVO industrialCameraVo){
+// Path folder = Paths.get(configProperties.getSavePath().getMediaPath()+"template/"+industrialCameraVo.getTypeMacth());
+// try {
+// Files.walkFileTree(folder, new SimpleFileVisitor() {
+// @Override
+// public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+// Files.delete(file);
+// return FileVisitResult.CONTINUE;
+// }
+//
+// @Override
+// public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+// Files.delete(dir);
+// return FileVisitResult.CONTINUE;
+// }
+// });
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// for (int i = 0; i < industrialCameraVo.getFileList().size(); i++){
+// try {
+// Mat template = base642Mat(industrialCameraVo.getFileList().get(i).getThumbUrl()
+// .replace("data:image/jpeg;base64,","").replace("data:image/png;base64,",""));
+//
+// Imgcodecs.imwrite(configProperties.getSavePath().getMediaPath()+"template/"+industrialCameraVo.getTypeMacth()+"/"+i+".jpg", template);
+// } catch (IOException e) {
+// throw new RuntimeException(e);
+// }
+// }
+// return new Result<>(true);
+// }
+}
diff --git a/web/src/main/java/com/zhehekeji/web/controller/KuKouController.java b/web/src/main/java/com/zhehekeji/web/controller/KuKouController.java
index 5845e42..55a658c 100644
--- a/web/src/main/java/com/zhehekeji/web/controller/KuKouController.java
+++ b/web/src/main/java/com/zhehekeji/web/controller/KuKouController.java
@@ -1,11 +1,45 @@
package com.zhehekeji.web.controller;
+import com.github.pagehelper.PageInfo;
+import com.zhehekeji.core.pojo.Result;
+import com.zhehekeji.web.entity.KuKou;
+import com.zhehekeji.web.pojo.OrderSearch;
+import com.zhehekeji.web.pojo.OrderVO;
+import com.zhehekeji.web.service.KuKouService;
+import com.zhehekeji.web.service.OrderService;
import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import javax.annotation.Resource;
+
@Api(tags = "盘点统计")
@RequestMapping("/KuKouController")
@RestController
public class KuKouController {
+ @Resource
+ private KuKouService kuKouService;
+ @Resource
+ private OrderService orderService;
+
+
+ @PostMapping("/list")
+ @ApiOperation(value = "查询")
+ //@SessionHandler
+ public Result> list(@RequestBody OrderSearch orderSearch) {
+ //validatorUtil.validate(orderSearch);
+ return Result.success(kuKouService.pageInfo(orderSearch));
+ }
+
+ @PostMapping("/latest")
+ @ApiOperation(value = "最新数据")
+ //@SessionHandler
+ public Result latest() {
+ //validatorUtil.validate(orderSearch);
+ return Result.success(kuKouService.latest());
+ }
+
}
diff --git a/web/src/main/java/com/zhehekeji/web/entity/KuKou.java b/web/src/main/java/com/zhehekeji/web/entity/KuKou.java
index e7a33c7..2ac3d8f 100644
--- a/web/src/main/java/com/zhehekeji/web/entity/KuKou.java
+++ b/web/src/main/java/com/zhehekeji/web/entity/KuKou.java
@@ -1,5 +1,7 @@
package com.zhehekeji.web.entity;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
@@ -9,10 +11,13 @@ import java.time.LocalDateTime;
@TableName("kukou")
@Data
public class KuKou {
+
+ @TableId(type = IdType.UUID)
private int id;
private int count;
private String code;
private String path;
+ private int flag;
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
diff --git a/web/src/main/java/com/zhehekeji/web/entity/Street.java b/web/src/main/java/com/zhehekeji/web/entity/Street.java
index b843850..4af1c77 100644
--- a/web/src/main/java/com/zhehekeji/web/entity/Street.java
+++ b/web/src/main/java/com/zhehekeji/web/entity/Street.java
@@ -57,4 +57,6 @@ public class Street {
private Integer videoStyleRow;
private Integer videoStyleColumn;
+
+
}
diff --git a/web/src/main/java/com/zhehekeji/web/mapper/KukouMapper.java b/web/src/main/java/com/zhehekeji/web/mapper/KukouMapper.java
new file mode 100644
index 0000000..f419309
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/mapper/KukouMapper.java
@@ -0,0 +1,8 @@
+package com.zhehekeji.web.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhehekeji.web.entity.EmptyCheckPic;
+import com.zhehekeji.web.entity.KuKou;
+
+public interface KukouMapper extends BaseMapper {
+}
diff --git a/web/src/main/java/com/zhehekeji/web/pojo/IndustrialCameraVO.java b/web/src/main/java/com/zhehekeji/web/pojo/IndustrialCameraVO.java
new file mode 100644
index 0000000..0528cc0
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/pojo/IndustrialCameraVO.java
@@ -0,0 +1,17 @@
+package com.zhehekeji.web.pojo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class IndustrialCameraVO {
+ private String name;
+ private String picImg;
+ private String typeMacth;
+ //private List fileList;
+ private String pcdPath;
+
+
+ private String streetId;
+}
diff --git a/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/CameraSaveUtil.java b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/CameraSaveUtil.java
new file mode 100644
index 0000000..ef67264
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/CameraSaveUtil.java
@@ -0,0 +1,12 @@
+package com.zhehekeji.web.service.IndustrialCamera;
+
+public interface CameraSaveUtil {
+ /**
+ *
+ * @param sn sn
+ * @param path 图片路径
+ * @param type 暂时默认sn 后续可以添加ip等
+ * @return
+ */
+ boolean saveImage(String sn,String path,String type);
+}
diff --git a/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/DcLibrary.java b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/DcLibrary.java
new file mode 100644
index 0000000..1d0af86
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/DcLibrary.java
@@ -0,0 +1,50 @@
+package com.zhehekeji.web.service.IndustrialCamera;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+import com.sun.jna.ptr.PointerByReference;
+
+import java.util.Arrays;
+import java.util.List;
+public interface DcLibrary extends Library {
+ DcLibrary INSTANCE = (DcLibrary) Native.load((System.getProperty("user.dir"))+"\\libs\\plc\\LxCameraApiR.dll", DcLibrary.class);
+ // 创建一个新的实例
+
+ // 设备详细信息结构体
+ class LxDeviceInfo extends Structure {
+ public long handle; // 使用 long 类型表示 DcHandle
+ public short dev_type;
+ public byte[] id = new byte[32];
+ public byte[] ip = new byte[32];
+ public byte[] sn = new byte[32];
+ public byte[] mac = new byte[32];
+ public byte[] firmware_ver = new byte[32];
+ public byte[] algor_ver = new byte[32];
+ public byte[] name = new byte[32];
+ public byte[] reserve = new byte[32];
+ public byte[] reserve2 = new byte[32];
+ public byte[] reserve3 = new byte[64];
+ public byte[] reserve4 = new byte[128];
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList("handle", "dev_type", "id", "ip", "sn", "mac", "firmware_ver", "algor_ver", "name", "reserve", "reserve2", "reserve3", "reserve4");
+ }
+ }
+
+ // 设备打开函数
+ int DcOpenDevice(int open_mode, String param, PointerByReference handle, LxDeviceInfo info);
+
+ String DcGetDeviceList(PointerByReference devlist, int devnum);
+ int DcCloseDevice(Pointer handle);
+
+ int DcStartStream(Pointer handle);
+
+ int DcStopStream(Pointer handle);
+
+ int DcSaveXYZ(Pointer handle, String filename);
+ int DcSetCmd(Pointer handle, int cmd);
+}
+// Define LX_OPEN_MODE as an enum or int based on your C definition
diff --git a/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/HikSaveImage.java b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/HikSaveImage.java
new file mode 100644
index 0000000..b599723
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/HikSaveImage.java
@@ -0,0 +1,321 @@
+package com.zhehekeji.web.service.IndustrialCamera; /***************************************************************************************************
+ * @file SaveImage.java
+ * @breif Use functions provided in MvCameraControlWrapper.jar to save image as JPEG。
+ * @author zhanglei72
+ * @date 2020/02/10
+ *
+ * @warning
+ * @version V1.0.0 2020/02/10 Create this file
+ * @since 2020/02/10
+ **************************************************************************************************/
+
+import MvCameraControlWrapper.*;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PreDestroy;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static MvCameraControlWrapper.MvCameraControl.*;
+import static MvCameraControlWrapper.MvCameraControlDefines.*;
+
+@Service
+public class HikSaveImage implements CameraSaveUtil{
+
+ // 设备句柄保存
+ static Map handleMap = new ConcurrentHashMap<>();
+ private static void printDeviceInfo(MV_CC_DEVICE_INFO stDeviceInfo)
+ {
+ if (null == stDeviceInfo) {
+ System.out.println("stDeviceInfo is null");
+ return;
+ }
+
+ if (stDeviceInfo.transportLayerType == MV_GIGE_DEVICE) {
+ System.out.println("\tCurrentIp: " + stDeviceInfo.gigEInfo.currentIp);
+ System.out.println("\tserialNumber: " + stDeviceInfo.gigEInfo.serialNumber);
+ System.out.println("\tModel: " + stDeviceInfo.gigEInfo.modelName);
+ System.out.println("\tUserDefinedName: " + stDeviceInfo.gigEInfo.userDefinedName);
+ } else if (stDeviceInfo.transportLayerType == MV_USB_DEVICE) {
+ System.out.println("\tUserDefinedName: " + stDeviceInfo.usb3VInfo.userDefinedName);
+ System.out.println("\tSerial Number: " + stDeviceInfo.usb3VInfo.serialNumber);
+ System.out.println("\tDevice Number: " + stDeviceInfo.usb3VInfo.deviceNumber);
+ } else {
+ System.err.print("Device is not supported! \n");
+ }
+
+ System.out.println("\tAccessible: "
+ + MvCameraControl.MV_CC_IsDeviceAccessible(stDeviceInfo, MV_ACCESS_Exclusive));
+ System.out.println("");
+ }
+
+ private static void printFrameInfo(MV_FRAME_OUT_INFO stFrameInfo)
+ {
+ if (null == stFrameInfo)
+ {
+ System.err.println("stFrameInfo is null");
+ return;
+ }
+
+ StringBuilder frameInfo = new StringBuilder("");
+ frameInfo.append(("\tFrameNum[" + stFrameInfo.frameNum + "]"));
+ frameInfo.append("\tWidth[" + stFrameInfo.width + "]");
+ frameInfo.append("\tHeight[" + stFrameInfo.height + "]");
+ frameInfo.append(String.format("\tPixelType[%#x]", stFrameInfo.pixelType.getnValue()));
+
+ System.out.println(frameInfo.toString());
+ }
+
+ public static void saveDataToFile(byte[] dataToSave, int dataSize, String fileName)
+ {
+ OutputStream os = null;
+
+ try
+ {
+ Path path = Paths.get(fileName);
+
+ // 检查文件是否存在
+ if (!Files.exists(path)) {
+ try {
+ // 创建所有父目录
+ Files.createDirectories(path.getParent());
+
+
+ } catch (IOException e) {
+ System.err.println("Failed to create file: " + e.getMessage());
+ }
+ } else {
+ System.out.println("File already exists: " + path);
+ }
+ // 创建文件
+
+ os = new FileOutputStream( fileName);
+ os.write(dataToSave, 0, dataSize);
+ System.out.println("SaveImage succeed.");
+ // Create directory
+
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ // Close file stream
+ try
+ {
+ os.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static int chooseCamera(ArrayList stDeviceList,String sn ,String type)
+ {
+ if (null == stDeviceList)
+ {
+ return -1;
+ }
+ for(int i = 0; i < stDeviceList.size(); i++){
+ if(type.equals("sn") && sn.equals(stDeviceList.get(i).gigEInfo.serialNumber)){
+
+ return i;
+ }else if(type.equals("ip") && sn.equals(stDeviceList.get(i).gigEInfo.currentIp)){
+
+ return i;
+ }
+ }
+ return -1;
+
+
+ }
+
+ public static void main(String[] args)
+ {
+ HikSaveImage hikSaveImage = new HikSaveImage();
+ hikSaveImage.saveImage("00J51775660", "E:\\m\\55.bpm","sn");
+ }
+ @PreDestroy
+ void clean(){
+ for (Handle h :handleMap.values()){
+
+ // Stop grabbing
+ int nRet = MvCameraControl.MV_CC_StopGrabbing(h);
+ if (MV_OK != nRet)
+ {
+ System.err.printf("StopGrabbing fail, errcode: [%#x]\n", nRet);
+ break;
+ }
+ nRet = MvCameraControl.MV_CC_DestroyHandle(h);
+ if (MV_OK != nRet) {
+ System.err.printf("DestroyHandle failed, errcode: [%#x]\n", nRet);
+ }
+ }
+
+ }
+ @Override
+ public boolean saveImage(String sn, String path,String type) {
+ int nRet = MV_OK;
+ ArrayList stDeviceList;
+
+ do
+ {
+ System.out.println("SDK Version " + MvCameraControl.MV_CC_GetSDKVersion());
+
+ // Enuerate GigE and USB devices
+ try
+ {
+ stDeviceList = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE);
+ if (0 >= stDeviceList.size())
+ {
+ System.out.println("No devices found!");
+ break;
+ }
+ }
+ catch (CameraControlException e)
+ {
+ System.err.println("Enumrate devices failed!" + e.toString());
+ e.printStackTrace();
+ break;
+ }
+
+ // choose camera
+
+
+ Handle hCamera = init(sn,stDeviceList,type);
+ // Create handle
+
+
+
+ // Get payload size
+ MVCC_INTVALUE stParam = new MVCC_INTVALUE();
+ nRet = MvCameraControl.MV_CC_GetIntValue(hCamera, "PayloadSize", stParam);
+ if (MV_OK != nRet)
+ {
+ System.err.printf("Get PayloadSize fail, errcode: [%#x]\n", nRet);
+ break;
+ }
+
+
+
+ // Get one frame
+ MV_FRAME_OUT_INFO stImageInfo = new MV_FRAME_OUT_INFO();
+ byte[] pData = new byte[(int)stParam.curValue];
+ nRet = MvCameraControl.MV_CC_GetOneFrameTimeout(hCamera, pData, stImageInfo, 1000);
+ while(true){
+ if(stImageInfo.height >0 && stImageInfo.width >0){
+ break;
+ }
+ }
+ if (MV_OK != nRet)
+ {
+ System.err.printf("GetOneFrameTimeout fail, errcode:[%#x]\n", nRet);
+ break;
+ }
+
+ System.out.println("GetOneFrame: ");
+ printFrameInfo(stImageInfo);
+ int imageLen = stImageInfo.width * stImageInfo.height * 3; // Every RGB pixel takes 3 bytes
+ byte[] imageBuffer = new byte[imageLen];
+
+ // Call MV_CC_SaveImage to save image as JPEG
+ MV_SAVE_IMAGE_PARAM stSaveParam = new MV_SAVE_IMAGE_PARAM();
+ stSaveParam.width = stImageInfo.width; // image width
+ stSaveParam.height = stImageInfo.height; // image height
+ stSaveParam.data = pData; // image data
+ stSaveParam.dataLen = stImageInfo.frameLen; // image data length
+ stSaveParam.pixelType = stImageInfo.pixelType; // image pixel format
+ stSaveParam.imageBuffer = imageBuffer; // output image buffer
+ stSaveParam.imageType = MV_SAVE_IAMGE_TYPE.MV_Image_Jpeg; // output image pixel format
+ stSaveParam.methodValue = 0; // Interpolation method that converts Bayer format to RGB24. 0-Neareast 1-double linear 2-Hamilton
+ stSaveParam.jpgQuality = 99; // JPG endoding quality(50-99]
+
+ nRet = MvCameraControl.MV_CC_SaveImage(hCamera, stSaveParam);
+ if (MV_OK != nRet)
+ {
+ System.err.printf("SaveImage fail, errcode: [%#x]\n", nRet);
+ break;
+ }
+
+ // Save buffer content to file
+ saveDataToFile(imageBuffer, imageLen, path);
+
+ } while (false);
+
+
+ return false;
+ }
+ Handle init(String sn, ArrayList stDeviceList,String type){
+ int nRet = MV_OK;
+ int camIndex = -1;
+
+ Handle hCamera = null;
+ try
+ {
+ if(handleMap.containsKey(sn) && handleMap.get(sn)!=null){
+ //已经获得句柄
+ hCamera = handleMap.get(sn);
+ }else {
+ System.out.println("创建句柄");
+ camIndex = chooseCamera(stDeviceList,sn,type);
+ if (camIndex == -1)
+ {
+ System.out.println("未找到摄像头");
+ return null;
+ }
+ //未获得句柄并初始化
+ hCamera = MvCameraControl.MV_CC_CreateHandle(stDeviceList.get(camIndex));
+ handleMap.put(sn,hCamera);
+
+ // Open device
+ nRet = MvCameraControl.MV_CC_OpenDevice(hCamera);
+ if (MV_OK != nRet)
+ {
+ System.err.printf("Connect to camera failed, errcode: [%#x]\n", nRet);
+
+ return null;
+ }
+
+ // Make sure that trigger mode is off
+ nRet = MvCameraControl.MV_CC_SetEnumValueByString(hCamera, "TriggerMode", "Off");
+ if (MV_OK != nRet)
+ {
+ System.err.printf("SetTriggerMode failed, errcode: [%#x]\n", nRet);
+
+ return null;
+ }
+ // Get payload size
+
+
+ // Start grabbing
+ nRet = MvCameraControl.MV_CC_StartGrabbing(hCamera);
+ if (MV_OK != nRet)
+ {
+ System.err.printf("Start Grabbing fail, errcode: [%#x]\n", nRet);
+
+ return null;
+ }
+ }
+ }
+ catch (CameraControlException e)
+ {
+ System.err.println("Create handle failed!" + e.toString());
+ e.printStackTrace();
+
+
+ return null;
+ }
+ System.out.println(hCamera);
+ return hCamera;
+ }
+}
diff --git a/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/LxPointCloudSaveImage.java b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/LxPointCloudSaveImage.java
new file mode 100644
index 0000000..8c16150
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/LxPointCloudSaveImage.java
@@ -0,0 +1,106 @@
+package com.zhehekeji.web.service.IndustrialCamera;
+
+import com.sun.jna.Native;
+import com.sun.jna.ptr.PointerByReference;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+public class LxPointCloudSaveImage {
+
+ static DcLibrary INSTANCER = (DcLibrary) Native.loadLibrary((System.getProperty("user.dir"))+"\\libs\\plc\\LxCameraApi.dll", DcLibrary.class);
+
+ static Map handleMap = new ConcurrentHashMap<>();
+ public static PointerByReference getHandle(String sn,int type ,DcLibrary INSTANCE){
+ if(handleMap.get(sn)!=null){
+ return handleMap.get(sn);
+ }else {
+ // 创建 Pointer 的引用
+ PointerByReference handleRef = new PointerByReference();
+
+
+ // 创建 PointerByReference 的实例用于接收设备列表
+ // PointerByReference devlistRef = new PointerByReference();
+ // 创建 LxDeviceInfo 实例
+ DcLibrary.LxDeviceInfo info = new DcLibrary.LxDeviceInfo();
+
+ //library.DcGetDeviceList( devlistRef,0);
+
+ // 调用 DcOpenDevice 函数
+ System.out.println(sn+" "+handleRef+" "+info);
+ int result = INSTANCE.DcOpenDevice(1, sn, handleRef, info);
+ int i = 0;
+ if(result ==0) {
+ handleMap.put(sn, handleRef);
+ }else {
+ for (int ii=0;i<50;i++ ) {
+ result = INSTANCE.DcOpenDevice(1, sn, handleRef, info);
+ System.out.println(i + "次尝试");
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ // 输出结果
+ System.out.println("Result: " + result);
+ System.out.println("Handle: " + handleRef.getValue()); // 获取 DcHandle 的值
+ System.out.println("Device Type: " + info.dev_type);
+ System.out.println("ID: " + new String(info.id).trim());
+ System.out.println("IP: " + new String(info.ip).trim());
+ System.out.println("SN: " + new String(info.sn).trim());
+ System.out.println("MAC: " + new String(info.mac).trim());
+ System.out.println("Firmware Version: " + new String(info.firmware_ver).trim());
+ System.out.println("Algorithm Version: " + new String(info.algor_ver).trim());
+ System.out.println("Name: " + new String(info.name).trim());
+ System.out.println("Reserve: " + new String(info.reserve).trim());
+ System.out.println("Reserve2: " + new String(info.reserve2).trim());
+ System.out.println("Reserve3: " + new String(info.reserve3).trim());
+ System.out.println("Reserve4: " + new String(info.reserve4).trim());//lxPointCloudApi.DcCloseDevice(handle);
+
+
+ return handleRef;
+ }
+
+ }
+ public static boolean saveImage(String sn, String path,int type) {
+ DcLibrary INSTANCE;
+ INSTANCE = INSTANCER;
+ int result = 0;
+ PointerByReference handleRef = getHandle(sn,type,INSTANCE);
+ result = INSTANCE.DcStartStream(handleRef.getValue());
+ System.out.println("Result: " + result);
+ result = INSTANCE.DcSetCmd(handleRef.getValue(),5001);
+
+
+ System.out.println("Result: " + result);
+ Path path1 = Paths.get(path);
+
+ try {
+ // 如果路径不存在,则创建目录
+ Files.createDirectories(path1.getParent());
+ }catch (Exception e){
+ log.info("路径失败");
+ }
+ int i = INSTANCE.DcSaveXYZ(handleRef.getValue(), path);
+ log.info(sn+" 3dCamera get pcd :"+path +";rest:"+i);
+ System.out.println(sn+" 3dCamera get pcd :"+path +";rest:"+i);
+ result = INSTANCE.DcStopStream(handleRef.getValue());
+
+ System.out.println("stop Stream Result: " + result);
+
+ return true;
+ }
+
+ public static void main(String[] args) {
+ saveImage("1","E:\\1-8-2-R.pcd",1);
+
+ saveImage("2","E:\\12-R.pcd",2);
+ }
+}
diff --git a/web/src/main/java/com/zhehekeji/web/service/KuKouService.java b/web/src/main/java/com/zhehekeji/web/service/KuKouService.java
new file mode 100644
index 0000000..85f2bd5
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/KuKouService.java
@@ -0,0 +1,32 @@
+package com.zhehekeji.web.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import com.zhehekeji.web.entity.KuKou;
+import com.zhehekeji.web.mapper.KukouMapper;
+import com.zhehekeji.web.pojo.OrderSearch;
+import com.zhehekeji.web.pojo.OrderVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+@Slf4j
+public class KuKouService extends ServiceImpl implements IService {
+ public PageInfo pageInfo(OrderSearch orderSearch){
+ PageHelper.startPage(orderSearch.getPageNum(), orderSearch.getPageSize());
+ List kuKous = list(new QueryWrapper<>());
+
+ return new PageInfo<>(kuKous);
+ }
+
+ public KuKou latest(){
+ KuKou kuKou = getOne(new QueryWrapper().orderByDesc("createTime").last("limit 1"));
+
+ return kuKou;
+ }
+}
diff --git a/web/src/main/java/com/zhehekeji/web/service/PlcCmdInfo.java b/web/src/main/java/com/zhehekeji/web/service/PlcCmdInfo.java
index 0305d73..182b24e 100644
--- a/web/src/main/java/com/zhehekeji/web/service/PlcCmdInfo.java
+++ b/web/src/main/java/com/zhehekeji/web/service/PlcCmdInfo.java
@@ -15,7 +15,7 @@ public class PlcCmdInfo {
private String taskId;
/**
- * 前两个命令 库内?苦口? 1:库内 2:库口
+ * 前两个命令 库内?苦口? 1:库内 2:库口plcCmdInfo
*/
private Integer side1;
diff --git a/web/src/main/java/com/zhehekeji/web/service/PlcService.java b/web/src/main/java/com/zhehekeji/web/service/PlcService.java
index 8ccbf9c..1ad36c1 100644
--- a/web/src/main/java/com/zhehekeji/web/service/PlcService.java
+++ b/web/src/main/java/com/zhehekeji/web/service/PlcService.java
@@ -12,6 +12,7 @@ import com.zhehekeji.web.pojo.stock.StockStatus;
import com.zhehekeji.web.service.RFID.RFIDMap;
import com.zhehekeji.web.service.RFID.RFIDSocket;
import com.zhehekeji.web.entity.EmptyCheckPic;
+import com.zhehekeji.web.service.algorithm.InventoryService;
import com.zhehekeji.web.service.client.ClientChanel;
import com.zhehekeji.web.service.client.TransmissionPojo;
import com.zhehekeji.web.service.ksec.KsecDataInfo;
@@ -26,7 +27,9 @@ import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.time.Duration;
+import java.time.LocalDate;
import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
@@ -42,6 +45,8 @@ import java.util.stream.Collectors;
@Service
@Slf4j
public class PlcService {
+ @Resource
+ KuKouService kuKouService;
@Resource
private OrderMapper orderMapper;
@@ -80,6 +85,9 @@ public class PlcService {
@Resource
private CheckStreetSummaryMapper checkStreetSummaryMapper;
+ @Resource
+ private InventoryService inventoryService;
+
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,8,200,TimeUnit.MINUTES,new ArrayBlockingQueue<>(100000));
public void setCameraControlModule(CameraControlModule cameraControlModule){
@@ -332,6 +340,7 @@ public class PlcService {
}
//update order info after capture
if (path != null && needCapture) {
+
captureUpdateOrderAndStock(orderInfo, path);
}
//转向原点位
@@ -347,7 +356,7 @@ public class PlcService {
@Resource
ReentrantLockExample reentrantLockExample;
- String scan(PlcCmdInfo plcCmdInfo,Street street,String path){
+ String scan(PlcCmdInfo plcCmdInfo,Street street){
Boolean trayCheck = Boolean.FALSE;
String trayCode = "";
log.info("扫码类型:" + configProperties.getScanCodeMode().getTray());
@@ -388,8 +397,29 @@ public class PlcService {
}
}
}else {
+ Integer cameraId;
+ String code = "E1-DEEP-";
+ String path = "";
+
+ //使用同侧的camera
+ if(plcCmdInfo.getLeftRight1() ==1){
+ cameraId = street.getCamera1Id();
+ code = code+"L";
+ }else {
+ cameraId = street.getCamera2Id();
+ code = code+"R";
+ }
+ path = PathUtil.createFileNameByRowColumn("jpg",cameraId,plcCmdInfo.getRow1(),plcCmdInfo.getColumn1());
+ gyrateCameraByCode(cameraId,code);
+ try {
+ Thread.sleep(1500L);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ path = cameraCapture(cameraId,false,0L,path);
- trayCode = reentrantLockExample.identify(path,configProperties.getIntelliBink().getIp(),configProperties.getIntelliBink().getPort());
+
+ trayCode = reentrantLockExample.identify(path,configProperties.getIntelliBink().getIp(),configProperties.getIntelliBink().getPort());
if(trayCode != null && !"".equals(trayCode)){
if ("".equals(plcCmdInfo.getWmsCode())) {
//托盘码为空,无货物
@@ -419,6 +449,31 @@ public class PlcService {
plcCmdInfo.setTrayCode(trayCode);
return trayCode;
}
+ public boolean checkVision(KsecDataInfo dataInfo){
+ KuKou kuKou = new KuKou();
+ LocalDate currentDate = LocalDate.now();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+ //路径
+ String path = configProperties.getSavePath().getMediaPath()
+ +"industrialCamera/"
+ +currentDate.format(formatter)+"/" +UUID.randomUUID()+"-";
+ //路径
+ String pathPcd = configProperties.getSavePath().getMediaPath()
+ +"camera3D/"
+ +currentDate.format(formatter)+"/" +UUID.randomUUID()+"-";
+ boolean flag2D =inventoryService.match2D(dataInfo.getCategoryName(),path+configProperties.getCameraConfig().getIndustrialCamera()+".jpeg");
+ int count = inventoryService.match3D(dataInfo.getCategoryName(),pathPcd+"-"+configProperties.getCameraConfig().getCamera3D()+".pcd");
+ kuKou.setCreateTime(LocalDateTime.now());
+ if (flag2D) {
+ kuKou.setFlag(1);
+ }
+ kuKou.setCode(dataInfo.getCategoryName());
+ kuKou.setCount(count);
+ kuKouService.save(kuKou);
+ return true;
+ }
+
public Boolean check(PlcCmdInfo plcCmdInfo,String cmdCode,String wmsCode,String wmsTrayCode,String wmsCatagary){
@@ -526,69 +581,6 @@ public class PlcService {
}
Boolean trayGoodCheck = Boolean.TRUE;
- //扫货物
- if(configProperties.getScanCodeMode().getGoods() == 2) {
- log.info("扫码类型:" + configProperties.getScanCodeMode().getTray());
- //托盘使用sick扫码枪
- SensorGun sensorGun = sensorService.getSensorByPlc(street.getId(), plcCmdInfo.getLeftRight1());
- if (sensorGun == null) {
- trayCode = "扫码枪识别异常";
- trayGoodCheck = Boolean.FALSE;
- log.error("no sensor gun config in database ,street id:{},direction:{}", street.getId(), plcCmdInfo.getLeftRight1());
- } else {
- trayCode = SickSocket.readOCR(sensorGun.getIp(), sensorGun.getPort());
- log.info("sensor tray code:{}", trayCode);
- if ("".equals(wmsTrayCode)) {
- //托盘码为空,无货物
- //只要扫码枪未识别出条码,即认为盘点正确
- if (StringUtils.isEmpty(trayCode) || trayCode.equals("NoRead")) {
- trayCode = "";
- trayGoodCheck = Boolean.TRUE;
- } else {
- trayCode = "扫码枪识别异常";
- trayGoodCheck = Boolean.FALSE;
- log.warn("sick ocr error:{}", trayCode);
- }
- } else {
- if ((StringUtils.isEmpty(trayCode) || trayCode.equals("NoRead")) ){
- trayCode = "扫码枪识别异常";
- trayGoodCheck = Boolean.FALSE;
- log.warn("sick ocr error:{}", trayCode);
- } else if(trayCode.equals(wmsTrayCode)){
- //扫到码
- trayGoodCheck = Boolean.TRUE;
- }else {
-
- trayGoodCheck = Boolean.FALSE;
- }
- }
- }
- }else
- if(configProperties.getScanCodeMode().getGoods() == 3){
- Set tags = new HashSet<>();
- try {
- RFIDCheck(plcCmdInfo);
- Thread.sleep(1000 * configProperties.getRfid().getScanTime());
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- tags = RFIDStop(plcCmdInfo);
- log.info("盘点rfid扫描结果:" + tags);
- }
- if (tags !=null && tags.contains(wmsTrayCode)) {
- //托盘码为空,无货物
- //只要扫码枪未识别出条码,即认为盘点正确
- trayCode = wmsTrayCode;
- trayGoodCheck = Boolean.TRUE;
-
- } else {
- trayCode = trayCode+ "扫码枪识别异常";
- trayGoodCheck = Boolean.FALSE;
- log.warn("rfid error:{}", trayCode);
-
- }
- }
-
OrderInfo orderInfo = new OrderInfo(street, plcCmdInfo, 1, cmdCode);
Stock stock = stockMapper.getByStreetAndDirectionAndSideAndRowColumn(orderInfo.getStreetId(), plcCmdInfo.getLeftRight1(), orderInfo.getSeparation(), orderInfo.getRow(), orderInfo.getColumn());
@@ -932,11 +924,7 @@ public class PlcService {
return true;
}
- /**
- * 保存盘点记录
- * @param scTransmission
- * @return
- */
+
// public Stock checkStart(SCTransmission scTransmission){
// log.info("【开始盘点货位】save stock info ,taskId:{},SRMNumber:{},goodsLocation:{}",scTransmission.getTaskNo(),scTransmission.getSRMNumber(),scTransmission.getGoodsLocation());
// Street street = streetService.getStreetByPlcId(scTransmission.getSRMNumber());
diff --git a/web/src/main/java/com/zhehekeji/web/service/algorithm/FeatureMatchingExample.java b/web/src/main/java/com/zhehekeji/web/service/algorithm/FeatureMatchingExample.java
new file mode 100644
index 0000000..de5b8c4
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/algorithm/FeatureMatchingExample.java
@@ -0,0 +1,305 @@
+package com.zhehekeji.web.service.algorithm;
+
+
+import org.opencv.core.*;
+import org.opencv.core.Point;
+import org.opencv.features2d.DescriptorMatcher;
+import org.opencv.features2d.Features2d;
+import org.opencv.features2d.ORB;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Base64;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * 通过opencv进行图片识别,包括图片识别,模板建立,模板识别等
+ */
+
+public class FeatureMatchingExample {
+ static {
+ // 加载动态库
+
+ try {
+ String path = System.getProperty("user.dir")+"\\libs";
+ System.setProperty("java.library.path", path);
+ Field fieldSysPath = null;
+ fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
+
+ fieldSysPath.setAccessible(true);
+ fieldSysPath.set(null, null);
+ //URL url = ClassLoader.getSystemResource(System.getProperty("user.dir")+"/libs/opencv_java480.dll");
+
+ System.loadLibrary("opencv_java480");
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ public static Mat base642Mat(String base64Image) throws IOException {
+ // 解码 Base64 字符串
+ byte[] imageBytes = Base64.getDecoder().decode(base64Image);
+
+ // 将字节数组转换为 BufferedImage
+ BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(imageBytes));
+
+ // 转换 BufferedImage 为 Mat
+ int width = bufferedImage.getWidth();
+ int height = bufferedImage.getHeight();
+ Mat mat = new Mat(height, width, CvType.CV_8UC3);
+
+ // 读取 BufferedImage 数据到 Mat
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int rgb = bufferedImage.getRGB(x, y);
+ byte blue = (byte) (rgb & 0xFF);
+ byte green = (byte) ((rgb >> 8) & 0xFF);
+ byte red = (byte) ((rgb >> 16) & 0xFF);
+ mat.put(y, x, new byte[]{blue, green, red});
+ }
+ }
+
+ return mat;
+ }
+
+
+ public static void main(String[] args) {
+ // 加载图像
+ // 创建 URL 对象
+ //特征图
+
+ Mat img2 = Imgcodecs.imread("D:\\WeChet\\WeChat Files\\wxid_ttkf0xgmyihv22\\FileStorage\\File\\2024-09\\aa20c309-7412-4392-88cd-aef08153f7c8 (2).jpeg");;//原图
+ Mat img1 = Imgcodecs.imread("D:\\WeChet\\WeChat Files\\wxid_ttkf0xgmyihv22\\FileStorage\\File\\2024-09\\aa20c309-7412-4392-88cd-aef08153f7c8.jpeg");
+
+ /* try {
+ img1 = base642Mat("data:image/png;base64,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".replace("data:image/jpeg;base64,","").replace("data:image/png;base64,",""));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }*/
+
+ // 生成特征图
+ Mat featureMap1 = generateFeatureMap(img1);
+ Imgcodecs.imwrite("D:\\WeChet\\WeChat Files\\wxid_ttkf0xgmyihv22\\FileStorage\\File\\2024-09\\feature_map1.jpg", featureMap1);
+
+ // 检查两张图片是否匹配
+ boolean isMatched = matchFeatures(img1, img2);
+ System.out.println("Images matched: " + isMatched);
+ //FeatureOrbLannbased(img1, img2);
+ matchTemplate(img1, img2,"D:\\WeChet\\WeChat Files\\wxid_ttkf0xgmyihv22\\FileStorage\\File\\2024-09\\2oooo.jpg");
+ // 裁剪图片
+// Mat croppedImage = cropImage(img1, 100, 100, 200, 200);
+// Imgcodecs.imwrite("E:\\m\\3.png", croppedImage);
+ }
+
+ // 生成特征图的方法
+ // 生成特征图的方法
+ public static Mat generateFeatureMap(Mat image) {
+ // 转换为灰度图
+ Mat gray = new Mat();
+ Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);
+
+ // 创建 ORB 检测器
+ ORB orb = ORB.create();
+
+ // 找到关键点
+ MatOfKeyPoint keypoints = new MatOfKeyPoint();
+ orb.detect(gray, keypoints);
+
+ // 在图像上画出关键点
+ Mat featureMap = drawKeypoints(image, keypoints);
+
+ return featureMap;
+ }
+
+ // 绘制关键点的方法
+ private static Mat drawKeypoints(Mat image, MatOfKeyPoint keypoints) {
+ Mat outputImage = image.clone(); // 创建一个输出图像的副本
+ for (KeyPoint kp : keypoints.toArray()) {
+ // 获取关键点的位置
+ double x = kp.pt.x;
+ double y = kp.pt.y;
+
+ // 绘制关键点
+ Imgproc.circle(outputImage, new org.opencv.core.Point(x, y), 3, new Scalar(0, 255, 0), -1);
+ }
+
+ return outputImage;
+ }
+
+ public static Mat FeatureOrbLannbased(Mat src, Mat dst) {
+ DescriptorMatcher Matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_L1);
+ // 转换为灰度图
+ Mat gray1 = new Mat();
+ Mat gray2 = new Mat();
+ Imgproc.cvtColor(src, gray1, Imgproc.COLOR_BGR2GRAY);
+ Imgproc.cvtColor(dst, gray2, Imgproc.COLOR_BGR2GRAY);
+
+ // 创建 ORB 检测器
+ ORB orb = ORB.create();
+
+ // 找到关键点
+ MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
+ MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
+ orb.detect(gray1, keypoints1);
+ orb.detect(gray2, keypoints2);
+
+ // 计算描述符
+ Mat descriptors1 = new Mat();
+ Mat descriptors2 = new Mat();
+ orb.compute(gray1, keypoints1, descriptors1);
+ orb.compute(gray2, keypoints2, descriptors2);
+
+ Features2d.drawKeypoints(src, keypoints1, src);
+
+ Features2d.drawKeypoints(dst, keypoints2, dst);
+
+
+ // Matching features
+
+ MatOfDMatch Matches = new MatOfDMatch();
+ Matcher.match(descriptors1, descriptors2, Matches);
+
+ double maxDist = Double.MIN_VALUE;
+ double minDist = Double.MAX_VALUE;
+
+ DMatch[] mats = Matches.toArray();
+ for (int i = 0; i < mats.length; i++) {
+ double dist = mats[i].distance;
+ if (dist < minDist) {
+ minDist = dist;
+ }
+ if (dist > maxDist) {
+ maxDist = dist;
+ }
+ }
+ System.out.println("Min Distance:" + minDist);
+ System.out.println("Max Distance:" + maxDist);
+ List goodMatch = new LinkedList<>();
+
+ for (int i = 0; i < mats.length; i++) {
+ double dist = mats[i].distance;
+ if (dist < 3 * minDist && dist < 0.2f) {
+ goodMatch.add(mats[i]);
+ }
+ }
+
+ Matches.fromList(goodMatch);
+ // Show result
+ Mat OutImage = new Mat();
+ Features2d.drawMatches(src, keypoints1, dst, keypoints2, Matches, OutImage);
+
+ Imgcodecs.imwrite("E:\\m\\qqqY4.jpg", OutImage);
+ return OutImage;
+ }
+
+ public static boolean matchTemplate(List templates, String srcImage) {
+ String srcImgRest = srcImage;
+ boolean macthBoolean = false;
+ for(String template: templates){
+ if(FeatureMatchingExample.matchTemplate( Imgcodecs.imread(template), Imgcodecs.imread(srcImgRest), srcImage+".jpg")){
+ //识别成功后之后识别根据结果图识别
+
+ srcImgRest = srcImage+".jpg";
+
+ macthBoolean = true;
+ break;
+ }
+ }
+ return macthBoolean;
+ }
+ /**
+ * 模板匹配
+ * @param template 模板
+ * @param image 图
+ * @return
+ */
+ public static boolean matchTemplate(Mat template, Mat image,String path) {
+ // Convert the images to grayscale
+ Mat grayImage = new Mat();
+ Mat grayTemplate = new Mat();
+ Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
+ Imgproc.cvtColor(template, grayTemplate, Imgproc.COLOR_BGR2GRAY);
+
+ // 执行模板匹配
+ Mat result = new Mat();
+ Imgproc.matchTemplate(grayImage, grayTemplate, result, Imgproc.TM_CCOEFF_NORMED);
+
+ // 检测匹配的阈值
+ double threshold = 0.6; // 根据需要调整此阈值
+ Mat mask = new Mat();
+ Core.compare(result, new Scalar(threshold), mask, Core.CMP_GT);
+
+ // 查找匹配值高于阈值的位置
+ MatOfPoint loc = new MatOfPoint();
+ Core.findNonZero(mask, loc);
+
+ // Draw rectangles around the detected features
+ for (Point p : loc.toArray()) {
+ Rect rect = new Rect(p, new Size(template.cols(), template.rows()));
+ Imgproc.rectangle(image, rect.tl(), rect.br(), new Scalar(0, 255, 0), 1);
+ }
+
+ // 保存图片
+ Imgcodecs.imwrite(path, image);
+ if (loc.toArray().length > 0) {
+ System.out.println("Template found in the image.");
+ return true;
+ } else {
+ System.out.println("Template not found in the image.");
+ return false;
+ }
+ }
+ // 识别两个图片是否一致的方法
+ public static boolean matchFeatures(Mat img1, Mat img2) {
+ // 转换为灰度图
+ Mat gray1 = new Mat();
+ Mat gray2 = new Mat();
+ Imgproc.cvtColor(img1, gray1, Imgproc.COLOR_BGR2GRAY);
+ Imgproc.cvtColor(img2, gray2, Imgproc.COLOR_BGR2GRAY);
+
+ // 创建 ORB 检测器
+ ORB orb = ORB.create();
+
+ // 找到关键点
+ MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
+ MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
+ orb.detect(gray1, keypoints1);
+ orb.detect(gray2, keypoints2);
+
+ // 计算描述符
+ Mat descriptors1 = new Mat();
+ Mat descriptors2 = new Mat();
+ orb.compute(gray1, keypoints1, descriptors1);
+ orb.compute(gray2, keypoints2, descriptors2);
+
+ // 创建匹配器
+ DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
+
+ // 匹配描述符
+ MatOfDMatch matches = new MatOfDMatch();
+ matcher.match(descriptors1, descriptors2, matches);
+
+ // 设置阈值以判断匹配是否成功
+ int threshold = 100; // 可以根据实际情况调整
+ boolean l =true;
+ return l;
+ }
+
+ // 图片裁剪的方法
+ public static Mat cropImage(Mat image, int x, int y, int width, int height) {
+ // 裁剪图像
+ Mat croppedImage = image.submat(y, y + height, x, x + width);
+
+ return croppedImage;
+ }
+}
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
new file mode 100644
index 0000000..f8ec069
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/algorithm/InventoryService.java
@@ -0,0 +1,215 @@
+package com.zhehekeji.web.service.algorithm;
+
+import com.zhehekeji.web.config.ConfigProperties;
+import com.zhehekeji.web.entity.CheckLog;
+import com.zhehekeji.web.entity.Stock;
+import com.zhehekeji.web.entity.Street;
+import com.zhehekeji.web.mapper.CheckLogMapper;
+import com.zhehekeji.web.mapper.StockMapper;
+import com.zhehekeji.web.service.IndustrialCamera.CameraSaveUtil;
+import com.zhehekeji.web.service.IndustrialCamera.LxPointCloudSaveImage;
+import com.zhehekeji.web.service.PlcService;
+import com.zhehekeji.web.service.StreetService;
+import com.zhehekeji.web.service.client.TransmissionPojo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+@Slf4j
+public class InventoryService {
+ @Resource
+ StreetService streetService;
+
+ @Resource
+ CameraSaveUtil cameraSaveUtil;
+
+ @Resource
+ ConfigProperties configProperties;
+
+ @Resource
+ CheckLogMapper checkLogMapper;
+
+
+ @Resource
+ StockMapper stockMapper;
+ @Resource
+ PlcService plcService;
+
+
+ public boolean match2D(String category,String path){
+ boolean flag = false;
+
+ //2d拍照并识别
+ cameraSaveUtil.saveImage(configProperties.getCameraConfig().getIndustrialCamera(),path,"sn");
+ log.info(configProperties.getCameraConfig().getIndustrialCamera()+" 2d拍照路径:"+path);
+ if(category!=null && !category.equals("")) {
+ flag = FeatureMatchingExample.matchTemplate(
+ readImagesInFolder(configProperties.getSavePath().getMediaPath() + "template/" + category),
+ path );
+ log.info("2d识别结果:"+flag);
+
+ }else {
+
+ log.info("未识别到任何模板");
+ }
+ return flag;
+ }
+
+ public int match3D(String category,String path){
+ LocalDate currentDate = LocalDate.now();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ Boolean flag = false;
+
+ //拍照
+ log.info("3D拍照pcd: "+path);
+ LxPointCloudSaveImage.saveImage(configProperties.getCameraConfig().getCamera3D(), path,1);
+ return 30;
+ }
+//
+//
+// public boolean matchFeatures(TransmissionPojo transmissionPojo){
+// Street street = streetService.getStreetByPlcId(transmissionPojo.getStreetNumber());
+//
+// LocalDate currentDate = LocalDate.now();
+// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+// //路径
+// String path = configProperties.getSavePath().getMediaPath()
+// +"industrialCamera/"
+// +transmissionPojo.getStreetNumber()+"/"
+// +currentDate.format(formatter)+"/"
+// +transmissionPojo.getRow()+"-"
+// +transmissionPojo.getColumn()+"-"
+// +transmissionPojo.getDirection()+"-";
+// //结果
+// transmissionPojo.setResult(1);
+// //2d拍照并识别
+// if(street != null&& street.getIndustrialCamera()!=null){
+// cameraSaveUtil.saveImage(street.getIndustrialCamera(),path+street.getIndustrialCamera()+".jpeg","sn");
+// log.info(street.getIndustrialCamera()+" 2d拍照路径:"+path+street.getIndustrialCamera()+".jpeg");
+// if(transmissionPojo.getCategory()!=null && !transmissionPojo.getCategory().equals("")) {
+// boolean re = FeatureMatchingExample.matchTemplate(
+// readImagesInFolder(configProperties.getSavePath().getMediaPath() + "template/" + transmissionPojo.getCategory()),
+// path + street.getIndustrialCamera() + ".jpeg");
+// log.info("2d识别结果:"+re);
+// if (!re) {
+// transmissionPojo.setResult(0);
+// }
+// }else {
+// transmissionPojo.setResult(0);
+// log.info("未识别到任何模板");
+// }
+// }
+// //3d拍照盘点
+// //盘点地址
+// String pcdPath1 =configProperties.getSavePath().getMediaPath() + street.getLeft3D()+ "/"+currentDate.format(formatter)+"/"+transmissionPojo.getCheckId()+".pcd";
+// String pcdPath2 =configProperties.getSavePath().getMediaPath() + street.getRight3D()+ "/"+ currentDate.format(formatter)+"/"+transmissionPojo.getCheckId()+".pcd";
+// //随行地址
+// //随行地址
+// String pcdPath3 =configProperties.getSavePath().getMediaPath() + street.getLeft3D()+"\\000\\"
+// +transmissionPojo.getRow()+"-"+transmissionPojo.getColumn()+"-"+transmissionPojo.getDirection()+"-L"+".pcd";
+//
+// String pcdPath4 =configProperties.getSavePath().getMediaPath() + street.getRight3D()+"\\000\\"
+// +transmissionPojo.getRow()+"-"+transmissionPojo.getColumn()+"-"+transmissionPojo.getDirection()+"-R"+".pcd";
+//
+// //拍照
+// log.info("3D拍照 左侧pcd: "+pcdPath1+" 右侧pcd:: "+pcdPath2);
+//
+// LxPointCloudSaveImage.saveImage(street.getLeft3D(), pcdPath1,1);
+//
+// LxPointCloudSaveImage.saveImage(street.getRight3D(), pcdPath2,2);
+// //左侧比较
+// PcdPojo pojo = new PcdPojo();
+// pojo.setPcd1(pcdPath1);
+// pojo.setPcd2(pcdPath3);
+// pojo.setConfigPath("D://config//3DConfig/"+street.getLeft3D()+".json");
+// log.info("对比左侧:"+pojo.toString());
+//
+// double leftScore = PointCloudProcessor.similarity(pojo,10);
+// log.info("3D leftScore:"+leftScore);
+//
+// //右侧比较
+// pojo.setPcd1(pcdPath2);
+// pojo.setPcd2(pcdPath4);
+// pojo.setConfigPath("D://config//3DConfig/"+street.getRight3D()+".json");
+// log.info("对比右侧:"+pojo.toString());
+//
+// double rightScore = PointCloudProcessor.similarity( pojo,10);
+//
+// log.info("3D rightScore:"+rightScore);
+// if(leftScore<0.87||rightScore<0.87){
+// transmissionPojo.setResult(0);
+// }
+// log.info("最终结果:"+transmissionPojo.getResult());
+// CheckLog checkLog = checkLogMapper.selectById(transmissionPojo.getCheckId());
+// plcService.visualCalculationResults(transmissionPojo,checkLog);
+//
+// Stock stock = stockMapper.getByStreetAndDirectionAndSideAndRowColumn(street.getId(), transmissionPojo.getDirection(),1, transmissionPojo.getRow(), transmissionPojo.getColumn());
+//
+// checkLogMapper.updateById(checkLog);
+// if (stock == null) {
+// stock = Stock.builder()
+// .streetId(street.getId())
+// .row(transmissionPojo.getRow())
+// .column(transmissionPojo.getColumn())
+// .direction(transmissionPojo.getDirection())
+// .side(1)
+// .statusVision(checkLog.getStatusVision())
+// .count(checkLog.getCount())
+// .category(checkLog.getCategory())
+// .exportTime(LocalDateTime.now()).build();
+// stockMapper.insert(stock);
+// } else {
+// stock.setStatusVision(checkLog.getStatusVision());
+// stock.setCount(checkLog.getCount());
+// stock.setCategory(checkLog.getCategory());
+// stockMapper.updateById(stock);
+// }
+// return !(transmissionPojo.getResult()==0);
+//
+// };
+//
+//
+//
+
+
+ /**
+ * 获取文件类型
+ * @param folderPath
+ * @return
+ */
+ public static List readImagesInFolder(String folderPath) {
+ File folder = new File(folderPath);
+ List list = new ArrayList<>();
+ // Check if the folder exists and is a directory
+ if (!folder.exists() || !folder.isDirectory()) {
+ log.info(folderPath);
+ log.info("The specified path is not a valid directory.");
+ return list;
+ }
+ // List all files in the folder that match the image filter
+ File[] imageFiles = folder.listFiles();
+ for(File imageFile : imageFiles){
+ if(!imageFile.isDirectory()) {
+ list.add(imageFile.getAbsolutePath());
+ log.info("Found image: " + imageFile.getAbsolutePath());
+ }
+ log.info("Found file: " + imageFile.getAbsolutePath());
+ }
+
+
+ return list;
+ }
+
+ public static void main(String[] args) {
+
+
+ }
+}
diff --git a/web/src/main/java/com/zhehekeji/web/service/algorithm/PCDReader.java b/web/src/main/java/com/zhehekeji/web/service/algorithm/PCDReader.java
new file mode 100644
index 0000000..9fd455c
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/algorithm/PCDReader.java
@@ -0,0 +1,77 @@
+package com.zhehekeji.web.service.algorithm;
+
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class PCDReader {
+
+ public static void main(String[] args) {
+ String filePath = "path/to/your/file.pcd";
+ try {
+ if (isBinaryFormat(filePath)) {
+ readBinaryPCD(filePath);
+ } else {
+ readAsciiPCD(filePath);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static boolean isBinaryFormat(String filePath) throws IOException {
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("DATA")) {
+ return line.contains("binary");
+ }
+ }
+ }
+ return false;
+ }
+
+ private static void readAsciiPCD(String filePath) throws IOException {
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.startsWith("#") && !line.startsWith("FIELDS") && !line.startsWith("SIZE") &&
+ !line.startsWith("TYPE") && !line.startsWith("COUNT") && !line.startsWith("WIDTH") &&
+ !line.startsWith("HEIGHT") && !line.startsWith("VIEWPOINT") && !line.startsWith("POINTS") &&
+ !line.startsWith("DATA")) {
+ String[] values = line.split(" ");
+ for (String value : values) {
+ System.out.println(value);
+ }
+ }
+ }
+ }
+ }
+
+ private static void readBinaryPCD(String filePath) throws IOException {
+ try (FileInputStream fis = new FileInputStream(filePath)) {
+ ByteBuffer buffer = ByteBuffer.wrap(fis.readAllBytes());
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ // Skip header (assuming 11 lines of header; adjust if needed)
+ for (int i = 0; i < 11; i++) {
+ while (buffer.get() != '\n') {
+ // Skip header lines
+ }
+ }
+ // Read binary data (assuming float x, y, z and int rgb)
+ while (buffer.hasRemaining()) {
+ float x = buffer.getFloat();
+ float y = buffer.getFloat();
+ float z = buffer.getFloat();
+ int rgb = buffer.getInt(); // Read 4 bytes for RGB
+ System.out.printf("x: %f, y: %f, z: %f, rgb: %d%n", x, y, z, rgb);
+ }
+ }
+ }
+
+
+}
diff --git a/web/src/main/java/com/zhehekeji/web/service/algorithm/PcdPojo.java b/web/src/main/java/com/zhehekeji/web/service/algorithm/PcdPojo.java
new file mode 100644
index 0000000..06c211a
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/algorithm/PcdPojo.java
@@ -0,0 +1,42 @@
+package com.zhehekeji.web.service.algorithm;
+
+import lombok.Data;
+
+@Data
+public class PcdPojo {
+
+ double[][] rotationMatrix = {
+ {0.9982215762138367, 0.057795289903879166, 0.014606773853302002},
+ {-0.0510917492210865, 0.7032182216644287, 0.7091360092163086},
+ {0.030712969601154327, -0.7086211442947388, 0.7049204111099243}
+ }; // 这里使用给定的旋转矩阵
+ double[] minBounds = {-905.5771484375, 174.71572875976562, 69.14165496826172}; // 裁剪最小值
+ double[] maxBounds = {321.80609130859375, 1170.4776611328125, 1199.3507080078125}; // 裁剪最大值
+
+
+ private double floorHeight;
+
+ private double[] max_pt;
+ private double[] min_pt;
+ private double[] rotation;
+ //路径D://config//3DConfig/"+相机sn+".json"
+ private String configPath;
+ //盘点路径 configProperties.getSavePath().getMediaPath() + street.getLeft3D()+ currentDate.format(formatter)+"/"+transmissionPojo.getCheckId()+".pcd";
+ //随行路径 configProperties.getSavePath().getMediaPath() + street.getLeft3D()+"000/"+ currentDate.format(formatter)+"/"+transmissionPojo.getRow()+".pcd";
+ private String pcd1;
+ private String pcd2;
+
+ private String cameraSn1;
+ private String cameraSn2;
+ private Integer count;
+ PcdPojo setPCDInfo(PcdPojo pojo) {
+ this.setMinBounds(pojo.getMin_pt());
+ this.setMaxBounds(pojo.getMax_pt());
+ double[] x = {pojo.getRotation()[0], pojo.getRotation()[1], pojo.getRotation()[2]};
+ double[] y = {pojo.getRotation()[3], pojo.getRotation()[4], pojo.getRotation()[5]};
+ double[] z = {pojo.getRotation()[6], pojo.getRotation()[7], pojo.getRotation()[8]};
+ this.setRotationMatrix(new double[][]{x, y, z});
+ return this;
+ }
+
+}
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
new file mode 100644
index 0000000..1520bb4
--- /dev/null
+++ b/web/src/main/java/com/zhehekeji/web/service/algorithm/PointCloudProcessor.java
@@ -0,0 +1,388 @@
+package com.zhehekeji.web.service.algorithm;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class PointCloudProcessor {
+
+
+ public static void main(String[] args) {
+ PcdPojo pojo = new PcdPojo();
+ pojo.setPcd1("D:\\WeChet\\WeChat Files\\wxid_ttkf0xgmyihv22\\FileStorage\\File\\2024-09\\8-6-2-L.pcd");
+ pojo.setPcd2("D:\\WeChet\\WeChat Files\\wxid_ttkf0xgmyihv22\\FileStorage\\File\\2024-09\\369.pcd");
+ pojo.setConfigPath("E:\\D91FCF1FAB3568DB.json");
+ double i = similarity(pojo,10);
+ System.out.println("111 "+i);
+ }
+
+ /**
+ * 计算两个pcd的相似性
+ *
+ * @param pojo 必须有两个pcd文件路径,和一个配置文件
+ * @return
+ */
+ //计算两个pcd的相似性
+ public static double similarity(PcdPojo pojo) {
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ try {
+ // 读取 JSON 文件并转换为 配置信息
+ PcdPojo pojoIn = mapper.readValue(new File(pojo.getConfigPath()), mapper.getTypeFactory().constructType(PcdPojo.class));
+
+
+ // 打印转换后的实体类列表
+ pojo = pojo.setPCDInfo(pojoIn);
+
+ //报错则认为没有配置文件,返回盘点失败
+ } catch (JsonMappingException e) {
+ e.printStackTrace();
+ return 0;
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ return 0;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return 0;
+ }
+ System.out.println(pojo.toString());
+ // 计算pcd1
+ List points = readPCD(pojo.getPcd1());
+ if (points == null || points.isEmpty()) {
+ return 0;
+ }
+
+ // 创建一个DecimalFormat对象,指定格式
+ DecimalFormat df = new DecimalFormat("#.#");
+ //map xyz
+ PcdPojo finalPojo = pojo;
+ Map> clippedPoint = points.stream()
+ .map(point -> {
+ //旋转
+ return rotatePoints(point, finalPojo.getRotationMatrix());
+
+ })
+ .filter(point ->
+ //截取
+ clipPoints(point, finalPojo.getMinBounds(), finalPojo.getMaxBounds())
+ )//z轴截取
+ .collect(Collectors.groupingBy(point -> point[0], Collectors.toMap(point -> point[1], point -> point[2], (a, b) -> {
+ return a > b ? a : b;
+ })));
+ //计算x,y轴平均值
+ Double xAge = clippedPoint.keySet().stream().collect(Collectors.averagingDouble(v -> v));
+ Double yAge = clippedPoint.values().stream().flatMap(map -> map.values().stream()).collect(Collectors.averagingDouble(v -> v));
+
+ //计算pcd2
+ List points2 = readPCD(pojo.getPcd2());
+ if (points2 == null || points2.isEmpty()) {
+ return 0;
+ }
+
+ //map xyz
+ Map> clippedPoint2 = points2.stream()
+ .map(point -> {
+ //旋转
+ return rotatePoints(point, finalPojo.getRotationMatrix());
+
+ })
+ .filter(point ->
+ //截取
+ clipPoints(point, finalPojo.getMinBounds(), finalPojo.getMaxBounds())
+ )//z轴截取
+ .collect(Collectors.groupingBy(point -> point[0], Collectors.toMap(point -> point[1], point -> point[2], (a, b) -> {
+ return a > b ? a : b;
+ })));
+
+ //计算x,y轴平均值
+ Double xAge2 = clippedPoint2.keySet().stream().collect(Collectors.averagingDouble(v -> v));
+ Double yAge2 = clippedPoint2.values().stream().flatMap(map -> map.values().stream()).collect(Collectors.averagingDouble(v -> v));
+ //计算相似度
+ return (Math.abs(yAge) < Math.abs(yAge2) ? Math.abs(yAge) / Math.abs(yAge2) : Math.abs(yAge2) / Math.abs(yAge));
+ }
+
+ public static double similarity(PcdPojo pojo,int i) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ // 读取 JSON 文件并转换为 配置信息
+ PcdPojo pojoIn = mapper.readValue(new File(pojo.getConfigPath()), mapper.getTypeFactory().constructType(PcdPojo.class));
+ // 打印转换后的实体类列表
+ pojo = pojo.setPCDInfo(pojoIn);
+ //报错则认为没有配置文件,返回盘点失败
+ } catch (JsonMappingException e) {
+ e.printStackTrace();
+ return 0;
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ return 0;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return 0;
+ }
+ System.out.println(pojo.toString());
+ // 计算pcd1
+ List points = readPCD(pojo.getPcd1());
+ if (points == null || points.isEmpty()) {
+ return 0;
+ }
+
+ //计算pcd2
+ List points2 = readPCD(pojo.getPcd2());
+ if (points2 == null || points2.isEmpty()) {
+ return 0;
+ }
+
+ //计算相似度
+ return calculateTruncatedMeans(points,points2,pojo,i,new ArrayList<>());
+ }
+
+
+ /**
+ * 计算每个子区域内的 y 坐标的截断均值
+ *
+ * @param pointCloud 点云列表
+ * @param divisions 每边分割的数量
+ * @return 一个列表,其中每个元素是一个长度为3的一维数组,表示每个子区域的左下角坐标、右上角坐标和该区域内的 y 坐标的截断均值
+ */
+ public static double calculateTruncatedMeans(List pointCloud, List pointCloud2,PcdPojo pojo, int divisions,List results) {
+
+ double width = (pojo.getMaxBounds()[0] - pojo.getMinBounds()[0]) / divisions; // 每个小方块的宽度
+ double depth = (pojo.getMaxBounds()[2] - pojo.getMinBounds()[2]) / divisions; // 每个小方块的深度
+ int count = 0;
+ int countSum = 0;
+ for (int i = 0; i < divisions; i++) {
+ for (int j = 0; j < divisions; j++) {
+ double leftBottomX = pojo.getMinBounds()[0] + i * width; // 左下角 x 坐标
+ double leftBottomZ = pojo.getMinBounds()[2] + j * depth; // 左下角 z 坐标
+ double rightTopX = pojo.getMinBounds()[0] + (i + 1) * width; // 右上角 x 坐标
+ double rightTopZ = pojo.getMinBounds()[2] + (j + 1) * depth; // 右上角 z 坐标
+
+ List yValues = new ArrayList<>();
+ for (double[] point : pointCloud) {
+ point = rotatePoints(point, pojo.getRotationMatrix());
+ if(clipPoints(point, pojo.getMinBounds(), pojo.getMaxBounds())) {
+ if (point[0] >= leftBottomX && point[0] < rightTopX &&
+ point[2] >= leftBottomZ && point[2] < rightTopZ) {
+ yValues.add(point[1]);
+ }
+ }
+ }
+
+ List yValues2 = new ArrayList<>();
+ for (double[] point : pointCloud2) {
+ point = rotatePoints(point, pojo.getRotationMatrix());
+ if(clipPoints(point, pojo.getMinBounds(), pojo.getMaxBounds())) {
+ if (point[0] >= leftBottomX && point[0] < rightTopX &&
+ point[2] >= leftBottomZ && point[2] < rightTopZ) {
+ yValues2.add(point[1]);
+ }
+ }
+ }
+ double truncatedMean = calculateTruncatedMean(yValues, 0.1); // 截断比例为10%
+ double truncatedMean2 = calculateTruncatedMean(yValues2, 0.1); // 截断比例为10%
+ double similarity = (truncatedMean - pojo.getMinBounds()[1])/(pojo.getMaxBounds()[1] - pojo.getMinBounds()[1]);
+ double similarity2 = (truncatedMean2 - pojo.getMinBounds()[1])/(pojo.getMaxBounds()[1] - pojo.getMinBounds()[1]);
+ countSum++;
+ if(Double.isNaN(similarity) && Double.isNaN(similarity2)){
+ break;
+ }
+ System.out.println("similarity:"+similarity+"similarity2:"+similarity2);
+ double max = Math.max(similarity, similarity2); // 获取较大数
+ double min = Math.min(similarity, similarity2); // 获取较小数
+ if(max-min<0.01){
+ count++;
+ }
+ results.add(new double[]{leftBottomX, leftBottomZ, truncatedMean, truncatedMean2});
+ }
+ }
+ System.out.println(count);
+ pojo.setCount(count);
+ return (double) count /countSum;
+ }
+
+
+ /**
+ * 计算截断均值
+ *
+ * @param values 值列表
+ * @param trimRatio 截断比例
+ * @return 截断均值
+ */
+ private static double calculateTruncatedMean(List values, double trimRatio) {
+ int trimCount = (int) Math.ceil(values.size() * trimRatio);
+ values.sort(Comparator.naturalOrder());
+ double sum = 0;
+ for (int i = trimCount; i < values.size() - trimCount; i++) {
+ sum += values.get(i);
+ }
+ return sum / (values.size() - 2 * trimCount);
+ }
+
+ public static List readPCD(String filePath) {
+ try {
+ if (isBinaryFormat(filePath)) {
+ return readBinaryPCD(filePath);
+ } else {
+ return readAsciiPCD(filePath);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+
+ }
+
+ private static boolean isBinaryFormat(String filePath) throws IOException {
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("DATA")) {
+ return line.contains("binary");
+ }
+ }
+ }
+ return false;
+ }
+
+ private static List readAsciiPCD(String filePath) throws IOException {
+ List points = new ArrayList<>();
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("DATA")) {
+ break;
+ }
+ }
+ while ((line = reader.readLine()) != null) {
+ double[] point = new double[3];
+ String[] values = line.split(" ");
+ point[0] = Double.parseDouble(values[0]);
+ point[1] = Double.parseDouble(values[1]);
+ point[2] = Double.parseDouble(values[2]);
+ points.add(point);
+
+ }
+ }
+ return points;
+ }
+
+ private static List readBinaryPCD(String filePath) throws IOException {
+ List points = new ArrayList<>();
+ try (FileInputStream fis = new FileInputStream(filePath)) {
+ ByteBuffer buffer = ByteBuffer.wrap(fis.readAllBytes());
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ // Skip header (assuming header size is known, or read until DATA section)
+ // This example assumes 11 lines of header, which may vary
+ for (int i = 0; i < 11; i++) {
+ while (buffer.get() != '\n') {
+ // Skip header lines
+ }
+ }
+ // Read binary data
+ while (buffer.hasRemaining()) {
+ double x = buffer.getFloat();
+ double y = buffer.getFloat();
+ double z = buffer.getFloat();
+ int rgb = buffer.getInt(); // Read 4 bytes for RGB
+ double[] point = {x, y, z};
+ points.add(point);
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ return points;
+ }
+
+ static void getPCDArray(String filePath) {
+
+ }
+
+ static void getPCDArray(Map> clippedPoint, String filePath) {
+
+ // 创建一个DecimalFormat对象,指定格式
+ DecimalFormat df = new DecimalFormat("#.####");
+ File file = new File(filePath + ".txt");
+ file.delete();
+ try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath + ".txt"))) {
+ bw.write("[");
+ for (Double x : clippedPoint.keySet()) {
+ for (Double y : clippedPoint.get(x).keySet()) {
+ double z = clippedPoint.get(x).get(y);
+ bw.write("[" + df.format(x) + ", " + df.format(y) + ", " + df.format(z) + "],\n");
+ }
+ }
+ bw.write("]");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ // 读取点云数据
+
+
+ }
+
+ public static double[] rotatePoints(double[] point, double[][] rotationMatrix) {
+
+ double x = point[0];
+ double y = point[1];
+ double z = point[2];
+
+ double[] rotatedPoint = new double[3];
+ rotatedPoint[0] = rotationMatrix[0][0] * x + rotationMatrix[0][1] * y + rotationMatrix[0][2] * z;
+ rotatedPoint[1] = rotationMatrix[1][0] * x + rotationMatrix[1][1] * y + rotationMatrix[1][2] * z;
+ rotatedPoint[2] = rotationMatrix[2][0] * x + rotationMatrix[2][1] * y + rotationMatrix[2][2] * z;
+
+ return rotatedPoint;
+ }
+
+ public static List clipPoints(List points, double[] minBounds, double[] maxBounds) {
+ List clippedPoints = new ArrayList<>();
+ for (double[] point : points) {
+ if (point[0] >= minBounds[0] && point[0] <= maxBounds[0] &&
+ point[1] >= minBounds[1] && point[1] <= maxBounds[1] &&
+ point[2] >= minBounds[2] && point[2] <= maxBounds[2]) {
+ clippedPoints.add(point);
+ }
+ }
+ return clippedPoints;
+ }
+
+ public static boolean clipPoints(double[] point, double[] minBounds, double[] maxBounds) {
+
+ return point[0] >= minBounds[0] && point[0] <= maxBounds[0] && //x轴截取
+ point[1] >= minBounds[1] && point[1] <= maxBounds[1] && //y轴截取
+ point[2] >= minBounds[2] && point[2] <= maxBounds[2];
+ }
+
+ public static void writePCD(List points, String filePath) {
+ try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath))) {
+ bw.write("# .PCD v0.7 - Point Cloud Data file format\n");
+ bw.write("VERSION 0.7\n");
+ bw.write("FIELDS x y z\n");
+ bw.write("SIZE 4 4 4\n");
+ bw.write("TYPE F F F\n");
+ bw.write("COUNT 1 1 1\n");
+ bw.write("WIDTH " + points.size() + "\n");
+ bw.write("HEIGHT 1\n");
+ bw.write("VIEWPOINT 0 0 0 1 0 0 0\n");
+ bw.write("PIXEL_FORMAT ascii\n");
+ bw.write("DATA ascii\n");
+ for (double[] point : points) {
+ bw.write(point[0] + " " + point[1] + " " + point[2] + "\n");
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/web/src/main/java/com/zhehekeji/web/service/client/TransmissionPojo.java b/web/src/main/java/com/zhehekeji/web/service/client/TransmissionPojo.java
index 77d7148..3279966 100644
--- a/web/src/main/java/com/zhehekeji/web/service/client/TransmissionPojo.java
+++ b/web/src/main/java/com/zhehekeji/web/service/client/TransmissionPojo.java
@@ -72,6 +72,7 @@ public class TransmissionPojo {
this.checkId = convertNumberToFixedLengthChars(row, 3) + convertNumberToFixedLengthChars(column, 3);
}
+ //输出
public String toString(TransmissionType type) {
this.header = type.toString();
if (type == TransmissionType.ST) {
diff --git a/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDecoder.java b/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDecoder.java
index f593257..96b79c4 100644
--- a/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDecoder.java
+++ b/web/src/main/java/com/zhehekeji/web/service/ksec/KsecDecoder.java
@@ -214,7 +214,7 @@ public class KsecDecoder extends DelimiterBasedFrameDecoder {
log.info("盘点结束:"+ksecInfo.getData().toString());
}else if (Cmd.SC.name().equals(ksecInfo.getType())) {
- //plcService.checkVision();
+ plcService.checkVision(dataInfo);
}
//找到该货位的最后一张照片与现在的照片比照
//plcService.recordStock(plcCmdInfo, dataInfo.getCode(), 0, 0);
diff --git a/web/src/main/resources/application-prod.yml b/web/src/main/resources/application-prod.yml
index f509222..acdf228 100644
--- a/web/src/main/resources/application-prod.yml
+++ b/web/src/main/resources/application-prod.yml
@@ -51,6 +51,10 @@ cameraConfig:
# 利珀延迟10s就可
# 单位毫秒
delayDownloadMp4: 10000
+ #工业相机编码sn
+ industrialCamera: F223
+ #camera3D(ip来标注
+ camera3D: 114
# ------------
# -----图片 mp4下载地址
savePath:
@@ -95,7 +99,15 @@ scanCodeMode:
- 14
# 照片 視頻保存多久
deleteFileDays: 30
-
+#灵闪tcp服务地址
intelliBlink:
ip: 127.0.0.1
port: 3002
+
+
+#模版信息
+template:
+ - id: 1
+ name: "模板一"
+ code: "a1123"
+ -
diff --git a/web/src/main/resources/libs/MvCameraControlWrapper.jar b/web/src/main/resources/libs/MvCameraControlWrapper.jar
new file mode 100644
index 0000000..3e2f591
Binary files /dev/null and b/web/src/main/resources/libs/MvCameraControlWrapper.jar differ
diff --git a/web/src/main/resources/libs/opencv-480.jar b/web/src/main/resources/libs/opencv-480.jar
new file mode 100644
index 0000000..71a14d3
Binary files /dev/null and b/web/src/main/resources/libs/opencv-480.jar differ
diff --git a/web/src/main/resources/libs/opencv_java480.dll b/web/src/main/resources/libs/opencv_java480.dll
new file mode 100644
index 0000000..42f7a47
Binary files /dev/null and b/web/src/main/resources/libs/opencv_java480.dll differ