DCT變換原理:從浮點(diǎn)DCT到整數(shù)變換的工程取舍)
簡(jiǎn)介這份文檔聚焦H.264視頻編碼標(biāo)準(zhǔn)中的整數(shù)DCT變換編碼與量化過(guò)程面向視頻編解碼學(xué)習(xí)者、算法工程師及多媒體方向研究者幫助理解4×4塊無(wú)乘法整數(shù)變換的原理與實(shí)現(xiàn)細(xì)節(jié)。資源為單個(gè)doc文檔壓縮包約194KB內(nèi)容源自北京理工大學(xué)學(xué)者的技術(shù)論文系統(tǒng)梳理了整數(shù)DCT變換的公式推導(dǎo)、BinDCT提升結(jié)構(gòu)、正反變換流程以及亮度與色度分量的DC/AC系數(shù)分配和Hadamard變換處理方式。文中還闡述了與變換配套的量化算法說(shuō)明如何通過(guò)整數(shù)運(yùn)算替代浮點(diǎn)運(yùn)算來(lái)降低復(fù)雜度并規(guī)避編解碼誤匹配問(wèn)題。目前已有873人學(xué)習(xí)下載適合希望深入掌握H.264變換編碼核心機(jī)制、對(duì)照公式推導(dǎo)與實(shí)現(xiàn)思路進(jìn)行研讀的讀者參考。1. 整數(shù) DCT 變換為什么 H.264 寧可放棄精確的浮點(diǎn) DCT很多人第一次翻 H.264 標(biāo)準(zhǔn)文檔看到“整數(shù) DCT 變換”這個(gè)詞會(huì)下意識(shí)覺(jué)得不就是把 DCT 的浮點(diǎn)系數(shù)四舍五入成整數(shù)嗎如果真這么簡(jiǎn)單它就不值得被單獨(dú)寫(xiě)進(jìn)標(biāo)準(zhǔn)、更不值得被反復(fù)討論了。真正反直覺(jué)的地方在于H.264 用的這個(gè)變換嚴(yán)格來(lái)說(shuō)已經(jīng)不是 DCT 了它只是“由 DCT 推導(dǎo)而來(lái)”的一個(gè)近似物卻解決了浮點(diǎn) DCT 在工程上最要命的一個(gè)問(wèn)題——編解碼誤匹配。要理解這件事得先回到變換編碼的初衷。視頻幀里相鄰像素高度相關(guān)空間冗余極大變換編碼的作用就是把能量從“均勻鋪在 16 個(gè)像素上”壓縮到“集中在少數(shù)幾個(gè)系數(shù)上”后續(xù)量化才能大刀闊斧地砍掉高頻。DCT 正是做這件事的最優(yōu)近似。但傳統(tǒng)標(biāo)準(zhǔn)直接用 DCT 定義式變換核里全是無(wú)理數(shù)有限精度浮點(diǎn)表示不了編碼器和解碼器各自舍入反變換出來(lái)的結(jié)果就對(duì)不上——這就是 mismatch。H.264 的解法是干脆用一套純整數(shù)、無(wú)乘法、只有加減和移位的變換讓正反變換在整數(shù)域里精確可逆。代價(jià)是編碼性能損失微乎其微收益是徹底消滅了誤匹配這個(gè)玄學(xué)問(wèn)題。下面就從它怎么推導(dǎo)出來(lái)、怎么用蝶形算法省運(yùn)算、量化怎么和變換“合體”這三層把機(jī)制拆開(kāi)看。2. 從浮點(diǎn) DCT 到整數(shù)變換變換核是怎么被“整數(shù)化”的2.1 浮點(diǎn) DCT 的兩個(gè)致命傷傳統(tǒng) DCT 的變換矩陣?yán)锵禂?shù)是 cos 函數(shù)算出來(lái)的無(wú)理數(shù)比如 4×4 DCT 的核里會(huì)出現(xiàn) 0.5、0.653、0.271 這類(lèi)值。這帶來(lái)兩個(gè)問(wèn)題。第一運(yùn)算復(fù)雜度高每個(gè)系數(shù)都要做浮點(diǎn)乘法硬件上浮點(diǎn)乘法器面積和功耗都遠(yuǎn)高于整數(shù)加法器對(duì)低端處理器尤其不友好。第二也是更隱蔽的是誤匹配。編碼器做正變換時(shí)把無(wú)理數(shù)截?cái)喑赡硞€(gè)浮點(diǎn)近似解碼器做反變換時(shí)又用另一套近似兩邊舍入方向一旦不一致重建圖像就會(huì)和編碼端預(yù)期產(chǎn)生偏差。這種偏差不會(huì)報(bào)錯(cuò)只會(huì)讓畫(huà)質(zhì)悄悄劣化排查起來(lái)極其痛苦。H.264 的思路是既然無(wú)理數(shù)表示不精確是根源那就構(gòu)造一個(gè)所有元素都是整數(shù)的變換矩陣讓正變換和反變換都能用精確整數(shù)運(yùn)算完成。注意這里不是簡(jiǎn)單地把 DCT 矩陣取整——那樣會(huì)破壞正交性能量集中效果會(huì)崩。它是通過(guò)一套尺度因子分解把“歸一化”這件事從變換里剝離出去挪到量化階段做。2.2 尺度分解把 1/2 提到矩陣外面推導(dǎo)的關(guān)鍵一步在原文公式 (1.1) 到 (1.2) 之間。原始形式里含有乘以 1/2 的操作如果直接在變換里用右移實(shí)現(xiàn)會(huì)產(chǎn)生截?cái)嗾`差。H.264 的做法是把 1/2 提到矩陣外面和右邊的點(diǎn)乘合并于是變換核只剩加減和左移就能實(shí)現(xiàn)而那個(gè)點(diǎn)乘操作被合并進(jìn)隨后的量化過(guò)程。這就是“尺度 DCT”思想變換本身不做歸一化歸一化交給量化。用一句話概括這個(gè)設(shè)計(jì)動(dòng)機(jī)變換只負(fù)責(zé)把能量集中縮放交給量化這樣變換端可以做到純整數(shù)、無(wú)乘法量化端本來(lái)就要做乘除順手把縮放吸收進(jìn)去整體運(yùn)算量反而下降。反變換同理與 Y 點(diǎn)乘的操作和反量化合并乘以 1/2 由右移實(shí)現(xiàn)——因?yàn)榉戳炕蟮慕Y(jié)果足夠大右移不會(huì)引入截?cái)嗾`差。正反兩個(gè)方向都精確誤匹配問(wèn)題從根上消失。2.3 三種變換的分工為什么 DC 系數(shù)要單獨(dú)拎出來(lái)H.264 并不是對(duì)所有殘差都用同一種變換。它根據(jù)殘差數(shù)據(jù)類(lèi)型分了三路普通的 4×4 殘差塊用整數(shù) DCT16×16 幀內(nèi)預(yù)測(cè)的亮度塊16 個(gè) 4×4 塊的 DC 系數(shù)被提取出來(lái)組成一個(gè) 4×4 矩陣做 Hadamard 變換色度分量的 4 個(gè) 4×4 塊DC 系數(shù)組成 2×2 矩陣做 2×2 Hadamard 變換。為什么要多此一舉因?yàn)?DC 系數(shù)代表塊的平均亮度相鄰塊的 DC 系數(shù)之間相關(guān)性極強(qiáng)。如果只做普通 4×4 變換這部分冗余沒(méi)被利用。把它們單獨(dú)抽出來(lái)再做一次 Hadamard等于對(duì) DC 分量做了二次去相關(guān)能量進(jìn)一步集中。Hadamard 變換的好處是它的正變換和反變換形式完全一樣實(shí)現(xiàn)上只需要加減連移位都省了。原文圖 2 里標(biāo)號(hào) -1 到 25 的塊順序就是這套 DC/AC 分離機(jī)制在碼流里的排布方式。3. 蝶形算法8 次加法 2 次乘法是怎么省出來(lái)的3.1 二維變換拆成一維先行后列整數(shù) DCT 是二維變換但直接按二維公式算16 個(gè)輸出系數(shù)每個(gè)都要遍歷 16 個(gè)輸入運(yùn)算量是 O(N?)。H.264 的做法是把它拆成兩步先對(duì)矩陣每一列做一維變換再對(duì)結(jié)果的每一行做一維變換次序反過(guò)來(lái)也行。這樣二維變換就用一維變換實(shí)現(xiàn)了復(fù)雜度降到 O(N3) 量級(jí)。這是所有可分離變換的標(biāo)準(zhǔn)套路但 H.264 在一維變換內(nèi)部又做了一層優(yōu)化。3.2 蝶形結(jié)構(gòu)利用運(yùn)算冗余原文公式 (1.4) 給出了一維變換的直接計(jì)算方式按它算需要 12 次加法和 4 次乘法。但仔細(xì)看會(huì)發(fā)現(xiàn)大量重復(fù)某些中間結(jié)果同時(shí)被多個(gè)輸出系數(shù)用到。蝶形算法就是把這些公共子表達(dá)式提取出來(lái)暫存避免重復(fù)計(jì)算。優(yōu)化后只需要 8 次加法和 2 次乘法。原文圖 4 畫(huà)的就是這個(gè)蝶形結(jié)構(gòu)。用代碼把一維變換的蝶形過(guò)程寫(xiě)出來(lái)能更直觀看到“哪些量被復(fù)用了”// H.264 4x4 整數(shù) DCT 一維變換蝶形實(shí)現(xiàn) // 輸入 x[0..3]輸出 y[0..3] // 核心思想先做加減組合再對(duì)部分結(jié)果做移位縮放 void idct_1d(int x[4], int y[4]) { // 第一級(jí)兩兩加減得到和與差 int s0 x[0] x[3]; // 復(fù)用點(diǎn)s0 同時(shí)參與 y[0] 和 y[2] int s1 x[1] x[2]; int d0 x[0] - x[3]; // 復(fù)用點(diǎn)d0 同時(shí)參與 y[1] 和 y[3] int d1 x[1] - x[2]; // 第二級(jí)對(duì)和、差分別組合 y[0] s0 s1; // 低頻能量主要集中在這里 y[2] s0 - s1; y[1] (d0 1) d1; // 左移代替乘以 2無(wú)乘法 y[3] d0 - (d1 1); }這段代碼里s0、d0這些中間量被兩個(gè)輸出共用正是蝶形算法省運(yùn)算的來(lái)源。 1是左移一位等價(jià)于乘 2用移位代替乘法是整數(shù)變換“無(wú)乘法”的關(guān)鍵手段之一。參數(shù)說(shuō)明輸入x是一維方向上的 4 個(gè)殘差像素值輸出y是變換后的 4 個(gè)系數(shù)y[0]是直流分量y[1..3]是交流分量。實(shí)際標(biāo)準(zhǔn)里還會(huì)在變換后做一次尺度縮放那部分被合并到量化里所以這里看不到除法或小數(shù)。3.3 為什么這個(gè)次序可以反過(guò)來(lái)原文特別提到“這個(gè)次序也可以反過(guò)來(lái)先行后列”。這不是隨口一說(shuō)而是因?yàn)檎麛?shù) DCT 的變換矩陣是對(duì)稱(chēng)的行變換和列變換用的是同一個(gè)核。實(shí)現(xiàn)時(shí)先做哪一維只影響中間數(shù)據(jù)的訪存模式不影響最終結(jié)果。對(duì)硬件設(shè)計(jì)來(lái)說(shuō)這意味著可以復(fù)用同一套蝶形運(yùn)算單元先跑一遍行再跑一遍列面積省一半。對(duì)軟件實(shí)現(xiàn)來(lái)說(shuō)先行后列往往對(duì)緩存更友好因?yàn)樾蟹较蜻B續(xù)訪存。這個(gè)自由度是設(shè)計(jì)者刻意留出來(lái)的。4. 量化怎么把除法藏起來(lái)還讓 QP 每步只漲 12.25%4.1 量化步長(zhǎng)為什么是 52 檔、非均勻H.264 的量化步長(zhǎng)共有 52 種由量化參數(shù) QP 決定。規(guī)律是QP 每增加 1量化步長(zhǎng)增加約 12.25%每增加 6量化步長(zhǎng)翻一倍。為什么是 12.25% 而不是均勻增長(zhǎng)因?yàn)槿搜蹖?duì)量化誤差的敏感度不是線性的碼率控制需要更細(xì)的調(diào)節(jié)粒度。如果步長(zhǎng)均勻增長(zhǎng)低碼率段調(diào)節(jié)太粗高碼率段又太細(xì)。用 2 的 1/6 次方作為公比正好讓每 6 檔翻倍既保證了 6 檔一個(gè)倍頻程的規(guī)律性又在每個(gè)倍頻程內(nèi)給出 6 個(gè)細(xì)分檔位碼率控制精度大幅提升。4.2 變換與量化的“合體”原文公式 (1.6) 是理解量化實(shí)現(xiàn)的核心。前向量化公式本來(lái)是“變換系數(shù)除以量化步長(zhǎng)再取整”但 H.264 把變換里的點(diǎn)乘步驟和量化合并最終量化值等于變換結(jié)果乘以一個(gè)量化系數(shù)再右移。這樣做的直接好處是變換階段不用做小數(shù)乘法量化階段也不用做除法全部變成整數(shù)乘加和移位。用代碼表示這個(gè)融合過(guò)程// H.264 前向量化融合了變換的尺度因子 // coeff: 變換后的系數(shù) // qp: 量化參數(shù) 0..51 // qp_div6: qp / 6qp_mod6: qp % 6 int quantize(int coeff, int qp) { int qp_div6 qp / 6; int qp_mod6 qp % 6; // 量化系數(shù)表每個(gè) qp_mod6 對(duì)應(yīng)一組乘數(shù)和移位量 // 乘數(shù)表把“除以量化步長(zhǎng)”轉(zhuǎn)化為“乘以倒數(shù)再移位” static const int mult[6] {13107, 11703, 10082, 9362, 8192, 7282}; static const int shift[6] {14, 14, 14, 14, 14, 14}; // 核心乘法 右移替代除法 int offset 1 (shift[qp_mod6] - 1); // 四舍五入偏移 int result (coeff * mult[qp_mod6] offset) shift[qp_mod6]; // qp_div6 每增加 1整體再右移一位相當(dāng)于步長(zhǎng)翻倍 result qp_div6; return result; }這段代碼里mult表存的是量化步長(zhǎng)倒數(shù)的定點(diǎn)近似shift是配套的移位量。offset的加入是為了實(shí)現(xiàn)四舍五入而不是截?cái)啾苊庀到y(tǒng)性偏差。qp_div6那一步右移對(duì)應(yīng)“每 6 檔步長(zhǎng)翻倍”的規(guī)律。整個(gè)量化過(guò)程沒(méi)有一次除法也沒(méi)有浮點(diǎn)全部是整數(shù)乘加和移位。參數(shù)說(shuō)明coeff是變換后的整數(shù)系數(shù)qp是當(dāng)前宏塊的量化參數(shù)返回值是量化后的系數(shù)準(zhǔn)備送熵編碼。反量化則是這套流程的逆過(guò)程同樣用乘數(shù)和移位實(shí)現(xiàn)且因?yàn)榉戳炕髷?shù)值足夠大右移不會(huì)產(chǎn)生截?cái)嗾`差。4.3 反量化為什么先反變換再反量化原文圖 3 里有個(gè)容易看漏的細(xì)節(jié)對(duì)于 DC 系數(shù)解碼器是先反變換再反量化。這和普通 AC 系數(shù)的順序相反。原因是 DC 系數(shù)經(jīng)過(guò)了 Hadamard 變換Hadamard 變換本身是精確整數(shù)可逆的先做它不會(huì)引入誤差而反量化會(huì)放大數(shù)值如果先反量化再反變換中間數(shù)值范圍會(huì)更大對(duì)寄存器位寬要求更高。先反變換再反量化能把數(shù)值控制在 16 位以內(nèi)硬件實(shí)現(xiàn)更省資源。這個(gè)順序不是隨意定的是位寬和精度權(quán)衡的結(jié)果。5. 避坑與排查整數(shù)變換實(shí)現(xiàn)里最容易翻車(chē)的幾個(gè)點(diǎn)5.1 現(xiàn)象反變換結(jié)果和正變換輸入對(duì)不上畫(huà)質(zhì)有細(xì)微偏差原因最常見(jiàn)的是在變換階段就做了歸一化比如手動(dòng)把系數(shù)右移了一位來(lái)“縮小數(shù)值”。H.264 的設(shè)計(jì)是變換不做歸一化縮放全部交給量化。一旦在變換里提前移位正反變換的尺度就不匹配了。解決檢查變換代碼里有沒(méi)有多余的移位或除法。變換階段只允許加減和左移任何右移都要確認(rèn)是不是標(biāo)準(zhǔn)規(guī)定的尺度因子而不是自己加的“優(yōu)化”。5.2 現(xiàn)象低 QP 時(shí)畫(huà)質(zhì)正常高 QP 時(shí)出現(xiàn)明顯塊效應(yīng)和色偏原因量化系數(shù)表用錯(cuò)了或者qp_div6的移位方向搞反了。高 QP 時(shí)移位量大一旦方向錯(cuò)誤系數(shù)會(huì)被放大而不是縮小導(dǎo)致量化失效。解決用一組已知輸入手動(dòng)驗(yàn)算。取qp0和qp51兩個(gè)極端確認(rèn)量化后的系數(shù)在高 QP 時(shí)明顯變小、大量歸零。如果高 QP 時(shí)系數(shù)反而變大就是移位方向錯(cuò)了。5.3 現(xiàn)象DC 系數(shù)處理路徑和 AC 系數(shù)混在一起16×16 幀內(nèi)塊出現(xiàn)規(guī)律性亮暗條紋原因忘了把 16×16 幀內(nèi)亮度塊的 DC 系數(shù)單獨(dú)抽出來(lái)做 Hadamard 變換直接當(dāng)普通 AC 系數(shù)處理了。DC 系數(shù)之間的強(qiáng)相關(guān)性沒(méi)被去除重建時(shí)塊與塊之間出現(xiàn)亮度跳變。解決在變換前判斷宏塊編碼模式如果是 16×16 幀內(nèi)先把 16 個(gè) 4×4 塊的 DC 系數(shù)收集成 4×4 矩陣走 Hadamard 路徑AC 系數(shù)走普通整數(shù) DCT 路徑。色度同理收集成 2×2 走 2×2 Hadamard。5.4 現(xiàn)象蝶形算法優(yōu)化后結(jié)果和直接計(jì)算不一致原因蝶形結(jié)構(gòu)里的中間變量復(fù)用順序?qū)戝e(cuò)了或者移位操作的優(yōu)先級(jí)搞混。比如(d0 1) d1如果寫(xiě)成d0 1 d1由于運(yùn)算符優(yōu)先級(jí)實(shí)際是d0 (1 d1)結(jié)果完全錯(cuò)誤。解決所有移位和加減混合的表達(dá)式都加括號(hào)。優(yōu)化前后用同一組隨機(jī)輸入對(duì)比輸出確保逐位一致再替換。5.5 現(xiàn)象反量化后數(shù)值溢出 16 位重建圖像出現(xiàn)大面積噪點(diǎn)原因反量化時(shí)乘數(shù)表選得過(guò)大或者沒(méi)有在反量化后做飽和截?cái)?。H.264 規(guī)定變換和量化用 16 位算術(shù)超出范圍必須飽和處理。解決在反量化輸出處加飽和判斷超過(guò) 16 位有符號(hào)數(shù)范圍就鉗位到邊界值。同時(shí)核對(duì)乘數(shù)表是否和標(biāo)準(zhǔn)一致不同 QP 檔位的乘數(shù)不能混用。6. 設(shè)計(jì)取舍為什么“近似”反而比“精確”更值得選回頭看 H.264 這套整數(shù) DCT 加量化的設(shè)計(jì)最值得琢磨的不是某個(gè)公式而是它做取舍的邏輯。它放棄了 DCT 的數(shù)學(xué)精確性換來(lái)了整數(shù)可逆放棄了變換階段的歸一化換來(lái)了無(wú)乘法放棄了量化的直接除法換來(lái)了定點(diǎn)乘加。每一步都是“用一個(gè)可控的近似換掉一個(gè)工程上難處理的精確”。這種取舍在編解碼領(lǐng)域特別典型。視頻編碼本質(zhì)是在人眼感知的容忍范圍內(nèi)做有損壓縮絕對(duì)精確從來(lái)不是目標(biāo)可預(yù)測(cè)、可復(fù)現(xiàn)、低復(fù)雜度才是。浮點(diǎn) DCT 的誤匹配之所以可怕不是因?yàn)樗`差大而是因?yàn)樗豢深A(yù)測(cè)——同樣的碼流不同解碼器解出來(lái)不一樣這在工程上是災(zāi)難。整數(shù)變換把這種不確定性徹底消除代價(jià)只是編碼效率損失不到百分之幾這筆賬怎么算都劃算。驗(yàn)證這套機(jī)制是否理解到位有個(gè)很直接的辦法拿一組 4×4 殘差手動(dòng)走一遍正變換、量化、反量化、反變換看最終輸出和輸入的偏差是否在量化步長(zhǎng)允許的范圍內(nèi)。如果偏差隨 QP 增大而單調(diào)增大且高 QP 時(shí)大量系數(shù)歸零說(shuō)明整條鏈路是通的。如果偏差和 QP 無(wú)關(guān)或者低 QP 時(shí)就有固定偏移那多半是某個(gè)環(huán)節(jié)的尺度因子沒(méi)對(duì)齊。我自己的習(xí)慣是每次實(shí)現(xiàn)或移植這類(lèi)整數(shù)變換都先寫(xiě)一個(gè)最笨的、直接按公式算的參考版本再用優(yōu)化版本去對(duì)逐系數(shù)比對(duì)到完全一致才敢往下走。蝶形算法省下來(lái)的那幾個(gè)加法不值得用一次隱蔽的尺度錯(cuò)誤去換。從那以后我每次碰定點(diǎn)變換都強(qiáng)制走一遍“參考實(shí)現(xiàn)對(duì)照”哪怕代碼看起來(lái)再簡(jiǎn)單。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取