ffmpeg入門篇-濾鏡的基本使用

白狼棧發表於2021-07-21

轉發自白狼棧:檢視原文

濾鏡

什麼是濾鏡?百度百科介紹說“濾鏡主要是用來實現影像的各種特殊效果......”。

我們最早在ffmpeg是如何轉碼的一文中瞭解過濾鏡,來回顧下當時的轉碼流程圖。


從圖中可以看到濾鏡前後畫的是虛線,表示可有可無,在術語中,濾鏡指的是在編碼之前針對解碼器解碼出來的原始資料(即音視訊幀)進行處理的動作,我們還可以稱它為過濾器。

ffmpeg內建了大概近400種濾鏡,我們可以用 ffmpeg -filters 命令檢視所有的濾鏡,也可以用命令 ffmpeg -h filter=xxx 或者檢視官方文件瞭解每一種濾鏡。

實際在大部分音視訊的處理過程中都離不開濾鏡,所以你應該能明白其重要性。

多個濾鏡可以結合在一起使用形成濾鏡鏈或者濾鏡圖,在每一個濾鏡中,不僅可以對輸入源進行處理,A濾鏡處理好的結果還可以作為B濾鏡的輸入引數,通過B濾鏡繼續處理。

針對濾鏡的處理,ffmpeg提供了兩種處理方式,簡單濾鏡和複雜濾鏡。

簡單濾鏡

簡單濾鏡指的是隻有一個輸入和輸出,而且保證輸入和輸出的流型別相同。

比如我們在上篇文章流的操作(二)如何選擇流?末尾提到的把原視訊 r3.mp4 等比例縮放一倍

ffmpeg -i r3.mp4 -vf scale=272:480 -y filter.mp4

 

-vf 是 -filter:v 的簡寫,類似的我們還可以使用 -filter:a 或者 -af 針對音訊流做處理。

-filter的語法規則:-filter[:stream_specifier] filtergraph (output,per-stream)
stream_specifier流的型別我們一般用a表示音訊,v表示視訊,filtergraph表示具體的濾鏡,這裡用的是scale濾鏡

scale濾鏡用於調整視訊的大小,比如等比例縮放、等比例放大,不做等比例操作輸出就變形了,變形結果我們一般不考慮。

因為我們知道原視訊 r1ori.mp4 的解析度是 544x960,所以等比例縮放一倍,上面的命令直接指定了 272x480,scale濾鏡自帶很多引數,我們介紹幾個常用的。

in_w in_h 或者 iw ih 表示輸入視訊的寬高
out_w out_h 或者 ow oh 表示輸出視訊的寬高

當然不一定是視訊,輸入輸出也可以是圖片。

所以原視訊縮放一倍我們還可以這樣寫:

ffmpeg -i r3.mp4 -vf scale=iw/2:ih/2 -y filter.mp4

 

問題一:如果我們要把原視訊的寬度調整為300且保持原解析度,怎麼辦?

列一個方程 544/960 = 300/x ,x=300x960/540,很麻煩,結果還不一定能整除,為此我們可以直接指定高度等於-1,它會自動做等比例處理。

ffmpeg -i r1ori.mp4 -vf scale=300:-1 -y filter.mp4

 

結果發現轉碼失敗了,提示

[libx264 @ 0x7ff509053a00] height not divisible by 2 (300x529) 
Error initializing output stream 0:0 -- 
Error while opening encoder for output stream #0:0 - 
maybe incorrect parameters such as bit_rate, rate, width or height [aac @ 0x7ff50904e200] 
Qavg: 28010.410 [aac @ 0x7ff50904e200] 2 frames left in the queue on closing

 

提示我們 height not divisible by 2 (300x529)即高度529不能被2整除。這是因為一些編解碼器要求很多視訊的寬高必須是n的倍數(這裡n是2),所以我們寫指令碼處理視訊或者圖片寬高的時候,切記不要使用-1,正確的用法是使用-2。

ffmpeg -i r1ori.mp4 -vf scale=300:-2 -y filter.mp4 輸出結果視訊的解析度是 300 × 530

  

問題二:老闆為了刁難你,提出了一個新的要求:“我想要所有輸出視訊的解析度是 300x500且不能變形”,怎麼辦?

我們知道3:5的寬高比是很少見的,現在常見的解析度是16:9、4:3,也就是說原視訊我們必須要經過一番處理才可以滿足老闆的變態需求。

針對原視訊 r1ori.mp4,如果保證寬度是300,等比例縮放後高度是530,強制設定高度為500就會變形,也就是說我們只能讓高度等於500,儘量縮小寬度試試。

ffmpeg -i r1ori.mp4 -vf scale=-2:500 -y filter.mp4 輸出的結果視訊的解析度是284x500


如上圖,藍色框表示視訊的真實寬高,紅色框表示目標寬高,有些像html中的css一樣,可以給空出來的部分填充顏色即內邊距不就可以了?

查閱了文件我們發現pad濾鏡可以解決我們的問題。

pad濾鏡的語法規則:-pad=width[:height[:x[:y[:color]]]]

1、ffmpeg -i r1ori.mp4 -vf "scale=-2:500,pad=300:500:(300-iw)/2:0" -y filter2.mp4 
2、ffmpeg -i r1ori.mp4 -vf scale=-2:500,pad=300:500:-1:0 -y filter.mp4 
3、ffmpeg -i r1ori.mp4 -vf scale=-2:500,pad=300:500:-1:0:black -y filter.mp4 
4、ffmpeg -i r1ori.mp4 -vf "scale=-2:500,pad=300:ih:(ow-iw)/2:0:green" -y filter.mp4

 

上面提供4中寫法,我們以方法4做個簡單介紹。

scale=-2:500,指原視訊按照等比例縮放,高度等於500,就是上面大家看到的284x500。

pad=300:ih:(ow-iw)/2:0:green,300:ih即300:500就是紅色框的寬高(ow-iw)/2,指的是紅色框和藍色框差值的一半,即兩邊各需要填充的範圍;最後一個參數列示需要填充的顏色,預設是黑色 black,為了除錯方便我們把顏色設為green。

現在我們保證了當前視訊一定會按照300x500的比例輸出且不會變形,但是請注意老闆說的“所有輸出視訊”,也就是說輸入視訊的解析度可能是200x300、544x960、500x400、200x800等等各種比例都要保證按照300x500輸出,很顯然,上面的寫法不完全通用,怎麼辦?

現在我們已知原輸入視訊的寬高和想要的寬高,針對這種情況,我們制定一套處理規則即可解決:

  1. 寬高都偏小,不拉伸,不縮放
  2. 寬高都偏大,等比例縮小,以高度為準
  3. 寬超出範圍,等比例縮小,以寬為準
  4. 高超出範圍,等比例縮小,以高為準

在實際的開發過程中,我們要跟程式碼打交道,平時在命令列中的實現都是練習,所以基於該規則,我們有了下面一段程式碼

<?php
declare(strict_types=1);

class CalculatorService
{
    /**
     * 使用者視訊解析度轉換
     * 規則:
     *  寬高都偏小,不拉伸,不縮放
     *  寬高都偏大,等比例縮小,以高度為準
     *  寬超出範圍,等比例縮小,以寬為準
     *  高超出範圍,等比例縮小,以高為準
     * @param int $inputWidth 輸入視訊的寬度
     * @param int $inputHeight 輸入視訊的高度
     * @param int $outWidth 輸出視訊的寬高
     * @param int $outHeight 輸出視訊的高度
     * @return string scale
     */
    public function getSize(int $inputWidth, int $inputHeight, int $outWidth, int $outHeight): string
    {
        $scale = "";
        if ($inputWidth <= $outWidth && $inputHeight <= $outHeight) {
            $scale = "scale={$inputWidth}:{$inputHeight},pad={$outWidth}:{$outHeight}:-1:-1:green";
        } elseif (($inputWidth > $outWidth && $inputHeight > $outHeight)
            || ($inputHeight > $outHeight)
        ) {
            $scale = "scale=-2:{$outHeight},pad={$outWidth}:{$outHeight}:-1:0:green";
        } elseif ($inputWidth > $outWidth) {
            $scale = "scale={$outWidth}:-2,pad={$outWidth}:{$outHeight}:0:-1:green";
        }

        return $scale;
    }
}

$calculatorService = new CalculatorService();
var_dump($calculatorService->getSize(200, 300, 300, 500));
var_dump($calculatorService->getSize(544, 960, 300, 500));
var_dump($calculatorService->getSize(500, 400, 300, 500));
var_dump($calculatorService->getSize(200, 600, 300, 500));

// 結果
string(37) "scale=200:300,pad=300:500:-1:-1:green"
string(35) "scale=-2:500,pad=300:500:-1:0:green"
string(35) "scale=300:-2,pad=300:500:0:-1:green"
string(35) "scale=-2:500,pad=300:500:-1:0:green"

 

為了方便理解,大家可以參考下面的圖一一對應。


複雜濾鏡

相對於簡單濾鏡,複雜濾鏡是可以處理任意數量輸入和輸出效果的濾鏡圖,它幾乎無所不能。

複雜濾鏡用命令 -filter_complex 表示,它還有一個別名 -lavfi。

上篇文章介紹到流和濾鏡結合是一種最重要、最常用的方法。依然是將輸入視訊 r3.mp4 等比例縮放一倍,我們以手動選擇流的方式為例。

ffmpeg -i r3.mp4 -filter_complex "[0]scale=272:480[out]" -map 0:a -map "[out]" -y filter.mp4

 

簡單分析如下:

  1. 命令 "[0]scale=272:480[out]" 中的[0]表示第一個輸入的視訊,因為要對視訊做處理,所以也可以用[0:v]表示,如果要對音訊單獨處理,就需要用 [0:a] 了;
  2. [0] 結合scale濾鏡,表示的就是把第一個輸入的視訊作為scale濾鏡的引數輸入;
  3. [out] 中括號是必須要的,out是自定義的一個別名,結合scale濾鏡,表示的是把scale濾鏡輸出的結果命名為[out],但並非是最終輸出的結果,只能作為中間過程輸出的一個結果;
  4. -map "[out]" 就是直接選擇[out] 流作為輸出

我們說過,一個濾鏡的輸出作為另一個濾鏡的輸入,這樣就極大的避免了寫多條命令反覆編解碼操作,我們的原則只有一個,能用一條命令處理的絕不用兩條命令。

有損編解碼器反覆編解碼操作會降低原視訊質量。

比如現在要把原視訊 r1ori.mp4 的中間部分裁剪出來,但仍保持原視訊的解析度544x960,如何做呢?

ffmpeg -i r1ori.mp4 -filter_complex "nullsrc=s=544x960[background]; \
crop=iw:(ih/2 - 110):0:250[middle]; \
[background][middle]overlay=shortest=1:x=(main_w-overlay_w)/2:y=(main_h-overlay_h)/2[out]" \
-map "[out]" 
-map 0:a 
-movflags +faststart 
-y fc.mp4

 

這個命令就顯得稍微長了一些,在這條命令中使用了nullsrccropoverlay三種常見濾鏡。

nullsrc濾鏡用於建立一個空的視訊,簡單的說就是一個空的畫布或者說是綠布,因為預設建立的顏色是綠色的。s用於指定畫布的大小,預設是320x240,這裡表示我們建立一個544x960的畫布,並命名為background;

關於nullsrc還有很多種不同的使用者,比如使用nullsrc和CIQRCodeGenerator建立一個“白狼棧”首頁的二維碼

ffmpeg -f lavfi -i nullsrc=s=200x200,coreimage=filter=CIQRCodeGenerator@inputMessage=\ 
http\\\\\://manks.top/@inputCorrectionLevel=H -frames:v 1 manks.png

 

crop濾鏡用於裁剪視訊,也就是說視訊的任意區域任意大小,我們都可以裁剪出來。crop=iw:(ih/2 - 110):0:250[middle]; 這裡我們裁剪原視訊的中間部分並命名為middle;

overlay濾鏡表示兩個視訊相互疊加,shortest官網是這麼介紹的:“If set to 1, force the output to terminate when the shortest input terminates. Default value is 0.”,因為我們使用nullsrc建立了一個沒有時間軸的畫布,所以這裡需要以middle的視訊時間為最終時間,故設定為1。main_w和main_h表示主視訊的寬高,overlay_w和overlay_h表示疊加視訊的寬高。如果要把A視訊疊加到B視訊上,則main_w和main_h表示B視訊的寬高,overlay_w和overlay_h表示A視訊的寬高。合起來便是把middle疊加到background之上且置於background的中間(相當於有個疊加層的概念);

最後一個引數是-movflags,它跟mp4的後設資料有關,設為faststart表示會將moov移動到mdat的前面,線上播放的時候會稍微快一些。

作業:我們在音視訊合成案例一文中介紹了兩個案例,快去試試你能不能一條命令解決?

關於濾鏡的基本介紹我們就介紹到這裡,有任何問題可以下方留言。

 

相關文章