2

我正在寻找用于运动计时的线扫描相机的替代品,或者更确切地说,用于需要确定放置的部分。我发现普通工业相机可以轻松匹配商用相机解决方案的速度,达到每秒 1000 帧以上。对于我的需要,通常计时的准确性并不重要,重要的是运动员的相对位置。我想我可以为此使用最便宜的 Basler、IDS 或任何其他区域扫描工业相机之一。当然,有些线扫描相机的速度可以超过几千 fps(或 hz),但也有可能以不到 500 欧元的价格获得可以达到所需 1000-3000fps 的区域扫描相机。

我的圣杯当然是 FinishLynx(或任何其他线扫描系统)的近实时图像合成功能,基本上这部分:https ://youtu.be/7CWZvFcwSEk?t=23s

我为替代方案考虑的整个过程是:

  • 使用 Basler Pylon Viewer(或其他软件)以相机最快的读取速度记录 2px 宽的图像。对于我目前使用的相机,这意味着它必须侧放并且需要降低高度,因为它是读取 1920x2px帧 @ >250fps的唯一方法
  • 制作一个程序或批处理脚本,然后将这些1920x2px帧拼接在一起,例如录制1000*1920x2px 帧的一秒钟,这意味着生成的图像分辨率为1920x2000px (水平 x 垂直)。
  • 最后使用相同的程序或其他方式,只需旋转图像,使其反映相机的位置,从而获得分辨率为2000x1920px的图像(再次水平 x 垂直)
  • 在分析程序(当前为 ImageJ)中打开图像以快速分析结果

我不是程序员,但这是我仅使用批处理脚本就能完成的,当然还有 stackoverflow 的帮助。

  • 目前,例如可以实时将整 10 秒作为原始/mjpeg(avi/mkv) 流录制到磁盘。
  • 将单个帧记录为 TIFF 或 BMP,或使用 FFMPEG 将它们保存为 PNG 或 JPG 大约需要 20-60 秒 附加和旋转然后需要大约 45-60 秒 这一切都需要在不到 60 秒的时间内完成 10几秒钟的镜头(1000-3000fps @ 10s = 10000-30000 帧),因此我需要更快的东西。

我能够弄清楚如何使用 ImageMagick 变得非常高效:

magick convert -limit file 16384 -limit memory 8GiB -interlace Plane -quality 85 -append +rotate 270 “%folder%\Basler*.Tiff” “%out%”

#%out% has a .jpg -filename that is dynamically made from folder name and number of frames.

这个命令可以在 i5-2520m 上在大约 30 秒内获得 10000 帧编码(尽管大部分处理似乎只使用一个线程,因为它的 cpu 使用率为 25%)。这是生成的图像:https ://i.imgur.com/OD4RqL7.jpg (19686x1928px)

但是,由于使用 Basler 的 Pylon Viewer 录制到 TIFF 帧所用的时间比录制 MJPEG 视频流要长得多,因此我想使用 MJPEG (avi/mkv) 文件作为附加源。我注意到 FFMPEG 有“image2pipe”-命令,它应该能够直接将图像提供给 ImageMagick。虽然我无法让这个工作:

   $ ffmpeg.exe -threads 4 -y -i "Basler acA1920-155uc (21644989)_20180930_043754312.avi" -f image2pipe - | convert - -interlace Plane -quality 85 -append +rotate 270 "%out%" >> log.txt
    ffmpeg version 3.4 Copyright (c) 2000-2017 the FFmpeg developers
      built with gcc 7.2.0 (GCC)
      configuration: –enable-gpl –enable-version3 –enable-sdl2 –enable-bzlib –enable-fontconfig –enable-gnutls –enable-iconv –enable-libass –enable-libbluray –enable-libfreetype –enable-libmp3lame –enable-libopenjpeg –enable-libopus –enable-libshine –enable-libsnappy –enable-libsoxr –enable-libtheora –enable-libtwolame –enable-libvpx –enable-libwavpack –enable-libwebp –enable-libx264 –enable-libx265 –enable-libxml2 –enable-libzimg –enable-lzma –enable-zlib –enable-gmp –enable-libvidstab –enable-libvorbis –enable-cuda –enable-cuvid –enable-d3d11va –enable-nvenc –enable-dxva2 –enable-avisynth –enable-libmfx
      libavutil      55. 78.100 / 55. 78.100
      libavcodec     57.107.100 / 57.107.100
      libavformat    57. 83.100 / 57. 83.100
      libavdevice    57. 10.100 / 57. 10.100
      libavfilter     6.107.100 /  6.107.100
      libswscale      4.  8.100 /  4.  8.100
      libswresample   2.  9.100 /  2.  9.100
      libpostproc    54.  7.100 / 54.  7.100
    Invalid Parameter - -interlace
    [mjpeg @ 000000000046b0a0] EOI missing, emulating
    Input #0, avi, from 'Basler acA1920-155uc (21644989)_20180930_043754312.avi’:
      Duration: 00:00:50.02, start: 0.000000, bitrate: 1356 kb/s
        Stream #0:0: Video: mjpeg (MJPG / 0x47504A4D), yuvj422p(pc, bt470bg/unknown/unknown), 1920x2, 1318 kb/s, 200 fps, 200 tbr, 200 tbn, 200 tbc
    Stream mapping:
      Stream #0:0 -> #0:0 (mjpeg (native) -> mjpeg (native))
    Press [q] to stop, [?] for help
    Output #0, image2pipe, to ‘pipe:’:
      Metadata:
        encoder         : Lavf57.83.100
        Stream #0:0: Video: mjpeg, yuvj422p(pc), 1920x2, q=2-31, 200 kb/s, 200 fps, 200 tbn, 200 tbc
        Metadata:
          encoder         : Lavc57.107.100 mjpeg
        Side data:
          cpb: bitrate max/min/avg: 0/0/200000 buffer size: 0 vbv_delay: -1
    av_interleaved_write_frame(): Invalid argument
    Error writing trailer of pipe:: Invalid argument
    frame=    1 fps=0.0 q=1.6 Lsize=       0kB time=00:00:00.01 bitrate= 358.4kbits/s speed=0.625x
    video:0kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000000%
    Conversion failed!

如果我将高度提高一点,我将不再收到“[mjpeg @ 000000000046b0a0] EOI missing, emulating” - 错误。然而,整个事情只适用于 <2px 高/宽的素材。

编辑:哦,是的,我也可以使用ffmpeg -i file.mpg -r 1/1 $filename%03d.bmpffmpeg -i file.mpg $filename%03d.bmp从 MJPEG/RAW 流中提取所有帧。然而,这是我不想采取的额外步骤。(仅删除一个 30000 jpgs 的文件夹就需要 2 分钟……)

有人能想到管道方法的可行解决方案或完全不同的替代方法吗?

4

2 回答 2

3

我生成了一个 10,000 帧的示例视频,并进行了一些测试。显然,我的机器与您的机器规格不同,因此结果无法直接比较,但我发现让ffmpeg转置视频并将其作为原始 RGB24 帧传输到ImageMagick会更快。

我发现我可以在 10.3 秒内将 10 秒的电影转换为 20,000x1920 像素的 JPEG,如下所示:

ffmpeg -threads 4 -y -i video.mov -frames 10000 -vf "transpose=1" -f image2pipe -vcodec rawvideo -pix_fmt rgb24 - | convert -depth 8 -size 2x1920 rgb:- +append result.jpg

生成的图像如下所示:

在此处输入图像描述


我生成了这样的视频CImg。基本上它只是在框架上连续绘制一个红色/绿色/蓝色的splodge,直到它碰到右边缘,然后再次从左边缘开始:

#include <iostream>
#include "CImg.h"

using namespace std;
using namespace cimg_library;

int main()
{
   // Frame we will fill
   CImg<unsigned char> frame(1920,2,1,3);

   int width =frame.width();
   int height=frame.height();

   // Item to draw in frame - created with ImageMagick
   // convert xc:red xc:lime xc:blue +append -resize 256x2\! splodge.ppm
   CImg<unsigned char> splodge("splodge.ppm");

   int offs  =0;

   // We are going to output 10000 frames of RGB raw video
   for(int f=0;f<10000;f++){
      // Generate white image
      frame.fill(255);

      // Draw coloured splodge at correct place
      frame.draw_image(offs,0,splodge);
      offs = (offs + 1) % (width - splodge.width());

      // Output to ffmpeg to make video, in planar GBR format
      // i.e. run program like this
      // ./main | ffmpeg -y -f rawvideo -pixel_format gbrp -video_size 1920x2 -i - -c:v h264 -pix_fmt yuv420p video.mov
      char* s=reinterpret_cast<char*>(frame.data()+(width*height));   // Get start of G plane
      std::cout.write(s,width*height);                                // Output it
      s=reinterpret_cast<char*>(frame.data()+2*(width*height));       // Get start of B plane
      std::cout.write(s,width*height);                                // Output it
      s=reinterpret_cast<char*>(frame.data());                        // Get start of R plane
      std::cout.write(s,width*height);                                // Output it
   }
}

splodge 为 192x2 像素,如下所示:

在此处输入图像描述

于 2018-10-01T18:25:52.847 回答
3

我对此进行了另一次尝试,看看我是否可以通过几种不同的方式来加快我的其他答案 - 因此是不同的答案。我使用了在另一个答案中生成的相同合成视频剪辑进行测试。

我没有将 2x1920 扫描线传递给ImageMagick以将其附加在一起并以 JPEG 格式写入,而是执行了以下操作:

  • 在 C++ 程序中预先创建完整的输出帧,然后在每次迭代时循环读取 2x1920 扫描线并将其填充到输出帧中的正确位置,并且

  • 读取整个序列后,使用 turbo-jpeg 将其压缩为 JPEG 并将其写入磁盘。

因此,不再需要ImageMagick 。整个程序现在运行大约 1.3 秒,而不是通过ImageMagick运行的 10.3 秒。

这是代码:

////////////////////////////////////////////////////////////////////////////////
// stitch.cpp
// Mark Setchell
//
// Read 2x1920 RGB frames from `ffmpeg` and stitch into 20000x1920 RGB image.
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <unistd.h>
#include <turbojpeg.h>

using namespace std;

int main()
{
   int frames = 10000;
   int height = 1920;
   int width  = frames *2;

   // Buffer in which to assemble complete output image (RGB), e.g. 20000x1920
   unsigned char *img = new unsigned char [width*height*3];

   // Buffer for one scanline image 1920x2 (RGB)
   unsigned char *scanline = new unsigned char[2*height*3];

   // Output column
   int ocol=0;

   // Read frames from `ffmpeg` fed into us like this:
   // ffmpeg -threads 4 -y -i video.mov -frames 10000 -vf "transpose=1" -f image2pipe -vcodec rawvideo -pix_fmt rgb24 - | ./stitch
   for(int f=0;f<10000;f++){
      // Read one scanline from stdin, i.e. 2x1920 RGB image...
      ssize_t bytesread = read(STDIN_FILENO, scanline, 2*height*3);

      // ... and place into finished frame
      // ip is pointer to input image
      unsigned char *ip = scanline;
      for(int row=0;row<height;row++){
         unsigned char *op = &(img[(row*width*3)+3*ocol]);
         // Copy 2 RGB pixels from scanline to output image
         *op++ = *ip++; // red
         *op++ = *ip++; // green
         *op++ = *ip++; // blue
         *op++ = *ip++; // red
         *op++ = *ip++; // green
         *op++ = *ip++; // blue
      }
      ocol += 2; 
   }

   // Now encode to JPEG with turbo-jpeg
   const int JPEG_QUALITY = 75;
   long unsigned int jpegSize = 0;
   unsigned char* compressedImage = NULL;
   tjhandle _jpegCompressor = tjInitCompress();

   // Compress in memory
   tjCompress2(_jpegCompressor, img, width, 0, height, TJPF_RGB,
          &compressedImage, &jpegSize, TJSAMP_444, JPEG_QUALITY,
          TJFLAG_FASTDCT);

   // Clean up
   tjDestroy(_jpegCompressor);

   // And write to disk
   ofstream f("result.jpg", ios::out | ios::binary);
   f.write (reinterpret_cast<char*>(compressedImage), jpegSize);
}

笔记:

注意 1:为了预先分配输出图像,程序需要提前知道有多少帧 - 我没有参数化,我只是硬编码了 10,000,但它应该很容易更改。

确定视频序列中帧数的一种方法是:

ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_frames -of default=nokey=1:noprint_wrappers=1 video.mov

注意 2:请注意,我使用几个开关编译代码以提高性能:

g++-8 -O3 -march=native stitch.cpp -o stitch

注意 3:stdin如果您在 Windows 上运行,您可能需要在执行以下操作之前以二进制模式重新打开:

read(STDIN_FILENO...)

注意 4:如果您不想使用 turbo-jpeg,您可以在主循环结束后删除所有内容,只需通过管道将NetPBM PPM图像发送到ImageMagick并让它进行 JPEG 写入。这看起来非常粗略:

writeToStdout("P6 20000 1920 255\n");
writeToStdout(img, width*height*3);

然后你会运行:

ffmpeg ... | ./stitch | magick ppm:-  result.jpg
于 2018-10-02T14:32:07.297 回答