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:
Dữ liệu Transform data XML từ Adobe After Effect lưu vị trí mặt tại mỗi khung hình:
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.
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> ....
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.
ffmpeg -i music.wav -f mp2 music.mp3
ReplyDeletewget http://littlewing.wc.lt/sounds/music.wav
# first convert an image sequence to a movie
ReplyDeleteffmpeg -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
convert $(for ((a=0; a<700; a++)); do printf -- "-delay 10 name%s.png " $a; done;) result.gif
ReplyDeletehttp://www.imagemagick.org/script/import.php
ReplyDeleteHi, anyone have this example working? thanks!
ReplyDeleteWait me a bit. I will rebuild example and reply to you soon.
DeleteCurrently I am quite busy.
Thanks a lot Nick! that will really help me!
Deletehave a great day!
Rod
Hi Rodrigo, here is the PHP file for processing video: https://github.com/dungnv53/Laratype/blob/master/app/lib/LaravelPrototype/Common/VideoProcessing.php
DeleteThe 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.
This comment has been removed by the author.
Delete@Rodrigo I will update this post and try to rebuild project then provide you detail guide.
DeleteThe 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...
Hello Nick!!
DeleteThank 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?
Please add my Skype live: vandung_17
DeletePhone 841205029223
My skype name: dzung nguyen
DeleteQuote: 925 trap
hi, i will sent you and email and also i already add you to skype
Deletethanks!