Использование ImageMagick для эффективного сшивания изображения линейного сканирования - PullRequest
0 голосов
/ 30 сентября 2018

Я ищу альтернативы для камер с линейным сканированием, которые будут использоваться в спортивном хронометраже, или, скорее, в той части, где нужно определить местоположение.Я обнаружил, что обычные промышленные камеры могут легко соответствовать скорости коммерческих решений камеры при> 1000 кадров в секунду.Для моих потребностей, как правило, точность синхронизации не важна, но относительное размещение спортсменов.Я подумал, что мог бы использовать для этой цели одну из самых дешевых промышленных камер Basler, IDS или любую другую индустриальную камеру.Конечно, существуют камеры с линейным сканированием, которые могут работать со скоростью, превышающей несколько тысяч кадров в секунду (или Гц), но есть возможность приобрести камеры с зонным сканированием, которые могут выполнять требуемые 1000-3000 кадров в секунду менее чем за 500 €.

Моим святым Граалем, конечно же, будут возможности композиции изображений FinishLynx (или любой другой системы линейного сканирования), близкие к реальному времени, в основном эта часть: https://youtu.be/7CWZvFcwSEk?t=23s

Весь процесс, о котором я думал в качестве альтернативыis:

  • Используйте Basler Pylon Viewer (или другое программное обеспечение) для записи изображений шириной 2 пикселя с самой высокой скоростью чтения камеры.Для камеры, которой я сейчас пользуюсь, это означает, что ее нужно повернуть на бок, а высоту нужно уменьшить, поскольку это единственный способ, которым она будет читать 1920x2px frames @> 250fps
  • Создайте программу или пакетный скрипт, который затем соединит эти 1920x2px кадры вместе, например, в одну секунду записи 1000 * 1920x2px кадров, что означает полученное изображение с разрешением 1920x2000px (по горизонтали x по вертикали).
  • Наконец, используя ту же программу или другой способ, просто поверните изображение, чтобы оно отражало положение камеры, таким образом, получая изображение с разрешением 2000x1920px (снова по горизонтали x по вертикали)
  • Откройте изображение в программе для анализа (в настоящее время ImageJ) для быстрого анализа результатов

Я не программист, но эточто я смог собрать, просто используя пакетные сценарии, с помощью stackoverflow, конечно.

  • В настоящее время запись всего 1Например, 0 секунд на диск в виде потока raw / mjpeg (avi / mkv) можно выполнять в режиме реального времени.
  • Запись отдельных кадров в формате TIFF или BMP или использование FFMPEG для их сохранения в формате PNG или JPG занимает ~20-60 секунд. Затем добавление и поворот занимает еще ~ 45-60 секунд . Все это должно быть достигнуто менее чем за 60 секунд для 10 секунд отснятого материала (1000-3000 кадров в секунду при 10 с = 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.

Эта команда работает и заставляет меня10000 кадров, закодированных примерно за 30 секунд на i5-2520m (хотя большая часть обработки, по-видимому, использует только один поток, поскольку он работает с использованием 25% процессора).Это полученное изображение: https://i.imgur.com/OD4RqL7.jpg (19686x1928px)

Однако, поскольку запись в кадры TIFF с использованием Basler Pylon Viewer занимает намного больше времени, чем запись видеопотока 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 отсутствует, эмулируя».Однако все это будет работать только с </ 2px высоким / широким кадром. </p>

edit: О да, я также могу использовать ffmpeg -i file.mpg -r 1/1 $filename%03d.bmp или ffmpeg -i file.mpg $filename%03d.bmp для извлечения всех кадров из потока MJPEG / RAW.Однако это дополнительный шаг, который я не хочу делать.(простое удаление папки с 30000 jpgs занимает 2 минуты в одиночку…)

Может кто-нибудь придумать рабочее решение для метода трубопровода или совершенно другой альтернативный способ обработки этого?

Ответы [ 2 ]

0 голосов
/ 02 октября 2018

Я еще раз попробовал это сделать, чтобы проверить, смогу ли я ускорить свой другой ответ, выполнив несколько разных действий - отсюда и другой ответ.Для тестирования я использовал тот же синтетический видеоклип, который сгенерировал в другом ответе.

Вместо того, чтобы передавать 2x1920 строк развертки в ImageMagick , чтобы он мог сложиться вместе и записать в формате JPEG, я сделалследующее:

  • создавал полный выходной кадр заранее в программе на C ++, а затем зацикливал чтение в строке развертки 2x1920 на каждой итерации и вставлял это в правильную позицию в выходном кадре,и

  • когда вся последовательность была прочитана, сжал ее в JPEG с помощью turbo-jpeg и записал ее на диск.

Как таковой, ImageMagick больше не требуется.Вся программа теперь работает примерно за 1,3 секунды, а не за 10,3 секунды через ImageMagick .

Вот код:

////////////////////////////////////////////////////////////////////////////////
// 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: Если вы работаете в Windows, вам может потребоваться повторно открыть stdin в двоичном режиме перед выполнением:

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
0 голосов
/ 01 октября 2018

Я сгенерировал пример видео в 10 000 кадров и провел несколько тестов.Очевидно, что моя машина не совпадает с вашей спецификацией, поэтому результаты не сопоставимы напрямую, но я обнаружил, что быстрее ffmpeg преобразовать видео и передать его в ImageMagick как необработанные кадры RGB24.

Я обнаружил, что могу преобразовать 10-секундный фильм в JPEG с разрешением 20 000 x 1920 пикселей за 10,3 с:

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

Полученное изображение выглядит следующим образом:

enter image description here


Я сгенерировал видео, как это с CImg.По сути, он просто рисует красный, зеленый, синий пятна последовательно по всему кадру до тех пор, пока не достигнет правого края, а затем снова начинает с левого края:

#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
   }
}

Это пятно имеет размер 192x2 пикселей и выглядит следующим образом:

enter image description here

...