javafx通过javacv访问网络摄像头

百度搜 javafx 摄像头,结果都是访问USB或者机身自带摄像头的,几乎找不到访问 IP Camera的。

访问USB或机身自带摄像头,不用javacv也能很容易的实现,具体参考:
https://github.com/sarxos/webcam-capture/tree/master/webcam-capture-examples/webcam-capture-javafx

web-captrue这个项目本来也提供了可供参考的访问 IP Camera 的方式,但是我一直没有实现,或者是摄像头厂家不支持?不得而知。

访问IP Camera 大抵通过两种方式,一是使用厂家提供的SDK,二是通过RTSP协议(我自认为的,如果不对,欢迎指证)。

使用厂家SDK的话,对于java来讲非常麻烦。一般厂家提供的是H264编码的数据,经过厂家解码库解码后变成YUV格式,在 C++ 里面,通过 DirectX 直接就可以在界面上显示YUV了,然而 java 却不行,网上一搜索,都说是要转成 RGB 再显示,苦B的地方就在这了。一者网上罗列的转RGB的方法,不一定有用,反正我是搜了N个结果,也试了N种,才找到一种能够成功转换的方法。二者这个转换的成本太大了,需要100毫秒的时间,而一般摄像头是30几毫秒拍一帧。

当然也可以要求厂家做RGB的转换,厂家一般用C++来转(不知怎么的,C++的转换要比java快多了,也许是网上找到的java转换方法不够优化吧),如果转成RGB32,嗯,很好,这是 javaer 更加熟悉的格式,awt 里面BufferedImage.setRGB() 就能够处理(或者javafx WritableImage.getPixelWriter().setPixels()),然而,BufferedImage.setRGB()或者 javafx setPixels()的耗时也都是很要命的,所以还是考虑要厂家转成 RGB24 吧。不错,这快了很多,WritableImage.getPixelWriter().setPixels()20毫秒内就能够搞定。

可试用结果如何呢?SDK是30几毫秒取一帧,而转成YUV,再转成RGB,再 setPixels() 这一路走来,保不准就超过30毫秒了,有时甚至更久,所以结果还是让人傻眼了,界面上看到的视频明显滞后5秒,10秒,不等,啊不等,这不等实在比一个比较确切的时间更加让人棘手。

所以,最后,试一试 javacv 吧。

好不容易搜索到一个只有半截的例子,大致如下:
FFmpegFrameGrabber grabber= new FFmpegFrameGrabber(ip);//厂家提供的RTSP url
try {
            grabber.start();
            Frame frame = null;
            CanvasFrame window = new CanvasFrame(...);
            while ((frame = grabber.grab()) != null) {
                window.showImage(frame);
            }
        } catch (FrameGrabber.Exception e) {
            e.printStackTrace();
        }

看起来很简单,其实我在 javacv 库里面发现有个 IPCameraFrameGrabber ,从命名上看,这个更合适,但是不知道怎么用,没空去细看 API,反正FFmpegFrameGrabber能行,不管那么多了。

但是我的应用是 javafx 的,而 CanvasFrame 是 swing 的,再者我的应用不可能只是一个摄像头功能,所以 CanvasFrame 虽然封装得很简便,于我是没什么用的,跟到里面去,发现了个Java2DFrameConverter类,它能够将 grabber.grab() 到的 Frame 转换成一个 BufferedImage,于是 javafx 代码如下:
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(ip);//厂家提供的RTSP url
        Java2DFrameConverter converter = new Java2DFrameConverter();

        try {
            grabber.start();
            Frame frame = null;

            final AtomicReference<WritableImage> ref = new AtomicReference<>();
            BufferedImage img = null;

            while (imageView.isVisible() && (frame = grabber.grab()) != null) {
                if (frame.image != null) {
                    img = converter.convert(frame);
                    ref.set(SwingFXUtils.toFXImage(img, ref.get()));
                    img.flush();
                    Platform.runLater(() -> imageView.setImage(ref.get()));
                }
            }
        } catch (FrameGrabber.Exception e) {
            e.printStackTrace();
        }

看一看效果,very good!终于可以喘一口气!只是,厂家极力不推荐使用RTSP的方式,说是性能差很多。那有什么办法呢,直接使用你的SDK根本不行啊。

那大言不惭的结论就有了:java 真的不适合做这方面的事情,至少目前我所知的API没有非常好的解决方案,当然牛逼的人或许在解码时用汇编来完成,那就另说了。

对了,说半天,忘记讲如何搭建 javacv 环境了。
使用 maven 吧,在 pom 里面加入如下一段就行:
<dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.3.2</version>
        </dependency>
我使用的是目前最新版1.3.2,不需要单独下载 opoencv 可执行包和dll,据网上说是从0.8版本开始就不需要这样了,实在方便很多。如果maven是第一次下载 javacv,OH,那有得等了,好几百M。

还有一件重要的事情,程序写好后第一次调试或运行之前,必须要执行
mvn package -Dplatform.dependencies
关键部分是 -Dplatform.dependencies ,如果不这样做,会报 avutil 无法实例化的异常,大概是需要javacv 与当前环境的平台进行绑定吧。第一次需要这样,之后就不需要了。

如若使用上述方式搜索 javacv 环境,打包后会把几乎所有 javacv 中的库都包括进来,200多M,呵呵,jre 就够笨的了,这 javacv 200多M怎么能让人忍受得了。还好,有些不必要的 jar,从名字上就能分辨出来,比如我的目标环境是 win32,那么,那些 XXX-android, XXX-linux, XXX-macxos, XXX-windows-64 的jar 就不要打包进去了。这样一处理,javacv 运行库剩下30多 M,勉强能接受。(如果哪位还知道哪些 jar 是可以去掉的,请不吝赐教)

OK,就这样吧。

猜你喜欢

转载自seashoreblue.iteye.com/blog/2377003
今日推荐