使用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)  
}
posted @ 2025-03-12 09:23  nan1mono  阅读(192)  评论(0)    收藏  举报