Мой опыт программирования на с++ насчитывает 5 месяцев, до этого времени я около двух лет разрабатывал приложения для мобильных операционных систем. В один момент мне это надоело, и я решил, что пора начать осуществлять свою юношескую мечту — стать разработчиком игр. И я немного сменил направление движения своей карьеры.

Вот как-то я сидел и думал, что бы мне написать. Я выбрал для себя 16 программок, несколько раз подбросил монетку, и жребий указал мне на программку получения гифки из видео. Кто хочет увидеть дилетантский крестовый код — прошу под кат.

Принцип и структура очень простые. На вход нашей программе подается два параметра: путь до нашего видео и имя выходной гифки. Создаем экземпляр нашего простенького класса и вызываем метод создания гифки (забыл сказать, что будем использовать библиотеки OpenCv и Magick++).

Описание класса:

class Video{
public:
  Video(const std::string& video_n, const std::string& gif_n){ video_name = video_n; output_gif_name = gif_n; };
  ~Video() {};
 void create_gif();
private:
  std::string video_name;
  std::string output_gif_name;
  std::vector<Mat> frames;
  std::vector<Magick::Image> Magick_frames;
  void extract_frames();
  static inline Magick::Image mat_2_magick(cv::Mat& src);
};

Конструктор содержит два параметра — наши входные параметры из командной строки.

std::vector frames — свойство, которое содержит кадры нашего видео в структуре cv::Mat, std::vector<Magick::Image>.
Magick_frames — это свойство хранит преобразованные кадры.

Метод extract_frames вытягивает кадры из видео. Алгоритм очень простой и заключается в следующем:

1) создаем экземпляр класса cv::VideoCapture (класс для видео-захвата)
2) определяем счетчик кадров и запускаем цикл где будем обрабатывать видео
3) если наш счетчик не попадает в рамки нашего видео — выходим, иначе устанавливаем кадр VideoCapture равный нашему счетчику (свойство CV_CAP_PROP_POS_FRAMES)
4) пробуем считать кадр и если получается, то добавляем его в std::vector frames и увеличиваем счетчик кадров на 10 (каждый десятый кадр, что бы облегчить гифку)

void Video::extract_frames(){
  try{
    VideoCapture cap(this->video_name);
    if (!cap.isOpened()) CV_Error(CV_StsError, "Can't open video file");
    double fIdx = 0;
    double frnb(cap.get(CV_CAP_PROP_FRAME_COUNT));
   // std::cout << "frame count = " << frnb<< std::endl;
    for (;;){
     // std::cout<<"frame : "<<fIdx<<std::endl;
      Mat frame;
      if (fIdx < 0 || fIdx >= frnb) break;
      cap.set(CV_CAP_PROP_POS_FRAMES, fIdx);
      bool success = cap.read(frame);
      if (success) { this->frames.push_back(frame); fIdx = fIdx + 10;}
      else break;
    }
  }
  catch(cv::Exception& e){
    cerr << e.msg << std::endl;
    exit(1);
  }
}

В главно методе create_gif() мы сначала выполняем метод получения кадров, потом преобразуем эти кадры к структуре Magic::Image и записываем выходной файл.

Это метод для конвертации cv::Mat к Magic::Image. Это надо, потому что opencv не умеет работать с gif файлами, я нашел кучу библиотек, но решил остановиться на magick++.

*Если честно, я не ощутил компрессии и уменьшения качества до 50%, выходной файл, как с этими, так и без этих параметров весил одинаково*

inline Magick::Image Video::mat_2_magick(cv::Mat& src) {
  Magick::Image mgk(src.cols, src.rows, "BGR", Magick::CharPixel, (char *)src.data);
  mgk.compressType(JPEGCompression);
  mgk.quality(50);
  return mgk;
}

void Video::create_gif() {
  extract_frames();//так скорей всего не очень правильно делать
  for(std::vector<cv::Mat>::iterator frame = this->frames.begin(); frame != this->frames.end(); ++frame){
    this->Magick_frames.push_back(Video::mat_2_magick(*frame));
  }
  Magick::writeImages(this->Magick_frames.begin(), this->Magick_frames.end(), this->output_gif_name);
}

Немного расскажу про одну проблему, с которой я провозился до 7 утра. Я в этом проекте использовал CMake и я не указал флаги линковки и компиляции. Было довольно обидно, что я это упустил. Вот часть Cmake файла:

find_file(MAGICK_CONFIG_EXE "Magick++-config" PATHS
    "/usr/bin"
    "/usr/local/bin"
)
if (MAGICK_CONFIG_EXE)
  message(STATUS "Found Image Magick++ libaries -- Enabling MagickPainter.")
  execute_process(COMMAND Magick++-config --cppflags OUTPUT_VARIABLE Magick_CPP_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
  execute_process(COMMAND Magick++-config --cxxflags OUTPUT_VARIABLE Magick_CXX_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
  execute_process(COMMAND Magick++-config --ldflags  OUTPUT_VARIABLE Magick_LD_FLAGS  OUTPUT_STRIP_TRAILING_WHITESPACE)
  execute_process(COMMAND Magick++-config --libs     OUTPUT_VARIABLE Magick_LIBS      OUTPUT_STRIP_TRAILING_WHITESPACE)
  
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Magick_CPP_FLAGS} ${Magick_CXX_FLAGS}")
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${Magick_LIBS} ${Magick_LD_FLAGS}")
  
  
# remove_definitions(-DUSE_MAGICK_PAINTER)
endif (MAGICK_CONFIG_EXE)

Все есть на гите.

P.S.: первый пост, не хочу просить о снисхождении. Наоборот: хочется, чтобы вы оценили по всей строгости. Надеюсь, вы напишете, что я накодил не так и что-нибудь посоветуете.

С уважением, Гарри.

Комментарии (17)


  1. JIghtuse
    22.10.2015 14:36
    +1

    Забавная утилитка, побаловался.

    Огорчу, что ваше решение с поиском Magick++ у меня не заработало. В Debian/Ubuntu скрипт Magick++-config теперь прячут глубоко. Однако, вроде пишут, что теперь принято использовать pkg-config. Я перекроил ваш CMakeLists.txt, чтобы работало с ним.

    По коду — если вы не против C++11/C++14 (особых причин не должно быть =) ), то можно вместо итератора использовать range-for. Ну и списки инициализации в конструкторах полезны, лучше их использовать.

    Ловите два PR.


    1. garrydvaraza
      22.10.2015 14:48

      Спасибо. У меня на маке ваш cmake не взлетел). Оставлю свой.


      1. JIghtuse
        22.10.2015 14:52

        Это потому что вы Magick++ скачали, судя по "/usr/local". Попробуйте пакет поставить, тогда pkg-config будет в курсе, где что находится. Если у вас Linux, конечно. В Debian название такое: libmagick++-dev

        PS. Вы оба коммита вмёржили, если что =)


      1. JIghtuse
        22.10.2015 14:58

        А, только теперь заметил словосочетание «на маке». Вероятнее всего, у вас нет pkg-config, или он не может найти Magick++.


        1. garrydvaraza
          22.10.2015 15:14

          Именно, все равно спасибо за с++11 =)


  1. MasMaX
    22.10.2015 16:12

    А не подскажите как с производительностью у extract_frames? Есть идея выдирать кадры и отрисовывать из по-одному на экране (очень своеобразный плеер нужен).


    1. garrydvaraza
      22.10.2015 16:18

      Она расчитана на небольшие видео ~100 mb. И именно нужно получение на выходе gif файла.


  1. Denai
    22.10.2015 20:15

    Современные технологии идут к обратному — gif преобразовывать в видео, чтоб весил меньше и работал лучше. Размеры не сравнивали исходного и итогового?


    1. garrydvaraza
      22.10.2015 20:38

      Сравнивал, гифка получается больше, но это под определенную задачу утилита:) У друзей есть сайт, на котором видео с двачей парсятся и я туда приделаю кнопочку, которая будет выплевывать гифку.
      PS а как нормально сделать из гифки видео, если она не хранит голосовую дорожку?


      1. Denai
        22.10.2015 21:32

        А зачем в итоговом видео звуковая дорожка? Пример. 3.4 MB против 46.7 MB. Просто «гифка», которая на самом деле видео. А вот другой пример. Вы знаете как к гифке приделать паузу и воспроизведение по клику? gif хорош, но не везде где его используют, в частности для видео он плох.


      1. Eternalko
        22.10.2015 21:57

        Denai говорит о том что gif анимация это очень плохо.
        gifs-make-me-puke.mp4


        1. Denai
          22.10.2015 23:10

          Не везде


  1. Eternalko
    22.10.2015 21:47
    +7

    Быстро протестил как дела у ffmpeg:

    Рабочий bash
    ffmpeg -y -t 10 -i SampleVideo_1080x720_10mb.mp4 	-vf fps=10,scale=320:-1:flags=lanczos,palettegen gifPallet.png
    
    
    ffmpeg -y -t 10 -i SampleVideo_1080x720_10mb.mp4 -i gifPallet.png -filter_complex 	"fps=10,scale=320:-1:flags=lanczos[x];[x][1:v]paletteuse" output2.gif
    



    1. TrueBers
      23.10.2015 06:36
      +3

      Во-во. Тащить за собой гигантский opencv, который, в свою очередь, использует тот же ffmpeg, как-то, мягко говоря, оверкилл… почему бы не использовать чистый ffmpeg?


      1. Eternalko
        23.10.2015 14:56
        +2

        Я так понял что это проект 4 lulz & exp


        1. garrydvaraza
          23.10.2015 19:34

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


          1. Eternalko
            24.10.2015 01:56
            +1

            Кто как не мы, когда как не сейчас.