在这一步,使用JavaCV
采集摄像头画面,再通过帧转换将JavaCV
采集到的视频帧解析成JavaFX
可以处理的Image
,并渲染到预先设定好的画面中
使用的JavaCV
依赖如下
<dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv</artifactId> <version>1.5.11</version>
</dependency> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> <version>1.5.11</version>
</dependency>
本次使用的是Java17
进行开发,JavaFX相关依赖如下
<dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> <version>17</version>
</dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-swing</artifactId> <version>17</version>
</dependency>
最终运行的demo代码如下
import javafx.application.Application
import javafx.application.Platform
import javafx.embed.swing.SwingFXUtils
import javafx.scene.Scene
import javafx.scene.image.ImageView
import javafx.scene.layout.StackPane
import javafx.stage.Stage
import org.bytedeco.javacv.Java2DFrameConverter
import org.bytedeco.javacv.OpenCVFrameConverter
import org.bytedeco.javacv.OpenCVFrameGrabber
import org.bytedeco.opencv.global.opencv_core
import kotlin.concurrent.thread class VideoTest : Application() { // 新建 OpenCV 视频抓取器 private val grabber = OpenCVFrameGrabber(0) // 用于显示视频帧 private val imageView = ImageView() private val frameConverter = OpenCVFrameConverter.ToMat() // 用于 Frame 和 Mat 转换 // 帧转换器 private val converter = Java2DFrameConverter() // 控制采集线程 private var isRunning = true override fun start(stage: Stage) { // 创建 JavaFX 窗口 val root = StackPane(imageView) val scene = Scene(root, 640.0, 480.0) // 窗口标题,与原代码一致 stage.title = "摄像头窗口" stage.scene = scene stage.show() // 窗口关闭时停止采集 stage.setOnCloseRequest { isRunning = false grabber.release() Platform.exit() } grabber.start() // 通过独立的线程,是采集和展示互不干扰,避免造成两个线程阻塞 thread { while (isRunning && stage.isShowing) { val grab = grabber.grab() // 现在开始采集过程 if (grab != null) { // 将 Frame 转为 Mat 并翻转 val mat = frameConverter.convertToMat(grab) opencv_core.flip(mat, mat, 1) // 水平翻转(1 表示左右翻转) val flippedFrame = frameConverter.convert(mat) // 转回 Frame // 将OpenCV的视频返回帧处理成Java Swing可以处理的BufferedImage val bufferedImage = converter.convert(flippedFrame) // 将BufferedImage转换成JavaFX的Image,第二个null为不使用预分配的WritableImage(性能优化选项) val fxImage = SwingFXUtils.toFXImage(bufferedImage, null) // 在主线程上更新UI,即视频采集到的画面通过主线渲染到提前设定好的imageView Platform.runLater { imageView.image = fxImage } } // 每次循环采集时间,用户控制采集频率以实现不同帧率 val sleepTime = (1000 / grabber.frameRate).toLong() Thread.sleep(sleepTime) } grabber.release() } } } fun main() { Application.launch(VideoTest::class.java)
}
视频调用代码
上述代码已经实现了java
配合openCV
实现录像采集,接下来更近一步,添加上视频的录制功能,此时需要借助FFmpeg去进行视频编码,具体代码如下 ^c302f7
import javafx.application.Application
import javafx.application.Platform
import javafx.embed.swing.SwingFXUtils
import javafx.scene.Scene
import javafx.scene.image.ImageView
import javafx.scene.layout.StackPane
import javafx.stage.Stage
import org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_H264
import org.bytedeco.javacv.FFmpegFrameRecorder
import org.bytedeco.javacv.Java2DFrameConverter
import org.bytedeco.javacv.OpenCVFrameConverter
import org.bytedeco.javacv.OpenCVFrameGrabber
import org.bytedeco.opencv.global.opencv_core
import kotlin.concurrent.thread class VideoTest : Application() { // 新建 OpenCV 视频抓取器 private val grabber = OpenCVFrameGrabber(0) // 用于显示视频帧 private val imageView = ImageView() private val frameConverter = OpenCVFrameConverter.ToMat() // 用于 Frame 和 Mat 转换 // 帧转换器 private val converter = Java2DFrameConverter() // 控制采集线程 private var isRunning = true // 视频编码器 private lateinit var recorder: FFmpegFrameRecorder override fun start(stage: Stage) { // 设置采集器大小 grabber.imageWidth = 640 grabber.imageHeight = 480 // 启动采集器 grabber.start() // 初始化FFmpeg视频解码器 recorder = FFmpegFrameRecorder("output.mp4", grabber.imageWidth, grabber.imageHeight) // 设置视频编码格式为H264 recorder.videoCodec = AV_CODEC_ID_H264 // 设置视频输出格式为H264 recorder.format = "mp4" // 设置帧率为30帧 recorder.frameRate = 30.0 // 设置画面质量(质量越小越高),默认为23 recorder.videoQuality = 23.0 recorder.start() // 创建 JavaFX 窗口 val root = StackPane(imageView) val scene = Scene(root, 640.0, 480.0) // 窗口标题,与原代码一致 stage.title = "摄像头窗口" stage.scene = scene // 显示窗口 stage.show() // 窗口关闭时停止采集 stage.setOnCloseRequest { isRunning = false // 窗口关闭时,释放资源 cleanup() Platform.exit() } // 通过独立的线程,是采集和展示互不干扰,避免造成两个线程阻塞 thread { while (isRunning && stage.isShowing) { val startTime = System.currentTimeMillis() try { val grab = grabber.grab() val mat = frameConverter.convert(grab) val flippedFrame = frameConverter.convert(mat) opencv_core.flip(mat, mat, 1) // 现在开始采集过程 // 将OpenCV的视频返回帧处理成Java Swing可以处理的BufferedImage val bufferedImage = converter.convert(flippedFrame) // 将BufferedImage转换成JavaFX的Image,第二个null为不使用预分配的WritableImage(性能优化选项) val fxImage = SwingFXUtils.toFXImage(bufferedImage, null) // 在主线程上更新UI,即视频采集到的画面通过主线渲染到提前设定好的imageView Platform.runLater { imageView.image = fxImage } try {// 设置编码并保存 recorder.record(flippedFrame) } catch (e: Exception) { e.printStackTrace() println("Record failed: ${e.message}") } // 释放反转帧 flippedFrame.close() // 释放原始视频帧 grab.close() } catch (e: Exception) { e.printStackTrace() } // 每次循环采集时间,用户控制采集频率以实现不同帧率 val endTime = System.currentTimeMillis() val elapsed = endTime - startTime val sleepTime = (33 - elapsed).coerceAtLeast(0) // 动态调整睡眠时间 Thread.sleep(sleepTime) } cleanup() } } /** * 释放资源 */ private fun cleanup() { recorder.stop() recorder.release() grabber.release() } } fun main() { Application.launch(VideoTest::class.java)
}