如何在 JavaFX 应用程序中显示 OpenCV 网络摄像头捕获帧

How to display OpenCV webcam capture frame in JavaFX app

我正在使用 JavaFX 创建桌面应用程序,它允许您从网络摄像头扫描二维码。

我决定选择 JavaCV 来处理网络摄像头捕获。但是,问题是 CanvasFrame class 创建了一个 Swing JFrame。我的主要目标是找到将其与 JavaFX 组件集成的最佳方式。

我的问题是是否可以在 JPanel(或其他 Swing/JavaFx 组件)而不是 JFrame 中创建 CanvasFrame。在此选项中,我会将 JPanel 包装到 SwingNode - 它解决了我的集成问题。

我也在寻求其他解决 JavaFX 与 JavaCV 集成问题的建议。 也许有一种直接的方法可以将相机屏幕嵌入到 JavaFx 组件中。

我在下面粘贴测试代码。我的代码是用kotlin写的,但是不影响问题:

import com.google.zxing.*
import com.google.zxing.client.j2se.BufferedImageLuminanceSource
import com.google.zxing.common.HybridBinarizer
import org.bytedeco.javacv.*
import java.awt.image.BufferedImage
import java.util.*
import java.util.concurrent.Executors

class Test {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            Executors.newSingleThreadExecutor().execute { testWebcam() }
        }

        private fun testWebcam() {
            val grabber: OpenCVFrameGrabber = OpenCVFrameGrabber(0);
            val canvasFrame: CanvasFrame = CanvasFrame("Cam")
            grabber.start()

            while (canvasFrame.isVisible) {
                val frame: Frame = grabber.grabFrame() ?: break
                canvasFrame.showImage(frame)
                decodeQrCode(grabber)
            }
        }

        private fun decodeQrCode(grabber: OpenCVFrameGrabber) {
            val java2DFrameConverter: Java2DFrameConverter = Java2DFrameConverter()

            val frame: Frame = grabber.grabFrame()
            val image = java2DFrameConverter.getBufferedImage(frame)

            val decodedQr = parseQr(image)
            println(decodedQr)
        }

        private fun parseQr(image: BufferedImage): String? {

            val reader: MultiFormatReader = MultiFormatReader()
            val binaryBitmap: BinaryBitmap =
                BinaryBitmap(HybridBinarizer(BufferedImageLuminanceSource(image)))

            val hints: Hashtable<DecodeHintType, Any> = Hashtable()
            hints[DecodeHintType.CHARACTER_SET] = "UTF-8"
            hints[DecodeHintType.POSSIBLE_FORMATS] = listOf(BarcodeFormat.QR_CODE)

            return try {
                reader.decode(binaryBitmap, hints).text
            } catch (e: NotFoundException) {
                null;
            }

        }
    }
}

https://github.com/rladstaetter/javacv-webcam that has examples of using javacv 上有一个项目,其中包含 Swing、JavaFX 以及一种在 OpenCV 和 JavaFX 的 PixelBuffer 之间使用共享内存缓冲区的更新方法。

您可以使用由共享 ByteBuffer 支持的 JavaFX ImageView,而不是使用 CanvasFrame。伪代码是:

import java.nio.ByteBuffer

import javafx.scene.image._
import org.bytedeco.javacv.Frame
import org.bytedeco.opencv.global.opencv_imgproc._
import org.bytedeco.opencv.opencv_core.Mat

val videoView: ImageView = ImageView()
val grabber: OpenCVFrameGrabber = OpenCVFrameGrabber(0)
grabber.start()

// Fire off a thread to grab frames while the camera is active
// Each frame will ber passed to the updateView method below
// ... timer/thread omitted for brevity

val javaCVMat = Mat()

/** create buffer only once saves much time! */
val buffer: ByteBuffer = javaCVMat.createBuffer()

val formatByte: WritablePixelFormat<ByteBuffer> = PixelFormat.getByteBgraPreInstance()

fun updateView(frame: Frame): Unit = {
  val w = frame.imageWidth()
  val h = frame.imageHeight()

  val mat = javaCVConv.convert(frame)
  cvtColor(mat, javaCVMat, COLOR_BGR2BGRA)

  val pb = PixelBuffer(w, h, buffer, formatByte)
  val wi = WritableImage(pb)
  videoView.setImage(wi)
}

我解决了我的问题。就我而言,最好的解决方案是使用 Java2DFrameConverter:

import javafx.application.Application
import javafx.embed.swing.SwingFXUtils
import javafx.scene.Scene
import javafx.scene.image.ImageView
import javafx.scene.image.WritableImage
import javafx.scene.layout.VBox
import javafx.stage.Stage
import org.bytedeco.javacv.Frame
import org.bytedeco.javacv.Java2DFrameConverter
import org.bytedeco.javacv.OpenCVFrameGrabber
import java.awt.image.BufferedImage
import java.util.concurrent.Executors

class Whosebug : Application() {
    private val java2DFrameConverter: Java2DFrameConverter = Java2DFrameConverter()

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            launch(Whosebug::class.java)
        }
    }

    override fun start(primaryStage: Stage) {
        val grabber: OpenCVFrameGrabber = OpenCVFrameGrabber(0)
        grabber.start()

        val imageView: ImageView = ImageView()

        Executors.newSingleThreadExecutor().execute {
            while (true) {
                val frame = grabber.grabFrame()
                imageView.image = frameToImage(frame)
            }
        }

        val scene: Scene = Scene(VBox(imageView), 800.0, 800.0)
        primaryStage.scene = scene
        primaryStage.show()
    }

    private fun frameToImage(frame: Frame): WritableImage {
        val bufferedImage: BufferedImage = java2DFrameConverter.getBufferedImage(frame)
        return SwingFXUtils.toFXImage(bufferedImage, null)
    }
}