介紹
Java OpenCV 是一個強大的開源計算機視覺庫,它提供了豐富的圖像處理和分析功能,越來越多的應用需要使用攝像頭來獲取實時視頻流進行處理和分析。
在 Java 中使用 OpenCV 打開攝像頭的基本步驟如下:
- 確保已經安裝了OpenCV庫
- 使用 OpenCV 的 VideoCapture 類來打開攝像頭
- 使用 Mat 類來存儲每一幀的圖像
- 使用循環來不斷從攝像頭中讀取幀,并顯示這些幀
- 處理完畢后,釋放攝像頭資源
安裝 OpenCV
下載地址:https://opencv.org/releases
從 OpenCV 官網下載適合自己操作系統版本的,然后雙擊安裝(實質就是解壓),解壓完打開文件夾是:
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 類來打開攝像頭。
添加依賴
<!--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 不配置,打包不生效。
打開攝像頭
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 中保存。
打開多個攝像頭
要打開多個攝像頭,我們可以通過創建多個線程來拉取不同的視頻流。
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;// 設備IPprivate 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;}@Overridepublic 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)
)防止重復實例化。