Skip to main content

FFmpeg and Imagick

Ghép mặt người vào 1 video cụ thể.

Có nhiều trang như http://www.jibjab.com/ cho phép người dùng đưa mặt mình vào một số video ngộ nghĩnh như rockstar, dance video ... Thậm chí bạn có thể thêm nhiều mặt người vào video để thành cặp hay nhóm.

Kỹ thuật làm trang này chủ yếu dựa trên Flash, chi tiết có thể tra GG.

Mình muốn giới thiệu một cách khác không dùng Flash và có thể chạy trên mọi thiết bị thông qua web.
 Về kỹ thuật video ghép từ nhiều khung hình, thường là 25fps (frames per second, hay video nhanh hơn là 30fps. Do vậy, chỉ cần ghép mặt mình vào từng khung hình hoặc đè lên theo từng thời điểm là OK. Công cụ đã có sẵn FFmpeg ghép ảnh thành video, ImageMagick chỉnh sửa ảnh cơ bản. Và một Tool quan trọng đó là  Adobe AfterEffect (AE) trong bộ Adobe Suites. Tool này chuyên để chỉnh sửa video. Nó có 1 chức năng là Motion Tracking detect vật thể di chuyển theo khung hình.

Như vậy là về kỹ thuật là có thể làm được, kết quả vd:

https://drive.google.com/file/d/0B9XwFe7bHCQ0RE94Qm1yeGJKRGs/view?usp=sharing

Video này không phải video chuẩn (mình cũng không nhớ để đâu) nhưng nếu lúc bạn crop face chuẩn thì video sẽ đẹp hơn. Lúc chọn mặt sẽ có bước xoay, zoom sao cho mặt vừa với khung mẫu.

Đại khái là vậy, chi tiết mình sẽ cập nhật sau.

dữ liệu test.
https://drive.google.com/folderview?id=0B9XwFe7bHCQ0clB5RHJ2NDVTMjQ&usp=sharing


Source code (mình để nhiều chỗ nên sẽ tổng hợp sau) đại khái như sau:

<?php

    $source = 'Transform_data.xml';



    $xmlstr = file_get_contents($source);

    $xmlcont = @simplexml_load_string($xmlstr); 



    $bg = new ImagickPixel('none');

    $face_name = $_GET['face_name'];

    $face_dir = basename($face_name, ".png"); 

    new_working_dir($face_dir);



    $face = new Imagick();

    $face->readImage("./faces/".$face_name);

    $face->resizeImage(191,232,imagick::FILTER_LANCZOS,1);

    $face_w = $face->getimagewidth();

    $face_h = $face->getimageHeight();





    $start = time();

    $quality = "low_png_frm"; // high frame quality, low_png_frm for low quality

    

    

    foreach($xmlcont as $frame) 

    {

         if((intval($frame->time) >= 44) && (intval($frame->time) <= 79)) { // part1

            $imgFace = clone $face;

            $imgFace = transform(-2, 2.42*$face_w, 2.42*$face_h, $imgFace);

            // $face = new Imagick("RESULT/frame_0".$frame->time. ".png");

            $frm = new Imagick("low_png_frm/frame_0".$frame->time. ".png");

            $frm->compositeImage($imgFace, imagick::COMPOSITE_DSTOVER, intval($frame->x +92.4 -241), intval($frame->y -20.8 -277)); 

            $img_outp = $face_dir."/frame_0". $frame->time. ".png";

            $frm->setImageCompressionQuality(40);

            $frm->stripImage();    

            $frm->writeImage(''.$img_outp); 

        } 

        

        else if((intval($frame->time) >= 80) && (intval($frame->time) <= 125)) { // part2

            $imgFace = clone $face;

            $imgFace = transform(-2, 2.42*$face_w, 2.42*$face_h, $imgFace);

            $frm = new Imagick("low_png_frm/frame_0".$frame->time. ".png");

            // $face = new Imagick("RESULT/frame_0".$frame->time. ".png");

            $frm->compositeImage($imgFace, imagick::COMPOSITE_DSTOVER, intval($frame->x +62 -241), intval($frame->y -32.3 -277)); 

            $img_outp = $face_dir."/frame_0". $frame->time. ".png";

            $frm->setImageCompressionQuality(40);

            $frm->stripImage();    

            $frm->writeImage(''.$img_outp); 

        } else if((intval($frame->time) >= 132) && (intval($frame->time) <= 196)) { // part3  132 (196)

            $anchor_x = 43.7;

            $anchor_y = 56.6;



            $x_zoom = $frame->scl_x/100;

            $y_zoom = $frame->scl_y/100;

            $scl_x = floatval($x_zoom)*36.2/100 * $face_w;

            $scl_y = floatval($y_zoom)*39.5/100 * $face_h;

            $scl_d = sqrt($scl_x*$scl_x/4 + $scl_y*$scl_y/4);



            $scl_D = sqrt($anchor_x*$anchor_x*$x_zoom*$x_zoom + $anchor_y*$anchor_y*$y_zoom*$y_zoom);

            $diameter = abs($scl_D - $scl_d);   // 



            $base_angle = atan($scl_y/$scl_x);

            $base_img_rotate = 0/180 *pi(); // Caused by when generate data from AE

            $rt = floatval($frame->rt)/180 *pi();

            $delta_rot = $base_angle + $rt + $base_img_rotate;



            $delta_x = $diameter * cos($delta_rot);

            $delta_y = $diameter * sin($delta_rot);



            $new_anchor_x = $frame->x + $delta_x;  // anchor point is in center of face image

            $new_anchor_y = $frame->y + $delta_y;  





            $imgFace = clone $face;

            $imgFace = transform(floatval($frame->rt), $scl_x, $scl_y, $imgFace);

            $shift_x = sin($frame->rt/180 * pi()) *$scl_y;  // X pos shift caused by rotate

            $shift_y = sin($frame->rt/180 * pi()) *$scl_x;



            $imgBG = new Imagick("low_png_frm/frame_0".$frame->time. ".png");

            if($frame->rt > 0) {

                $imgBG->compositeImage($imgFace, imagick::COMPOSITE_DSTOVER, intval($new_anchor_x-$shift_x), intval($new_anchor_y)); 

            } else {

                $imgBG->compositeImage($imgFace, imagick::COMPOSITE_DSTOVER, intval($new_anchor_x), intval($new_anchor_y +$shift_y)); 

            }

            $img_outp = $face_dir."/frame_0". $frame->time. ".png";

            $imgBG->setImageCompressionQuality(40);

            $imgBG->stripImage();    

            $imgBG->writeImage(''.$img_outp); 

        } else if((intval($frame->time) >= 377) && (intval($frame->time) <= 455)) { // part4

            $x_zoom = $frame->scl_x/100;

            $y_zoom = $frame->scl_y/100;

            $scl_x = floatval($x_zoom)*36.2/100 * $face_w;

            $scl_y = floatval($y_zoom)*39.5/100 * $face_h;

            $scl_d = sqrt($scl_x*$scl_x/4 + $scl_y*$scl_y/4);

            $diameter = $scl_d;



            $base_angle = atan($scl_y/$scl_x);

            $rt = floatval($frame->rt)/180 *pi();

            $delta_rot = $base_angle + $rt;



            $delta_x = $diameter * cos($delta_rot);

            $delta_y = $diameter * sin($delta_rot);



            $new_anchor_x = $frame->x - $delta_x;  // anchor point is in center of face image

            $new_anchor_y = $frame->y - $delta_y;  



            $imgFace = clone $face;

            $imgFace = transform(floatval($frame->rt), $scl_x, $scl_y, $imgFace); //floatval($frame->rt)

            $shift_x = sin($frame->rt/180 * pi()) *$scl_y;  // X pos shift caused by rotate

            $shift_y = sin($frame->rt/180 * pi()) *$scl_x;



            $imgBG = new Imagick("low_png_frm/frame_0".$frame->time. ".png");



            if($frame->rt > 0) {

                $imgBG->compositeImage($imgFace, imagick::COMPOSITE_DSTOVER, intval($new_anchor_x-$shift_x+5), intval($new_anchor_y-5)); 

            } else {

                $imgBG->compositeImage($imgFace, imagick::COMPOSITE_DSTOVER, intval($new_anchor_x+5), intval($new_anchor_y +$shift_y-5)); 

            }

            $img_outp = $face_dir."/frame_0". $frame->time. ".png";

            $imgBG->setImageCompressionQuality(40);

            $imgBG->stripImage();

            $imgBG->writeImage(''.$img_outp); 

        }

        



    }

    

    

    // compose($face_dir);

    // exec("/opt/local/bin/ffmpeg -framerate 30 -i RST/frame_0%03d.png -s:v 1280x720 -c:v libx264 -profile:v high -crf 23 -pix_fmt yuv420p -r 30 -vb 12M  RST/movies_2.mp4 2>&1", $o, $v);

    $end = time();

    echo " <br/> Time: ". ($end -$start). "<br/>";

    echo $end;



    function transform($rotate, $scale_x, $scale_y, $face) {

        $scale_x = intval($scale_x);          

        $scale_y = intval($scale_y);



        $bg = new ImagickPixel('none');

        // $imgFace = $face;

        $face->scaleImage($scale_x, $scale_y, false);

        $face->rotateImage($bg, $rotate); 

        return $face;

    }

    function compose($face_dir) {

        $ffmpeg = "/usr/bin/ffmpeg";

        $name = rand(1, 99999);

        exec("cp -r ./low_buffer/*.png $face_dir/ 2>&1", $out_cp, $log_cp);

        print_r($out_cp[0]);

        echo exec("chmod 777 $face_dir/*");

        exec("$ffmpeg -framerate 30 -i $face_dir/frame_0%03d.png -preset ultrafast -s:v 1280x720 -c:v libx264 -profile:v high -crf 23 -pix_fmt yuv420p -r 30 -vb 12M  $face_dir/movies_$name.mp4 1>&2", $o, $v);

        print_r($o[0]);

        exec("$ffmpeg -i $face_dir/movies_$name.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts $face_dir.ts", $o2, $v2);

        print_r($o2);

        exec("$ffmpeg -i \"concat:intermediate1.ts|$face_dir.ts|intermediate3.ts\" -c copy -bsf:a aac_adtstoasc -preset ultrafast output_$name.mp4", $o3, $v3);

        print_r($o3[0]);

        exec("rm -rf $face_dir/");

        exec("$ffmpeg -i output_$name.mp4 -i audio.mp3  -strict -2 -preset ultrafast Final_$name.mp4", $out_aud, $v_aud);

        print_r($out_aud);

        print_r($o3);



        echo "xong !";

        echo "<a href='Final_$name.mp4'>Movie</a>";

    }

    function add_buffer_frame($face_dir) {

        exec("cp -r low_buffer/* $face_dir 2>&1", $o, $v);

    }

    function new_working_dir($dir_name) {

        exec("mkdir -p $dir_name 2>&1", $o, $v);

        print_r($o);

    }



?>



Dữ liệu Transform data XML từ Adobe After Effect lưu vị trí mặt tại mỗi khung hình:

<?xml version="1.0" encoding="UTF-8"?>
<frames>
    <frame><time>044</time><x>591.562</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>045</time><x>591.562</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>046</time><x>591.562</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>047</time><x>591.562</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>048</time><x>591.562</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>049</time><x>590.6</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>050</time><x>590.6</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>051</time><x>590.6</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>052</time><x>590.6</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>053</time><x>590.6</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>054</time><x>589.6</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>055</time><x>589.6</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame>
    <frame><time>056</time><x>589.6</x><y>334.75</y><scl_x>100</scl_x><scl_y>100</scl_y><rt>0</rt></frame> ....




File:  https://drive.google.com/file/d/0B9XwFe7bHCQ0YVZ6eUJmWE1Mc1k/view?usp=sharing

Update 1 Mar 17, 2018:

I update source code to https://github.com/FIF/cropzoom_laravel_jibjab
The web part have problem with update laravel from 4.1 to 5.6, I will fix and update later.
For sample video processing, you can clone source code and try run this URL:
http://localhost/Hannibal/cropzoom_laravel_jibjab2/resources/material/compose.php?face_name=AT4.png
Since I use Apache and put source code at /var/www/Hannibal/...
face_name is GET variable for sample face to process video, it accept Face_1.png to Face_11.png
Please see processing video file (compose.php) in resources folder for more detail.

I will update remain documents about source code and Adobe After Effect latter.

Comments

  1. ffmpeg -i music.wav -f mp2 music.mp3

    wget http://littlewing.wc.lt/sounds/music.wav

    ReplyDelete
  2. # first convert an image sequence to a movie
    ffmpeg -sameq -i %03d.jpg output.mp4

    # ... and then convert the movie to a GIF animation
    ffmpeg -i output.mp4 -pix_fmt rgb24 -s qcif -loop_output 0 output.gif

    ReplyDelete
  3. convert $(for ((a=0; a<700; a++)); do printf -- "-delay 10 name%s.png " $a; done;) result.gif

    ReplyDelete
  4. http://www.imagemagick.org/script/import.php

    ReplyDelete
  5. Hi, anyone have this example working? thanks!

    ReplyDelete
    Replies
    1. Wait me a bit. I will rebuild example and reply to you soon.
      Currently I am quite busy.

      Delete
    2. Thanks a lot Nick! that will really help me!
      have a great day!
      Rod

      Delete
    3. Hi Rodrigo, here is the PHP file for processing video: https://github.com/dungnv53/Laratype/blob/master/app/lib/LaravelPrototype/Common/VideoProcessing.php
      The whole simple website is here https://github.com/dungnv53/Laratype
      Because of there are some config, setup in order to make this site work, so I only focus on video processing path.

      Delete
    4. This comment has been removed by the author.

      Delete
    5. @Rodrigo I will update this post and try to rebuild project then provide you detail guide.
      The basic idea is simple. We have a face image, we have a short video.
      Then firstly, we cut video to frame (ie. 30s x 30fps = 900 frame).
      Second, we merge (compose) a face to each frame and then combined to a video.
      The position of face in each frame is stored in an array or an XML file. The position value is created using Adobe After Effect tracking tool or something like this.
      I will provide detail for archive this task latter. There are may be another way to done this ie. Fake Deep (but require a lot of sample data and computing power).
      I code in bash or PHP. PHP is only for Laravel to connect with other feature like cut user face from facebook/uploaded image, post video to youtube, facebook...

      Delete
    6. Hello Nick!!
      Thank you very much for your answers! they really help me a lot !! Thank you for your selfless help! I will investigate a little more with the new info that you send me, and I will be looking at the updates that you send.
      Thank you!
      PS: can I contact you privately?

      Delete
    7. Please add my Skype live: vandung_17
      Phone 841205029223

      Delete
    8. My skype name: dzung nguyen
      Quote: 925 trap

      Delete
    9. hi, i will sent you and email and also i already add you to skype
      thanks!

      Delete

Post a Comment

Popular posts from this blog

Rand mm 10

https://stackoverflow.com/questions/2447791/define-vs-const Oh const vs define, many time I got unexpected interview question. As this one, I do not know much or try to study this. My work flow, and I believe of many programmer is that search topic only when we have task or job to tackle. We ignore many 'basic', 'fundamental' documents, RTFM is boring. So I think it is a trade off between the two way of study language. And I think there are a bridge or balanced way to extract both advantage of two method. There are some huge issue with programmer like me that prevent we master some technique that take only little time if doing properly. For example, some Red Hat certificate program, lesson, course that I have learned during Collage gave our exceptional useful when it cover almost all topic while working with Linux. I remember it called something like RHEL (RedHat Enterprise Linux) Certificate... I think there are many tons of documents, guide n books about Linux bu

Martin Fowler - Software Architecture - Making Architecture matter

  https://martinfowler.com/architecture/ One can appreciate the point of this presentation when one's sense of code smell is trained, functional and utilized. Those controlling the budget as well as developer leads should understand the design stamina hypothesis, so that the appropriate focus and priority is given to internal quality - otherwise pay a high price soon. Andrew Farrell 8 months ago I love that he was able to give an important lesson on the “How?” of software architecture at the very end: delegate decisions to those with the time to focus on them. Very nice and straight-forward talk about the value of software architecture For me, architecture is the distribution of complexity in a system. And also, how subsystems communicate with each other. A battle between craftmanship and the economics and economics always win... https://hackernoon.com/applying-clean-architecture-on-web-application-with-modular-pattern-7b11f1b89011 1. Independent of Frameworks 2. Testable 3. Indepe