java cv流媒体处理高频抓帧

您所在的位置:网站首页 mat获取图像大小 java cv流媒体处理高频抓帧

java cv流媒体处理高频抓帧

2023-05-26 21:56| 来源: 网络整理| 查看: 265

什么是javacv

JavaCV 是一个基于 OpenCV 和 FFmpeg 的 Java 接口库,它提供了许多图像和视频处理的功能。在本文中,我们将介绍如何使用 JavaCV 实现流媒体处理高频抓帧。

获取视频流 使用FFmpegFrameGrabber类获取视频流

FFmpegFrameGrabber 是 JavaCV 中用于抓取视频帧的类,它是基于 FFmpeg 实现的。FFmpeg 是一个开源的视频和音频处理库,能够处理多种格式的音视频文件。

FFmpegFrameGrabber 提供了一系列方法来设置视频源、读取视频帧、获取视频信息等操作。通过FFmpegFrameGrabber 类,我们可以获取视频流、抓取视频帧并进行处理。

以下是 FFmpegFrameGrabber 的一些常用方法:

start():启动视频流读取器。 stop():停止视频流读取器。 grab():抓取一帧视频帧。 getLengthInFrames():获取视频流中的总帧数。 getFrameRate():获取视频流的帧率。 setFrameRate():设置视频流的帧率。 getPixelFormat():获取视频流的像素格式。 getAudioChannels():获取音频流的通道数。 getAudioSampleRate():获取音频流的采样率。 getTimestamp():获取视频帧的时间戳 setImageWidth(640):设置视频帧的大小 setImageHeight(480):设置视频帧的大小 setAudioChannels(2):设置视频帧的通道数 setVideoCodec(avcodec.AV_CODEC_ID_H264):设置视频帧的编解码器 setSampleRate(44100):设置视频帧的采样率 setPixelFormat(avutil.AV_PIX_FMT_BGR24):设置视频帧的格式

除了 FFmpegFrameGrabber,JavaCV 还提供了其他抓取视频帧的类,例如 OpenCVFrameGrabber 和Java2DFrameGrabber。根据不同的需求,我们可以选择使用不同的类来进行视频帧的抓取和处理。

设置视频流的URL、格式、帧率等参数 FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(input); // tcp 形式 方式丢包过多 grabber.setOption("rtsp_transport", "tcp"); grabber.setFrameRate(24); ... 复制代码 抓取视频帧

使用 JavaCV 抓取视频帧的基本步骤如下:

创建 FFmpegFrameGrabber 对象

使用 FFmpegFrameGrabber 创建一个视频帧抓取器对象,并指定要抓取的视频文件路径:

FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("input.mp4"); 复制代码

开始视频帧的抓取

调用 grabber 对象的 start() 方法开始视频帧的抓取:

grabber.start(); 复制代码

循环获取视频帧

在一个循环中,使用 grabber 对象的 grab() 方法获取视频帧,直到抓取到视频帧为止:

Frame frame; while ((frame = grabber.grab()) != null) { // 处理视频帧 } 复制代码

处理视频帧

在循环中,使用获取到的视频帧进行处理,例如将视频帧保存到本地文件:

OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat(); Mat mat = converter.convert(frame); Imgcodecs.imwrite("output.jpg", mat); 复制代码

停止视频帧的抓取

在循环结束后,调用 grabber 对象的 stop() 方法停止视频帧的抓取:

grabber.stop(); 复制代码 抓取视频帧存在问题 图片越来越模糊

错误原因:丢包严重导致,默认情况下,JavaCV使用RTSP传输协议中的UDP方式进行数据传输。

在使用 JavaCV 抓取 RTSP 视频流时,有些视频服务器只支持使用 TCP 协议进行数据传输,如果使用 UDP 协议可能会导致视频流中断或者无法正常播放。

默认情况下,JavaCV 使用 RTSP 传输协议中的 UDP 方式进行数据传输,如果视频服务器不支持 UDP 协议,则需要手动设置使用 TCP 协议进行数据传输。设置方法如下:

FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("rtsp://example.com/stream"); grabber.setOption("rtsp_transport", "tcp"); 复制代码

使用 TCP 协议进行数据传输会增加一些网络传输开销,但可以保证数据传输的可靠性。因此,如果您遇到 RTSP 视频流无法正常播放或者中断的问题,可以尝试设置使用 TCP 协议进行数据传输。

ffmpeg报错 error while decoding MB 52 46, bytestream -7

错误原因:视频编码中的错误,通常是由于视频文件损坏或编码错误导致的。这个错误信息指出在解码视频时发生了一个错误,MB 52 46 是指解码器在处理视频帧时遇到了错误的宏块,bytestream -7 表示解码器在处理视频时遇到了错误的字节流。

此错误查阅了很多资料 Python使用小教程01——[h264 @ 0x55abeda05080] error while decoding MB 0 14, bytestream 104435_puffdoudou的博客-CSDN博客 这篇文章说的比较靠谱,原因是处理速度跟不上输出速度,导致处理的图片是好几、几十秒之前的画面,缓存区爆满,最后导致程序报错。

解决此问题就要加快读取到图片的处理速度。

当同时读取10路左右流时机器卡死

问题原因:在抓取视频桢一节中我们使用while循环处理抓帧,可能会存在以下问题:

CPU 和内存资源不足

同时处理多路视频流会占用大量的 CPU 和内存资源,如果计算机的 CPU 和内存资源不足,就可能导致计算机卡死。因此,在处理多路视频流时,需要确保计算机具有足够的 CPU 和内存资源。

磁盘 I/O 速度不足

如果同时处理多路视频流,可能会对磁盘 I/O 速度造成很大压力,导致磁盘 I/O 速度不足,从而导致计算机卡死。因此,在处理多路视频流时,需要确保计算机的磁盘 I/O 速度足够快。

程序逻辑问题

如果程序逻辑存在问题,例如没有及时释放内存、处理速度跟不上视频帧的获取速度等,也可能导致计算机卡死。因此,在处理多路视频流时,需要注意程序逻辑的正确性和效率。

使用 FrameGrabber 类的 grab() 方法抓取视频帧 将视频帧转换为 Mat 对象进行处理 高频抓帧优化 使用协程

下面是一个使用协程处理的java例子:

/** * @author: fankk * @create: 2023-04-21 16:22:11 * @description: */ public class Test4 { public static void record() throws FrameRecorder.Exception, org.bytedeco.javacv.FrameGrabber.Exception{ String input = "rtsp://172.168.250.4/lg_gw/test"; List fiberList = Lists.newArrayList(); for(int i = 0; i < 1; i++) { int finalI = i; FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(input + (i + 1)); grabber.setOption("rtsp_transport", "tcp"); grabber.setPixelFormat(1); grabber.setFrameRate(3); // grabber.setOption("video_colorspace", "bt709"); // grabber.setOption("pixel_format", "yuv420p"); // grabber.setPixelFormat(AV_PIX_FMT_RGB24); try { grabber.start(); } catch (FFmpegFrameGrabber.Exception e) { e.printStackTrace(); } Integer width=grabber.getImageWidth(); Integer height=grabber.getImageHeight(); FFmpegFrameRecorder recorder =new FFmpegFrameRecorder(output,width,height,0); int ftp = (int)grabber.getFrameRate(); recorder.setFormat("image2"); recorder.setOption("update", "0"); try { recorder.start(); } catch (FFmpegFrameRecorder.Exception e) { e.printStackTrace(); } CanvasFrame canvas = new CanvasFrame("图像预览" + finalI);// 新建一个窗口 canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); int finalI1 = i; Fiber fiber = new Fiber(() -> { Frame frame = null; int delay = 1000 / ftp; try { while ((frame = grabber.grabImage()) != null) { canvas.showImage(frame); Frame finalFrame = frame; new Fiber(() -> { saveImg(finalFrame, finalI1); }).start(); Fiber.sleep(delay); } }catch (Exception e) { e.printStackTrace(); } }); fiberList.add(fiber); } for (int i = 0; i < fiberList.size(); i++) { fiberList.get(i).start(); } try { Thread.sleep(3000000000000000L); } catch (InterruptedException e) { e.printStackTrace(); } } private static void saveImg(Frame frame, Integer finalI1) { try { RenderedImage renderedImage = frameToBufferedImage(frame); @Cleanup ByteArrayOutputStream fs = new ByteArrayOutputStream(); ImageIO.write(renderedImage, "jpg", fs); String base64Img = Base64Encoder.encode(fs.toByteArray()).replaceAll("[\s*\t\n\r]", ""); downloadImg(base64Img, "F:/img1/img" + finalI1); } catch (IOException e) { e.printStackTrace(); } } /** * 帧转为流 * * @param frame * @return */ private static RenderedImage frameToBufferedImage(Frame frame) { //创建BufferedImage对象 Java2DFrameConverter converter = new Java2DFrameConverter(); BufferedImage bufferedImage = converter.getBufferedImage(frame); return bufferedImage; } /** * base64转图片 * * @param base64str base64码 * @param savePath 图片路径 * @return boolean 判断是否成功下载到本地 */ private static boolean downloadImg(String base64str, String savePath) throws IOException { //对字节数组字符串进行Base64解码并生成图片 if (base64str == null) { return false; } //Base64解码 byte[] b = Base64Decoder.decode(base64str); for (int i = 0; i < b.length; ++i) { //调整异常数据 if (b[i] < 0) { b[i] += 256; } } // 判断路径是否不存在,不存在就创建文件夹 File fileDir = new File(savePath); if (!fileDir.exists() && !fileDir.isDirectory()) { fileDir.mkdirs(); } // 生成一个空文件,自定义图片的名字 File file = new File(savePath +"/" + System.currentTimeMillis() + ".jpg"); if (!file.exists()) { file.createNewFile(); } //生成jpg图片 OutputStream out = new FileOutputStream(file.getPath()); out.write(b); out.flush(); out.close(); return true; } public static void main(String[] args) try { record(); } catch (IOException e) { e.printStackTrace(); } } } 复制代码

在上面的例子中,使用 fiber 在每次抓取图像后休眠n,在测试过程中发现能很好的解决同时抓取 20 路摄像头时的资源消耗问题。但引入了协程又增加了系统的复杂性。本文发布于 juejin 谢绝转载。

使用 ScheduledExceutorService

ScheduledExecutorService 是 Java 并发包中提供的一个接口,它继承了 ExecutorService 接口,提供了一些方法可以用来执行定时任务或周期性任务。

与 Timer 类相比,ScheduledExecutorService 更加灵活,更加可靠,因为它使用了线程池来执行任务,可以避免任务间的相互影响,还可以动态调整线程池的大小,以适应任务的负载情况;而 Timer 类则是单线程执行任务,任务之间可能会相互干扰,而且无法动态调整线程池的大小。

ScheduledExecutorService 简介

ScheduledExecutorService 接口中常用的方法有:

schedule(Runnable command, long delay, TimeUnit unit):在指定的时间后执行一次任务。 scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):在指定的时间后开始执行任务,并以指定的周期执行任务。 scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit):在指定的时间后开始执行任务,并以指定的延迟时间执行任务。

使用 ScheduledExecutorService 执行定时任务或周期性任务的步骤一般如下:

创建一个 ScheduledExecutorService 对象。 创建一个实现 Runnable 接口的任务。 调用 ScheduledExecutorService 对象的 schedule()、scheduleAtFixedRate() 或 scheduleWithFixedDelay() 方法来执行任务。 关闭 ScheduledExecutorService 对象。

例如,以下代码演示了如何使用 ScheduledExecutorService 执行一个定时任务:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); Runnable task = new Runnable() { @Override public void run() { System.out.println("Hello, world!"); } }; executor.schedule(task, 5, TimeUnit.SECONDS); executor.shutdown(); 复制代码

这段代码会创建一个 ScheduledExecutorService 对象,然后创建一个实现 Runnable 接口的任务,最后调用 schedule() 方法,在 5 秒后执行任务。执行完任务后,调用 shutdown() 方法关闭 ScheduledExecutorService 对象。

使用 ScheduledExecutorService 改进抓帧 /** * @author: fankk * @create: 2023-04-21 16:22:11 * @description: */ public class VideoStreamProcessor { private static final int QUEUE_CAPACITY = 6; private CircularFifoQueue frameQueue; private Java2DFrameConverter converter; private FrameGrabber grabber; private ScheduledExecutorService executor; private boolean isRunning; public VideoStreamProcessor(FrameGrabber grabber) { this.grabber = grabber; this.converter = new Java2DFrameConverter(); this.frameQueue = new CircularFifoQueue(QUEUE_CAPACITY); this.executor = new ScheduledThreadPoolExecutor(1); this.isRunning = true; } public void start() { executor.scheduleAtFixedRate(() -> { try { BufferedImage image = converter.convert(grabber.grab()); if (image != null) { if (!frameQueue.offer(image)) { System.err.println("Frame queue is full!"); } else { System.out.println("入队成功!" + System.currentTimeMillis()); } } } catch (Exception e) { e.printStackTrace(); } }, 0, 10, TimeUnit.MILLISECONDS); } public void stop() { this.isRunning = false; executor.shutdown(); try { executor.awaitTermination(1, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } try { grabber.stop(); } catch (Exception e) { e.printStackTrace(); } } public BufferedImage getNextFrame() throws InterruptedException { return frameQueue.poll(); } } 复制代码

这个类是一个视频流处理器,它使用了 JavaCV 库来获取视频流并将视频帧转换成 BufferedImage 对象,然后将这些帧存储在一个循环队列中,供其他模块使用。本文发布于 juejin 谢绝转载。

这个类的主要属性和方法如下:

frameQueue:循环队列,用于存储视频帧。 converter:JavaCV 库中的一个工具类,用于将视频帧转换成 BufferedImage 对象。 grabber:JavaCV 库中的一个类,用于获取视频流。 executor:ScheduledExecutorService 对象,用于定时抓取视频帧。 isRunning:标志位,用于表示视频流是否正在运行。 start():启动视频流处理器,开始定时抓取视频帧。 stop():停止视频流处理器,停止抓取视频帧。 getNextFrame():从视频帧队列中获取下一个视频帧。

在这个类的构造函数中,它初始化了循环队列、Java2DFrameConverter 对象、FrameGrabber 对象、ScheduledExecutorService 对象以及标志位。

在 start() 方法中,它通过调用 executor.scheduleAtFixedRate() 方法来定时抓取视频帧,抓取的时间间隔为 10 毫秒。在抓取视频帧后,它会将视频帧转换成 BufferedImage 对象,并将其存储在循环队列中。

在 stop() 方法中,它会停止视频流处理器的运行,并停止定时抓取视频帧。它首先关闭 ScheduledExecutorService 对象,等待 1 秒钟,然后停止 FrameGrabber 对象。

在 getNextFrame() 方法中,它从视频帧队列中获取下一个视频帧,并返回它。如果队列为空,则返回 null。

这个类的使用方法一般如下:

创建一个 FrameGrabber 对象,用于获取视频流。 创建一个 VideoStreamProcessor 对象,将 FrameGrabber 对象传递给它。 调用 VideoStreamProcessor 对象的 start() 方法,开始获取视频帧。 循环调用 VideoStreamProcessor 对象的 getNextFrame() 方法,获取视频帧并进行处理。 调用 VideoStreamProcessor 对象的 stop() 方法,停止获取视频帧。

这样改进的优势是把视频帧的读取和图像的处理分离,同时在读取视频流的时候通过每隔n毫秒读取一次的方式来读取视频帧,即兼顾了视频帧读取的效率也避免了 while 循环的性能消耗问题。

图像处理 例如进行人脸识别、运动检测等操作

结论

JavaCV 是一个基于 Java 的计算机视觉和机器学习库,它提供了一组用于处理图像和视频的工具和算法。在 JavaCV 中,可以使用 FFmpegFrameGrabber 类来获取视频流并抓取视频帧。但是,抓取视频帧存在一些问题,比如低效率和内存泄漏等。为了解决这些问题,我们可以使用高频抓帧优化技术,比如使用协程或者 ScheduledExecutorService。这些技术可以提高抓帧的效率,减少内存占用,并提高程序的稳定性。此外,JavaCV 还提供了一些图像处理的工具和算法,可以用于处理视频帧。通过使用 JavaCV,我们可以轻松地实现图像和视频处理的功能,并将其集成到我们的 Java 程序中。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3