使用JavaCV调用摄像头与录制
在这一步,使用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)
}

浙公网安备 33010602011771号