介绍
Java OpenCV 是一个强大的开源计算机视觉库,它提供了丰富的图像处理和分析功能,越来越多的应用需要使用摄像头来获取实时视频流进行处理和分析。
在 Java 中使用 OpenCV 打开摄像头的基本步骤如下:
- 确保已经安装了OpenCV库
- 使用 OpenCV 的 VideoCapture 类来打开摄像头
- 使用 Mat 类来存储每一帧的图像
- 使用循环来不断从摄像头中读取帧,并显示这些帧
- 处理完毕后,释放摄像头资源
安装 OpenCV
下载地址:https://opencv.org/releases
从 OpenCV 官网下载适合自己操作系统版本的,然后双击安装(实质就是解压),解压完打开文件夹是:
txt
build/
sources/
LICENSE.txt
LICENSE_FFMPEG.txt
README.md.txt
build 是 OpenCV 使用时要用到的一些库文件,而 sources 中则是 OpenCV 官方为我们提供的一些 demo 示例源码
配置环境变量可以不用配置,直接将用到的 dll(opencv_java411.dll
、opencv_world411.dll
、opencv_videoio_ffmpeg411_64.dll
) 文件复制到 C:\Windows\System32
下即可。
编码实现
将 OpenCV 库添加到 Java 项目的构建路径中,使用 VideoCapture 类来打开摄像头。
添加依赖
xml
<!--openCV 依赖包-->
<dependency>
<groupId>org.opencv</groupId>
<artifactId>opencv</artifactId>
<version>4.1.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/opencv-411.jar</systemPath>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
注:fork、includeSystemScope 不配置,打包不生效。
打开摄像头
java
package com.demo.utils;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.VideoWriter;
import static org.opencv.videoio.Videoio.CAP_PROP_FRAME_HEIGHT;
import static org.opencv.videoio.Videoio.CAP_PROP_FRAME_WIDTH;
@Slf4j
public class RtspRecordingUtil {
public static void main(String[] args) {
init();
}
public static void init() {
// 加载库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
VideoCapture capture = new VideoCapture();
capture.open("rtsp://admin:123456@192.168.1.11/Streaming/Channels/101");
log.info("=======isOpen:{}========", capture.isOpened());
if (capture.isOpened()) {
Mat mat = new Mat();
VideoWriter vw = new VideoWriter();
Size size = new Size();
size.width = capture.get(CAP_PROP_FRAME_WIDTH);
size.height = capture.get(CAP_PROP_FRAME_HEIGHT);
boolean t = vw.open("F:\\test.avi", VideoWriter.fourcc('M', 'P', '4', '2'), 30, size);
// 录制
while (capture.read(mat)) {
vw.write(mat);
}
capture.release();
vw.release();
}
log.info("=======结束======");
}
}
上述示例代码首先加载了 OpenCV 库,并创建了一个 VideoCapture 对象,打开默认摄像头。然后使用一个循环读取每一帧图像写到 VideoWriter 中保存。
打开多个摄像头
要打开多个摄像头,我们可以通过创建多个线程来拉取不同的视频流。
java
package com.demo.util;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.VideoWriter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.opencv.videoio.Videoio.CAP_PROP_FRAME_HEIGHT;
import static org.opencv.videoio.Videoio.CAP_PROP_FRAME_WIDTH;
@Slf4j
@Component
public class RtspRecordingUtil {
// 视频保存地址
@Value("${video.video-path}")
private String videoPath;
// 录制视频的默认时长
@Value("${video.video-recording-duration}")
private Long videoRecordingDuration;
// 默认开启十个线程
private String DEFAULT_THREAD_POOL = 10;
ExecutorService executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL);
{
/**
* 将以下三个文件:
* opencv_java411.dll、
* opencv_world411.dll、
* opencv_videoio_ffmpeg411_64.dll
* 文件拷贝到 C:\Windows\System32 目录下
*/
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// 本地运行可以,打包后找不到文件
/**
String path = this.getClass().getClassLoader().getResource("").getPath();
System.load(path + "lib/dll/opencv_java411.dll");
System.load(path + "lib/dll/opencv_world411.dll");
System.load(path + "lib/dll/opencv_videoio_ffmpeg411_64.dll");
*/
}
public String recording(String username, String password, String ip, Integer chanelId, String videoName, Integer duration) {
executorService.submit(new VideoCaptureTask(username, password, ip, chanelId, videoName, duration));
return "ok";
}
class VideoCaptureTask implements Runnable {
// 设备用户名
private String username;
// 设备密码
private String password;
// 设备IP
private String ip;
// 设备通道号
private Integer chanelId;
// 视频名称
private String videoName;
// 录制时长
private Integer duration;
public VideoCaptureTask(String username, String password, String ip, Integer chanelId, String videoName, Integer duration) {
this.username = username;
this.password = password;
this.ip = ip;
this.chanelId = chanelId;
this.videoName = videoName;
this.duration = duration;
}
@Override
public void run() {
VideoCapture capture = null;
VideoWriter vw = null;
try {
capture = new VideoCapture(videoName);
String url = "rtsp://" + username + ":" + password + "@" + ip + "/Streaming/Channels/" + (Objects.nonNull(chanelId) ? chanelId : "1");
capture.open(url);
log.info("==== VideoCapture 开始....URL: {} ======= isOpened:{}=====", url, capture.isOpened());
if (capture.isOpened()) {
Mat mat = new Mat();
Size size = new Size(capture.get(CAP_PROP_FRAME_WIDTH), capture.get(CAP_PROP_FRAME_HEIGHT));
// 视频存储地址
vw = new VideoWriter();
// 判断存储目录是否存在
if (Files.notExists(Paths.get(videoPath))) {
Files.createDirectories(Paths.get(videoPath));
}
vw.open(videoPath + videoName + ".avi", VideoWriter.fourcc('M', 'P', '4', '2'), 30, size);
Instant start = Instant.now();
long seconds = 0;
long _duration = Objects.nonNull(duration) ? duration : videoRecordingDuration;
while (capture.read(mat) && seconds <= _duration) {
vw.write(mat);
seconds = Duration.between(start, Instant.now()).getSeconds();
}
}
log.info("==== VideoCapture 结束....URL: {} =====", url);
} catch (Exception e) {
log.error("==== VideoCapture 异常:{}", e);
} finally {
if (Objects.nonNull(capture)) capture.release();
if (Objects.nonNull(vw)) vw.release();
}
}
}
}
需要处理不同摄像头之间分辨率和帧率的不匹配问题,以及考虑如何有效地管理多个 VideoCapture 实例问题,这里使用视频名称作为摄像头的索引(new VideoCapture(videoName)
)防止重复实例化。