11 changed files with 420 additions and 4 deletions
-
2build.gradle
-
BINlib/opencv-420.jar
-
4src/main/java/com/iflytop/gd/app/controller/PhotoController.java
-
46src/main/java/com/iflytop/gd/app/core/runner/BaslerCameraApplicationRunner.java
-
15src/main/java/com/iflytop/gd/app/service/api/PhotosService.java
-
BINsrc/main/java/com/iflytop/gd/camera/BaslerCameraWrapper$GrabResult.class
-
BINsrc/main/java/com/iflytop/gd/camera/BaslerCameraWrapper.class
-
130src/main/java/com/iflytop/gd/camera/BaslerCameraWrapper.java
-
65src/main/java/com/iflytop/gd/hardware/controller/BaslerCameraController.java
-
148src/main/java/com/iflytop/gd/hardware/drivers/CameraBaslerDriver.java
-
14src/main/resources/application-dev.yml
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
@ -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<String> setIndex(@RequestParam Integer index) { |
||||
|
driver.setIndex(index); |
||||
|
log.info("设置相机索引为: {}", index); |
||||
|
return ResponseEntity.ok("设置相机索引为: " + index); |
||||
|
} |
||||
|
|
||||
|
// 使能相机 |
||||
|
@PostMapping("/enable") |
||||
|
public ResponseEntity<String> enable() { |
||||
|
driver.enable(); |
||||
|
return ResponseEntity.ok("相机已使能"); |
||||
|
} |
||||
|
|
||||
|
// 关闭相机 |
||||
|
@PostMapping("/disable") |
||||
|
public ResponseEntity<String> disable() { |
||||
|
driver.disable(); |
||||
|
return ResponseEntity.ok("相机已关闭"); |
||||
|
} |
||||
|
|
||||
|
// 抓取Mat对象(仅演示返回字符串,实际可自定义返回格式) |
||||
|
@GetMapping("/grabToMat") |
||||
|
public ResponseEntity<String> 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<String> saveImg(@RequestParam String filePath) { |
||||
|
driver.saveImg(filePath); |
||||
|
return ResponseEntity.ok("图片已保存到: " + filePath); |
||||
|
} |
||||
|
|
||||
|
// 保存彩色图片 |
||||
|
@PostMapping("/saveColorImg") |
||||
|
public ResponseEntity<String> saveColorImg(@RequestParam String filePath) { |
||||
|
driver.saveColorImg(filePath); |
||||
|
return ResponseEntity.ok("彩色图片已保存到: " + filePath); |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue