ffmpeg sws_scale詳細分析

FFmpeg裏面的sws_scale庫可以在一個函數裏面同時實現:1.圖像色彩空間轉換;2.分辨率縮放;3.前後圖像濾波處理。

其核心函數主要有三個:

// 初始化sws_scale
struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
                                  int dstW, int dstH, enum AVPixelFormat dstFormat,
                                  int flags,
                                  SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);
參數int srcW, int srcH, enum AVPixelFormat srcFormat定義輸入圖像信息(寬、高、顏色空間(像素格式))
參數int dstW, int dstH, enum AVPixelFormat dstFormat定義輸出圖像信息寬、高、顏色空間(像素格式))。
參數int flags選擇縮放算法(只有當輸入輸出圖像大小不同時有效)
參數SwsFilter *srcFilter, SwsFilter *dstFilter分別定義輸入/輸出圖像濾波器信息,如果不做前後圖像濾波,輸入NULL
參數const double *param定義特定縮放算法需要的參數(?),默認爲NULL
函數返回SwsContext結構體,定義了基本變換信息。
如果是對一個序列的所有幀做相同的處理,函數sws_getContext只需要調用一次就可以了。
sws_getContext(w, h, YV12, w, h, NV12, 0, NULL, NULL, NULL);      // YV12->NV12 色彩空間轉換
sws_getContext(w, h, YV12, w/2, h/2, YV12, 0, NULL, NULL, NULL);  // YV12圖像縮小到原圖1/4
sws_getContext(w, h, YV12, 2w, 2h, YN12, 0, NULL, NULL, NULL);    // YV12圖像放大到原圖4倍,並轉換爲NV12結構

// 做轉換
int sws_scale(struct SwsContext *c,
              const uint8_t *const srcSlice[], const int srcStride[],
              int srcSliceY, int srcSliceH,
              uint8_t *const dst[], const int dstStride[]);
參數struct SwsContext *c,爲上面sws_getContext函數返回值;
參數const uint8_t *const srcSlice[], const int srcStride[]定義輸入圖像信息(當前處理區域的每個通道數據指針,每個通道行字節數)
stride定義下一行的起始位置。stride和width不一定相同,這是因爲:
1.由於數據幀存儲的對齊,有可能會向每行後面增加一些填充字節這樣 stride = width + N;
2.packet色彩空間下,每個像素幾個通道數據混合在一起,例如RGB24,每個像素3字節連續存放,因此下一行的位置需要跳過3*width字節。
srcSlice和srcStride的維數相同,由srcFormat值來。
csp       維數        寬width      跨度stride      高
YUV420     3        w, w/2, w/2    s, s/2, s/2   h, h/2, h/2
YUYV       1        w, w/2, w/2   2s, 0, 0       h, h, h
NV12       2        w, w/2, w/2    s, s, 0       h, h/2
RGB24      1        w, w,   w     3s, 0, 0       h, 0, 0           
參數int srcSliceY, int srcSliceH,定義在輸入圖像上處理區域,srcSliceY是起始位置,srcSliceH是處理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性處理完整個圖像。
這種設置是爲了多線程並行,例如可以創建兩個線程,第一個線程處理 [0, h/2-1]行,第二個線程處理 [h/2, h-1]行。並行處理加快速度。
參數uint8_t *const dst[], const int dstStride[]定義輸出圖像信息(輸出的每個通道數據指針,每個通道行字節數)

// 釋放sws_scale

void sws_freeContext(struct SwsContext *swsContext);

在網上沒有看到有關SwsFilter的討論,看FFMpeg代碼,總結下面的分析結果。

sws_scale前後圖像濾波都定義爲歸一化的2維或者1維圖像卷積處理。每個濾波器有四個分量
typedef struct SwsFilter {
    SwsVector *lumH; // 亮度水平處理
    SwsVector *lumV; // 亮度垂直處理
    SwsVector *chrH; // 色度水平處理
    SwsVector *chrV; // 色度垂直處理
} SwsFilter;
一般都是2維水平和垂直按照相同的處理係數來濾波。

每個濾波器定義爲:
typedef struct SwsVector {
    double *coeff;              // 濾波器係數
    int length;                 // 濾波器長度
} SwsVector;
一般濾波器具有歸一化:length個coeff之和等於1;
             對稱性:length一般爲奇數,coeff以中心爲軸左右對稱。

sws_scale庫裏定義了3種初始濾波器。
1. 高斯模糊 Gaussian Blur
   SwsVector *sws_getGaussianVec(double variance, double quality);
   variance就是σ。quality=3.0。
   const int length = (int)(variance * quality + 0.5) | 1;
   double middle  = (length - 1) * 0.5;
   for (i = 0; i < length; i++) {
       double dist = i - middle;
       vec->coeff[i] = exp(-dist * dist / (2 * variance * variance)) / sqrt(2 * variance * M_PI);
   }  
   如後在歸一化vec->coeff[i]。
   // 這個公式和標準高斯公式不一樣,標準高斯函數公式如下
   vec->coeff[i] = exp(-dist * dist / (2 * variance * variance)) / (variance*sqrt(2 * M_PI));

下面是一些variance值計算出來的結果。   
variance = 1.0 => length=3                 0.2741  0.4519  0.2741
variance = 1.5 => length=5         0.1201  0.2339  0.2921  0.2339  0.1201
variance = 2.0 => length=7 0.0702  0.1311  0.1907  0.2161  0.1907  0.1311  0.0702
垂直方向濾波器length過大,不僅計算量增加,數據讀取的帶寬需求也增大,近似爲讀取length*frame_size數據。

2.銳化濾波器 Sharpen
    if (lumaSharpen != 0.0) {
        SwsVector *id = sws_getIdentityVec();
        sws_scaleVec(filter->lumH, -lumaSharpen);  // 所有點矢量乘 -lumaSharpen
        sws_addVec(filter->lumH, id);              // 矢量加   
    }
    coeff[i] = i==(length-1)/2 ? 1 - lumaSharpen*coeff[i] : - lumaSharpen*coeff[i];
    中心點設爲1-lumaSharpen*coeff[i],其他點設爲 -lumaSharpen*coeff[i].
    一般情況兩個矢量相加,以中心點對齊,左右兩邊分別相加,沒有的值補0.
    {a1, a2, a3} + {b1, b2, b3, b4, b5} = {b1, a1+b1, a2+b3, a3+b4, b5}
如已經使用高斯模糊得到濾波器爲:
length=5         0.1201  0.2339  0.2921  0.2339  0.1201
設lumaSharpen = 0.7; 結果爲
length=5        -0.0841 -0.1637  0.7955 -0.1637 -0.0841

3.色度移動濾波器 ChromaShift
    if (chromaHShift != 0.0)
        sws_shiftVec(filter->chrH, (int)(chromaHShift + 0.5));
    函數sws_getShiftedVec(SwsVector *a, int shift) 左移矢量a;如果shift小於0,右移
    移動後矢量長度爲 length = a->length + FFABS(shift) * 2;
    左移就是後面補 length - a->length個0
    右移就是前面補 length - a->length個0
例如chromaHShift = 1.3, shift = (int)(1.3+0.5) = 1;
移位後結果增加 |1|*2 = 2個; 正數左移 後面補零
        a1, a2, a3, ... aN, 0.0000,  0.0000
例如chromaHShift = -3.1, shift = (int)(-3.1+0.5) = -2;
移位後結果增加 |-2|*2 = 4個; 負數左移 前面補零
   0.0000  0.0000  0.0000  0.0000 a1 a2 a3 ... aN
這個濾波器將色度位置移動,有什麼用處???
 
4. 設置初始濾波器的流程
SwsFilter *sws_getDefaultFilter(float lumaGBlur,    float chromaGBlur,
                                float lumaSharpen,  float chromaSharpen,
                                float chromaHShift, float chromaVShift,
                                int verbose);
參數float lumaGBlur, float chromaGBlur分別設置亮度和色度的高斯模糊參數。一般亮度做模糊,色度不做。
參數float lumaSharpen, float chromaSharpen分別設置亮度和色度的銳化參數。做高斯模糊後,物體邊緣也變得模糊,爲了減少這種影響,調用銳化濾波。如果不做高斯模糊,沒必要做銳化濾波。
參數float chromaHShift, float chromaVShift分別設置色度在水平和垂直兩方向上的色彩位移,不明白是什麼物理意義,還是固定爲0.0的好。
參數int verbose是控制打印濾波器參數,設置爲0。
在函數裏面亮度濾波器的設置流程是:
a. 如果lumaGBlur不爲0.0, 設置高斯濾波器;
b. 如果lumaSharpen不爲0.0, 在高斯濾波器上疊加銳化濾波;
c. 歸一化步驟2的濾波器,作爲最終的濾波器參數。

也可以按照需要設置自己的濾波器,但是都是做1維或者2維的卷積操作,所有有些濾波器也設置不出來。
例如線性拉伸處理。 g = k*f+b. f,g分別爲原始和處理後像素點值,k,b爲標量參數值。

基於計算複雜度的考慮,濾波器放置在圖像相對小的那一端,例如sws_scale做縮小處理,那麼濾波器在後端;如果做放大處理,濾波器放前端(個人建議)。

各種濾波器的效率,雷神做了詳細測試,貼出鏈接https://blog.csdn.net/leixiaohua1020/article/details/12029505

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章