diff --git a/build.gradle b/build.gradle index 667abe7..057e27d 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ dependencies { implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0' implementation 'com.fazecast:jSerialComm:2.11.0' implementation 'com.opencsv:opencsv:5.11' - implementation files('lib/modbus4j-3.1.0.jar') + implementation fileTree(dir: 'lib', include: '*.jar') compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/lib/opencv-420.jar b/lib/opencv-420.jar new file mode 100644 index 0000000..90f60bc Binary files /dev/null and b/lib/opencv-420.jar differ diff --git a/src/main/java/com/iflytop/gd/app/controller/PhotoController.java b/src/main/java/com/iflytop/gd/app/controller/PhotoController.java index 0bf30cb..6714f78 100644 --- a/src/main/java/com/iflytop/gd/app/controller/PhotoController.java +++ b/src/main/java/com/iflytop/gd/app/controller/PhotoController.java @@ -8,6 +8,7 @@ import com.iflytop.gd.app.service.api.PhotosService; import com.iflytop.gd.common.base.BasePageQuery; import com.iflytop.gd.common.result.PageResult; import com.iflytop.gd.common.result.Result; +import com.iflytop.gd.hardware.drivers.CameraBaslerDriver; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -25,7 +26,6 @@ import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @Slf4j public class PhotoController { - private final PhotosService photosService; @Operation(summary = "照片列表") @@ -40,7 +40,7 @@ public class PhotoController { @Operation(summary = "拍摄一张照片") @GetMapping("/take") public Result take() { - return Result.success("http://127.0.0.1/photos/temp/1.png"); + return Result.success(photosService.take()); } diff --git a/src/main/java/com/iflytop/gd/app/core/runner/BaslerCameraApplicationRunner.java b/src/main/java/com/iflytop/gd/app/core/runner/BaslerCameraApplicationRunner.java new file mode 100644 index 0000000..5c480c3 --- /dev/null +++ b/src/main/java/com/iflytop/gd/app/core/runner/BaslerCameraApplicationRunner.java @@ -0,0 +1,46 @@ +package com.iflytop.gd.app.core.runner; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Slf4j +@Order(2) +@Component +public class BaslerCameraApplicationRunner implements ApplicationRunner { + @Value("${opencv.library-path}") + private String opencvLibraryPath; + + @Value("${pylon.library-path}") + private String pylonLibraryPath; + + @Value("${pylon.wrapper-path}") + private String pylonWrapperPath; + + @Override + public void run(ApplicationArguments args) throws Exception { + try { + System.load(this.opencvLibraryPath); + log.info("成功加载OpenCV库: {}", this.opencvLibraryPath); + } catch (UnsatisfiedLinkError | SecurityException e) { + log.error("加载OpenCV库失败: {}", this.opencvLibraryPath, e); + } + + try { + System.load(this.pylonLibraryPath); + log.info("成功加载pylon库: {}", this.pylonLibraryPath); + } catch (UnsatisfiedLinkError | SecurityException e) { + log.error("加载pylon库失败: {}", this.pylonLibraryPath, e); + } + try { + System.load(this.pylonWrapperPath); + log.info("成功加载pylon wrapper库: {}", this.pylonWrapperPath); + } catch (UnsatisfiedLinkError | SecurityException e) { + log.error("加载pylon wrapper库失败: {}", this.pylonWrapperPath, e); + } + + } +} diff --git a/src/main/java/com/iflytop/gd/app/service/api/PhotosService.java b/src/main/java/com/iflytop/gd/app/service/api/PhotosService.java index 3d26e23..dd8f751 100644 --- a/src/main/java/com/iflytop/gd/app/service/api/PhotosService.java +++ b/src/main/java/com/iflytop/gd/app/service/api/PhotosService.java @@ -3,7 +3,9 @@ package com.iflytop.gd.app.service.api; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.iflytop.gd.app.mapper.PhotosMapper; import com.iflytop.gd.app.model.entity.Photos; +import com.iflytop.gd.hardware.drivers.CameraBaslerDriver; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; /** @@ -12,6 +14,19 @@ import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class PhotosService extends ServiceImpl { + private final CameraBaslerDriver driver; + @Value("${photo.url}") + private String url; + @Value("${photo.path}") + private String path; + + public String take() { + String filePath = path + "/temp/" + System.currentTimeMillis() + ".png"; + driver.enable(); + driver.saveColorImg(filePath); + driver.disable(); + return url + filePath; + } } diff --git a/src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper$GrabResult.class b/src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper$GrabResult.class new file mode 100644 index 0000000..581a7a5 Binary files /dev/null and b/src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper$GrabResult.class differ diff --git a/src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper.class b/src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper.class new file mode 100644 index 0000000..8fcbed0 Binary files /dev/null and b/src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper.class differ diff --git a/src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper.java b/src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper.java new file mode 100644 index 0000000..d57b4af --- /dev/null +++ b/src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper.java @@ -0,0 +1,130 @@ +package com.iflytop.gd.camera; + +public class BaslerCameraWrapper { + public static final int ACCESS_MODE_MONITOR = 0; + public static final int ACCESS_MODE_CONTROL = 1; + public static final int ACCESS_MODE_STREAM = (1 << 1); + public static final int ACCESS_MODE_EVENT = (1 << 2); + public static final int ACCESS_MODE_EXCLUSIVE = (1 << 3); + + public static class GrabResult { + public byte[] imageBuffer; + public int payloadType; + public int pixelType; + public int sizeX; + public int sizeY; + public int offsetX; + public int offsetY; + public int paddingX; + public int paddingY; + public long PayloadSize; + public int ErrorCode; + } + + /** + * Initializes the pylon runtime system. + */ + public native void initialize(); + + /** + * Enumerates all camera devices. + * @return The number of found devices. + */ + public native int enumerateDevices(); + + /** + * Terminates the pylon runtime system. + */ + public native void terminate(); + + /** + * Creates a camera device by index. + * @param index The index of the camera device. + * @return The handle of the camera device. + */ + public native long createDeviceByIndex(int index); + + /** + * Opens the camera device. + * @param hDev The handle of the camera device. + * @param accessMode The access mode. + */ + public native void deviceOpen(long hDev, int accessMode); + + /** + * Checks if a camera device feature is readable. + * @param hDev The handle of the camera device. + * @param name The name of the feature. + * @return true if the feature is readable, false otherwise. + */ + public native boolean deviceFeatureIsReadable(long hDev, String name); + + /** + * Read a camera device feature to a string. + * @param hDev The handle of the camera device. + * @param name The name of the feature. + * @param size The size of the string buffer. + * @return The string value of the feature. + */ + public native String deviceFeatureToString(long hDev, String name, int size); + + /** + * Checks if a camera device feature is available. + * @param hDev The handle of the camera device. + * @param name The name of the feature. + * @return true if the feature is available, false otherwise. + */ + public native boolean deviceFeatureIsAvailable(long hDev, String name); + + /** + * Writes a camera device feature from a string. + * @param hDev The handle of the camera device. + * @param name The name of the feature. + * @param value The value of the feature. + */ + public native void deviceFeatureFromString(long hDev, String name, String value); + + /** + * Checks if a camera device feature is writable. + * @param hDev The handle of the camera device. + * @param name The name of the feature. + * @return true if the feature is writable, false otherwise. + */ + public native boolean deviceFeatureIsWritable(long hDev, String name); + + /** + * Writes a camera device feature from an integer. + * @param hDev The handle of the camera device. + * @param name The name of the feature. + * @param value The value of the feature. + */ + public native void deviceSetIntegerFeature(long hDev, String name, int value); + + /** + * Reads a camera device feature to an integer. + * @param hDev The handle of the camera device. + * @param name The name of the feature. + * @return The integer value of the feature. + */ + public native int deviceGetIntegerFeatureInt32(long hDev, String name); + + /** + * Grabs a single frame from the camera device. + * @param hDev The handle of the camera device. + * @param channel The channel index. + * @return The grab result. + */ + public native GrabResult deviceGrabSingleFrame(long hDev, int channel); + + /** + * Closes the camera device. + * @param hDev The handle of the camera device. + */ + public native void deviceClose(long hDev); + + /** + * Destroys the camera device. + * @param hDev The handle of the camera device. + */ + public native void destroyDevice(long hDev); +} diff --git a/src/main/java/com/iflytop/gd/hardware/controller/BaslerCameraController.java b/src/main/java/com/iflytop/gd/hardware/controller/BaslerCameraController.java new file mode 100644 index 0000000..b5b2609 --- /dev/null +++ b/src/main/java/com/iflytop/gd/hardware/controller/BaslerCameraController.java @@ -0,0 +1,65 @@ +package com.iflytop.gd.hardware.controller; + +import com.iflytop.gd.hardware.drivers.CameraBaslerDriver; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.opencv.core.Mat; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@Tag(name = "Basler相机驱动控制器") +@RestController +@RequestMapping("/api/basler") +@RequiredArgsConstructor +public class BaslerCameraController { + + @Autowired + private CameraBaslerDriver driver; + + // 设置相机索引 + @PostMapping("/setIndex") + public ResponseEntity setIndex(@RequestParam Integer index) { + driver.setIndex(index); + log.info("设置相机索引为: {}", index); + return ResponseEntity.ok("设置相机索引为: " + index); + } + + // 使能相机 + @PostMapping("/enable") + public ResponseEntity enable() { + driver.enable(); + return ResponseEntity.ok("相机已使能"); + } + + // 关闭相机 + @PostMapping("/disable") + public ResponseEntity disable() { + driver.disable(); + return ResponseEntity.ok("相机已关闭"); + } + + // 抓取Mat对象(仅演示返回字符串,实际可自定义返回格式) + @GetMapping("/grabToMat") + public ResponseEntity grabToMat() { + Mat mat = driver.grabToMat(); + log.info("抓取Mat成功,尺寸: {}x{}", mat.rows(), mat.cols()); + return ResponseEntity.ok("抓取Mat成功,尺寸: " + mat.rows() + "x" + mat.cols()); + } + + // 保存图片 + @PostMapping("/saveImg") + public ResponseEntity saveImg(@RequestParam String filePath) { + driver.saveImg(filePath); + return ResponseEntity.ok("图片已保存到: " + filePath); + } + + // 保存彩色图片 + @PostMapping("/saveColorImg") + public ResponseEntity saveColorImg(@RequestParam String filePath) { + driver.saveColorImg(filePath); + return ResponseEntity.ok("彩色图片已保存到: " + filePath); + } +} diff --git a/src/main/java/com/iflytop/gd/hardware/drivers/CameraBaslerDriver.java b/src/main/java/com/iflytop/gd/hardware/drivers/CameraBaslerDriver.java new file mode 100644 index 0000000..69aedf5 --- /dev/null +++ b/src/main/java/com/iflytop/gd/hardware/drivers/CameraBaslerDriver.java @@ -0,0 +1,148 @@ +package com.iflytop.gd.hardware.drivers; + +import com.iflytop.gd.camera.BaslerCameraWrapper; +import lombok.extern.slf4j.Slf4j; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class CameraBaslerDriver { + // camera + private static BaslerCameraWrapper pylon = null; + // index + private Integer index = 0; + // channel + protected Integer channel = 0; + // camera handle + private long cam = -1; + + // 设置相机索引 + public void setIndex(Integer index) { + this.index = index; + log.info("设置相机索引为: {}", index); + } + + // get pylon + private BaslerCameraWrapper getPylon() { + if (null == CameraBaslerDriver.pylon) { + CameraBaslerDriver.pylon = new BaslerCameraWrapper(); + CameraBaslerDriver.pylon.initialize(); + log.info("Basler相机库初始化完成"); + } + return CameraBaslerDriver.pylon; + } + + // 使能相机 + public void enable() { + if (-1 != this.cam) { + log.info("相机已使能,句柄: {}", this.cam); + return; + } + try { + var pylon = this.getPylon(); + int count = pylon.enumerateDevices(); + if (this.index >= count) { + log.error("相机索引超出范围: {}", this.index); + throw new RuntimeException("相机索引超出范围: " + this.index); + } + this.cam = pylon.createDeviceByIndex(this.index); + pylon.deviceOpen(this.cam, BaslerCameraWrapper.ACCESS_MODE_CONTROL | BaslerCameraWrapper.ACCESS_MODE_STREAM); + + boolean isFeatureReadable = pylon.deviceFeatureIsReadable(cam, "DeviceModelName"); + if (isFeatureReadable) { + String name = pylon.deviceFeatureToString(cam, "DeviceModelName", 256); + log.info("相机型号: {}", name); + } + +// pylon.deviceFeatureFromString(cam, "PixelFormat", "Mono8"); + pylon.deviceFeatureFromString(cam, "PixelFormat", "BayerRG8"); + pylon.deviceFeatureFromString(cam, "TriggerSelector", "AcquisitionStart"); + pylon.deviceFeatureFromString(cam, "TriggerMode", "Off"); + pylon.deviceFeatureFromString(cam, "TriggerSelector", "FrameStart"); + pylon.deviceFeatureFromString(cam, "TriggerMode", "Off"); + pylon.deviceSetIntegerFeature(cam, "GevSCPSPacketSize", 1500); + pylon.deviceFeatureFromString(cam, "ExposureAuto", "Off"); + pylon.deviceSetIntegerFeature(cam, "ExposureTimeRaw", 27218); + + log.info("相机使能成功,句柄: {}", this.cam); + } catch (Exception e) { + log.error("相机使能失败: {}", e.getMessage(), e); + throw new RuntimeException("相机使能失败: " + e.getMessage(), e); + } + } + + // 关闭相机 + public void disable() { + if (-1 == this.cam) { + log.info("相机未使能,无需关闭"); + return; + } + var pylon = this.getPylon(); + pylon.deviceClose(this.cam); + log.info("相机关闭,句柄: {}", this.cam); + this.cam = -1; + } + + // 抓取图像为Mat + public Mat grabToMat() { + if (this.cam == -1) { + log.error("抓图失败:相机未使能"); + throw new RuntimeException("抓图失败:相机未使能"); + } + var pylon = this.getPylon(); + var result = pylon.deviceGrabSingleFrame(this.cam, this.channel); + if (result == null || result.imageBuffer == null) { + log.error("抓图失败:未获取到图像数据"); + throw new RuntimeException("抓图失败:未获取到图像数据"); + } + Mat frameMat = new Mat(result.sizeY, result.sizeX, CvType.CV_8UC1); + frameMat.put(0, 0, result.imageBuffer); + log.info("抓图成功,返回Mat对象"); + return frameMat; + } + + // 抓图并保存图片 + public void saveImg(String filePath) { + if (this.cam == -1) { + log.error("保存图片失败:相机未使能"); + throw new RuntimeException("保存图片失败:相机未使能"); + } + var pylon = this.getPylon(); + var result = pylon.deviceGrabSingleFrame(this.cam, this.channel); + if (result == null || result.imageBuffer == null) { + log.error("保存图片失败:未获取到图像数据"); + throw new RuntimeException("保存图片失败:未获取到图像数据"); + } + Mat frameMat = new Mat(result.sizeY, result.sizeX, CvType.CV_8UC1); + frameMat.put(0, 0, result.imageBuffer); + Imgcodecs.imwrite(filePath, frameMat); + log.info("图片已保存到: {}", filePath); + } + + // 抓图并保存彩色图片 + public void saveColorImg(String filePath) { + if (this.cam == -1) { + log.error("保存彩色图片失败:相机未使能"); + throw new RuntimeException("保存彩色图片失败:相机未使能"); + } + var pylon = this.getPylon(); + var result = pylon.deviceGrabSingleFrame(this.cam, this.channel); + if (result == null || result.imageBuffer == null) { + log.error("保存彩色图片失败:未获取到图像数据"); + throw new RuntimeException("保存彩色图片失败:未获取到图像数据"); + } + // 假设 result.imageBuffer 为8位单通道灰度,实际彩色需根据SDK返回格式调整 + Mat frameMat = new Mat(result.sizeY, result.sizeX, CvType.CV_8UC1); + frameMat.put(0, 0, result.imageBuffer); + + Mat colorMat = new Mat(); + Imgproc.cvtColor(frameMat, colorMat, Imgproc.COLOR_BayerRG2RGB); + + Imgcodecs.imwrite(filePath, colorMat); + log.info("彩色图片已保存到: {}", filePath); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 09a08af..10d14c9 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -18,7 +18,7 @@ spring: mybatis-plus: configuration: # 开启 SQL 日志输出(可选) -# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 如果需要加载 XML 文件(自定义 SQL),可配置 mapper-locations: mapper-locations: classpath*:mapper/*.xml @@ -49,3 +49,15 @@ iflytophald: modbus: port: ttyS1 baudrate: 9600 + + +opencv: + library-path: /usr/lib/jni/libopencv_java420.so + +pylon: + library-path: /opt/pylon-8.1.0_linux-aarch64/lib/libpylonc.so.10.0.0 + wrapper-path: /opt/basler_camera/lib/libbaslercamera.so + +photo: + url: http://192.168.8.168/static/photo + path: /home/firefly/package/photo \ No newline at end of file