乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      視頻編碼原理簡介

       mzsm 2015-12-11
      原文出處: 韋易笑   歡迎分享原創(chuàng)到伯樂頭條

      要徹底理解視頻編碼原理,看書都是虛的,需要實(shí)際動手,實(shí)現(xiàn)一個簡單的視頻編碼器:

      知識準(zhǔn)備:基本圖像處理知識,信號的時域和頻域問題,熟練掌握傅立葉正反變換,一維、二維傅立葉變換,以及其變種,dct變換,快速dct變換。

      來自知乎問題:http://www.zhihu.com/question/22567173/answer/73610451

      第一步:實(shí)現(xiàn)有損圖像壓縮和解壓

      參考 JPEG原理,將RGB->YUV,然后Y/U/V看成三張不同的圖片,將其中一張圖片分為 8×8的block進(jìn)行 dct變換(可以直接進(jìn)行二維dct變換,或者按一定順序?qū)?×8的二維數(shù)組整理成一個64字節(jié)的一維數(shù)組),還是得到一個8×8的整數(shù)頻率數(shù)據(jù)。于是表示圖像大輪廓的低頻信號(人眼敏感的信號)集中在 8×8的左上角;表示圖像細(xì)節(jié)的高頻信號集中在右下角。

      接著將其量化,所謂量化,就是信號采樣的步長,8×8的整數(shù)頻率數(shù)據(jù)塊,每個數(shù)據(jù)都要除以對應(yīng)位置的步長,左上角相對重要的低頻信號步長是1,也就是說0-255,是多少就是多少。而右下角是不太重要的高頻信號,比如步長取10,那么這些位置的數(shù)據(jù)都要/10,實(shí)際解碼的時候再將他們*10恢復(fù)出來,這樣經(jīng)過編碼的時候/10和解碼的時候*10,那么步長為10的信號1, 13, 25, 37就會變成規(guī)矩的:0, 10, 20, 30, 對小于步長10的部分我們直接丟棄了,因?yàn)楦哳l不太重要。

      經(jīng)過量化以后,8×8的數(shù)據(jù)塊左上角的數(shù)據(jù)由于步長小,都是比較離散的,而靠近右下角的高頻數(shù)據(jù),都比較統(tǒng)一,或者是一串0,因此圖像大量的細(xì)節(jié)被我們丟棄了,這時候,我們用無損壓縮方式,比如lzma2算法(jpeg是rle + huffman)將這64個byte壓縮起來,由于后面高頻數(shù)據(jù)步長大,做了除法以后,這些值都比較小,而且比較靠近,甚至右下部分都是一串0,十分便于壓縮。

      JPEG圖像有個問題就是低碼率時 block邊界比較嚴(yán)重,現(xiàn)代圖片壓縮技術(shù)往往要配合一些de-block算法,比如最簡單的就是邊界部分幾個像素點(diǎn)和周圍插值模糊一下。

      做到這里我們實(shí)現(xiàn)了一個同 jpeg類似的靜態(tài)圖片有損壓縮算法。在視頻里面用來保存I幀數(shù)據(jù)。

      第二步:實(shí)現(xiàn)宏塊誤差計(jì)算

      視頻由連續(xù)的若干圖像幀組成,分為 I幀,P幀,所謂I幀,就是不依賴就可以獨(dú)立解碼的視頻圖像幀,而P幀則需要依賴前面已解碼的視頻幀,配合一定數(shù)據(jù)才能生成出來。所以視頻中I幀往往都比較大,而P幀比較小,如果播放器一開始收到了P幀那么是無法播放的,只有收到下一個I幀才能開始播放。I幀多了視頻就變大,I幀少了,數(shù)據(jù)量是小了,但視頻受到丟包或者數(shù)據(jù)錯誤的影響卻又會更嚴(yán)重。

      那么所謂運(yùn)動預(yù)測編碼,其實(shí)就是P幀的生成過程:繼續(xù)將圖片分成 16×16的block(為了簡單只討論yuv的y分量壓縮)。I幀內(nèi)部單幀圖片壓縮我們采用了8×8的block,而這里用16×16的block來提高幀間編碼壓縮率(當(dāng)然也會有更多細(xì)節(jié)損失),我們用 x, y表示像素點(diǎn)坐標(biāo),而s,t表示block坐標(biāo),那么坐標(biāo)為(x,y)的像素點(diǎn)所屬的block坐標(biāo)為:

      1
      2
      s = x / 16 = x >> 4
      t = y / 16 = y >> 4

      接著要計(jì)算兩個block的相似度,即矢量的距離,可以表示為一個256維矢量(16×16)像素點(diǎn)色彩距離的平方,我們先定義兩個顏色的誤差為:

      PixelDiff(c1, c2) = (c1- c2) ^ 2

       

      那么256個點(diǎn)的誤差可以表示為所有對應(yīng)點(diǎn)的像素誤差和:

      BlockDiff(b1, b2) = sum( PixelDiff(c1, c2) for c1 in b1 for c2 in b2)

      代碼化為:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      int block_diff(const unsigned char b1[16][16], const unsigned char b2[16][16]) {
          int sum = 0;
          for (int i = 0; i < 16; i++) {
               for (int j = 0; j < 16; j++) {
                    int c1 = b1[i][j];
                    int c2 = b2[i][j];
                    sum += (c1 – c2) * (c1 – c2);
               }
          }
          return sum;
      }

      有了這個block求差的函數(shù),我們就可以針對特定block,搜索另外若干個block中哪個和它最相似了(誤差最?。?。

       

      第三步:實(shí)現(xiàn)運(yùn)動預(yù)測編碼

      根據(jù)上面的宏塊比較函數(shù),你已經(jīng)可以知道兩個block到底像不像了,越象的block,block_diff返回值越低。那么我們有兩幀相鄰的圖片,P1,P2,假設(shè) P1已經(jīng)完成編碼了,現(xiàn)在要對 P2進(jìn)行P幀編碼,其實(shí)就是輪詢 P2里面的每一個 block,為P2中每一個block找出上一幀中相似度最高的block坐標(biāo),并記錄下來,具體偽代碼可以表示為:

      其中在P1中搜索最相似 block的 block_search_nearest 函數(shù)原理是比較簡單的,我們可以暴力點(diǎn)用兩個for循環(huán)輪詢 P1中每個像素點(diǎn)開始的16×16的block(速度較慢),當(dāng)然實(shí)際中不可能這么暴力搜索,而是圍繞P2中該block對應(yīng)坐標(biāo)在P1中位置作為中心,慢慢四周擴(kuò)散,搜索一定步長,并得到一個:按照一定順序進(jìn)行搜索,并且在一定范圍內(nèi)最相似的宏塊坐標(biāo)。

      于是P2進(jìn)行運(yùn)動預(yù)測編碼的結(jié)果就是一大堆(x,y)的坐標(biāo),代表P2上每個block在上一幀P1里面最相似的 block的位置。反過來說可能更容易理解,我們可以把第三步整個過程定義為:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      unsigned char block[16][16];
      for (int t = 0; t <= maxt; t++) {
          for (int s = 0; s <= maxs; s++) {
               picture_get_block(P2, s * 16, t * 16, block); // 取得圖片 P2 的 block
               int x, y;
               block_search_nearest(P1, &x, &y, block); // 在P1中搜索最相似的block
               output(x, y);  // 將P1中最相似的block的左上角像素坐標(biāo) (x, y) 輸出
          }
      }

      怎么用若干 P1里不同起始位置的block拼湊出圖片P2來,使得拼湊以后的結(jié)果和P2最像。

      拼湊的結(jié)果就是一系列(x,y)的坐標(biāo)數(shù)據(jù),我們繼續(xù)用lzma2將它們先壓縮起來,按照 vcd的分辨率352 x 240,我們橫向需要 352 / 16 = 22個block,縱向需要 240 / 16 = 15 個block,可以用 P1中 22 x 15 = 330 個 block的坐標(biāo)信息生成一張和P2很類似的圖片 P2′ :

      1
      2
      3
      4
      5
      6
      7
      8
      for (int t = 0; t < 15; t++) {
          for (int s = 0; s < 22; s++, next++) {
               int x = block_positions[next].x;   // 取得對應(yīng) P1上的 block像素位置 x
               int y = block_positions[next].y;   // 取得對應(yīng) P1上的 block像素位置 y
               // 將 P1位置(x,y)開始的 16 x 16 的圖塊拷貝到 P2’的 (s * 16, t * 16)處
               CopyRect(P2′, s * 16, t * 16, P1, x, y, 16, 16);
          }
      }

      我們把用來生成P2的P1稱為 P2的 “參考幀”,再把剛才那一堆P1內(nèi)用來拼成P2的 block坐標(biāo)稱為 “運(yùn)動矢量”,這是P幀里面最主要的數(shù)據(jù)內(nèi)容。但是此時由P1和這些坐標(biāo)數(shù)據(jù)拼湊出來的P2,你會發(fā)現(xiàn)粗看和P2很象,但細(xì)看會發(fā)現(xiàn)有些支離破碎,并且邊緣比較明顯,怎么辦呢?我們需要第四步。

      第四步:實(shí)現(xiàn)P幀編碼

      有了剛才的運(yùn)動預(yù)測矢量(一堆block的坐標(biāo)),我們先用P1按照這些數(shù)據(jù)拼湊出一張類似 P2的新圖片叫做P2’,然后同P2上每個像素做減法,得到一張保存 differ的圖片:

      1
      D2 = (P2 – P2′) / 2

      誤差圖片 D2上每一個點(diǎn)等于 P2上對應(yīng)位置的點(diǎn)的顏色減去 P2’上對應(yīng)位置的點(diǎn)的顏色再除以2,用8位表示差值,值是循環(huán)的,比如-2就是255,這里一般可以在結(jié)果上 + 0×80,即 128代表0,129代表2,127代表-2。繼續(xù)用一個 8位的整數(shù)可以表示 [-254, 254] 之間的誤差范圍,步長精度是2。

      按照第三步實(shí)現(xiàn)的邏輯,P2’其實(shí)已經(jīng)很像P2了,只是有些誤差,我們將這些誤差保存成了圖片D2,所以圖片D2中,信息量其實(shí)已經(jīng)很小了,都是些細(xì)節(jié)修善,比起直接保存一張完整圖片熵要低很多的。所以我們將 D2用類似第一步提到的有損圖片壓縮方法進(jìn)行編碼,得到最終的P幀數(shù)據(jù):

      1
      Encode(P2) = Lzma2(block_positions) + 有損圖像編碼(D2)

      具體在操作的時候,D2的圖像塊可以用16×16進(jìn)行有損編碼,因?yàn)榍懊娴倪\(yùn)動預(yù)測數(shù)據(jù)是按16×16的宏塊搜索的,而不用象I幀那樣精確的用8×8表示,同時保存誤差圖時,量化的精度可以更粗一些用不著象I幀那么精確,可以理解成用質(zhì)量更低的JPEG編碼,按照16×16的塊進(jìn)行編碼,加上誤差圖D2本來信息量就不高,這樣的保存方式能夠節(jié)省不少空間。

       

      第五步:實(shí)現(xiàn)GOP生成

      通過前面的代碼,我們實(shí)現(xiàn)了I幀編碼和P幀編碼,P幀是參考P1對P2進(jìn)行編碼,而所謂B幀,就是參考 P1和 P3對P2進(jìn)行編碼,當(dāng)然間隔不一定是1,比如可以是參考P1和P5對P2進(jìn)行編碼,前提條件是P5可以依賴P1及以前的數(shù)據(jù)進(jìn)行解碼。

      不過對于一個完整的簡版視頻編碼器,I幀和P幀編碼已經(jīng)夠了,市面上任然有很多面向低延遲的商用編碼器是直接干掉B幀的,因?yàn)樽鰧?shí)時傳輸時收到B幀沒法播放,之后再往后好幾幀收到下一個I或者P幀時,先前收到的B幀才能被解碼出來,造成不少的延遲。

      而所謂的 GOP (Group of picture) 就是由一系列類似 I, P, B, B, P, B, B, P, B, B P 組成的一個可以完整被解碼出來的圖像組,而所謂視頻文件,就是一個接一個的GOP,每個GOP由一個I幀開頭,然后接下來一組連續(xù)的P 或者 B構(gòu)成,播放時只有完整收到下一個GOP的I幀才能開始播放。

      最后是關(guān)于參考幀選擇,前面提到的 P2生成過程是參考了 P1,假設(shè)一個GOP中十張圖片,是 I1, P1, P2, P3, P4, … P9 保存的,如果P1參考I1,P2參考P1, P3參考P2 …. P9參考P8這樣每一個P幀都是參考上一幀進(jìn)行編碼的話,誤差容易越來越大,因?yàn)镻1已經(jīng)引入一定誤差了,P2在P1的基礎(chǔ)上誤差更大,到了P9的話,圖片質(zhì)量可能已經(jīng)沒法看了。

      因此正確的參考幀選擇往往不需要這樣死板,比如可以P1-P9全部參考I1來生成,或者,P1-P4參考I1來生成,而P5-P9則參考P5來生成,這樣步子小點(diǎn),誤差也不算太離譜。

      第六步:容器組裝

      我們生成了一組組編碼過的GOP了,這時候需要一定的文件格式將他們恰當(dāng)?shù)谋4嫦聛?,記錄視頻信息,比如分辨率,幀率,時間索引等,就是一個類似MP4(h.264的容器)文件的東西。至此一個簡單的小型編碼器我們已經(jīng)完成了,可以用 SDL / DirectX / OpenGL 配合實(shí)現(xiàn)一個播放器,愉快的將自己編碼器編碼的視頻播放出來。

      第七部:優(yōu)化改進(jìn)

      這時候你已經(jīng)大概學(xué)習(xí)并掌握了視頻編碼的基礎(chǔ)原理了,接下來大量的優(yōu)化改進(jìn)的坑等著你去填呢。優(yōu)化有兩大方向,編碼效率優(yōu)化和編碼性能優(yōu)化:前者追求同質(zhì)量(同信噪比)下更低的碼率,后者追求同樣質(zhì)量和碼率的情況下,更快的編碼速度。

      有這個基礎(chǔ)后接下來可以回過頭去看JPEG標(biāo)準(zhǔn),MPEG1-2標(biāo)準(zhǔn),并閱讀相關(guān)實(shí)現(xiàn)代碼,你會發(fā)現(xiàn)簡單很多了,接著肯H.264代碼,不用全部看可以針對性的了解以下H.264的I幀編碼和各種搜索預(yù)測方法,有H.264的底子,你了解 HEVC和 vpx就比較容易了。

      參考這些編碼器一些有意思的實(shí)現(xiàn)來改進(jìn)自己的編碼器,試驗(yàn)性質(zhì),可以側(cè)重原理,各種優(yōu)化技巧了解下即可,本來就是hack性質(zhì)的。

      有卯用呢?首先肯定很好玩,其次,當(dāng)你有需要使用并修改這些編碼器為他們增加新特性的時候,你會發(fā)現(xiàn)前面的知識很管用了。

      1 贊 5 收藏 評論

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多