據(jù)上機(jī)實(shí)驗(yàn)指南:圖像音頻視頻處理與代碼實(shí)現(xiàn))
簡(jiǎn)介西電多媒體數(shù)據(jù)上機(jī)實(shí)驗(yàn)完整代碼與數(shù)據(jù)集包面向計(jì)算機(jī)、電子、通信等專(zhuān)業(yè)的多媒體實(shí)驗(yàn)課程覆蓋圖像、音頻、視頻三大核心處理方向幫助學(xué)生在動(dòng)手編程中掌握數(shù)字化、編碼與增強(qiáng)等基礎(chǔ)方法。內(nèi)容包含多種典型算法實(shí)現(xiàn)圖像濾波、邊緣檢測(cè)、直方圖均衡化、RGB/HSV色彩空間轉(zhuǎn)換音頻采樣率轉(zhuǎn)換、降噪與MP3/AAC編碼視頻幀提取、幀間預(yù)測(cè)、運(yùn)動(dòng)估計(jì)與MPEG/H.264編碼同時(shí)整理MNIST、CIFAR-10、VGG-Sound、UCF101等標(biāo)準(zhǔn)數(shù)據(jù)集便于直接用于模型訓(xùn)練與測(cè)試。壓縮包共2002個(gè)文件以1991張JPEG圖像為主另有3個(gè)Python腳本、txt說(shuō)明、csv索引及C語(yǔ)言輔助文件整體大小38.61MB目錄按模塊拆分便于對(duì)照學(xué)習(xí)。目前已有330人學(xué)習(xí)瀏覽對(duì)于需要完整實(shí)驗(yàn)閉環(huán)和代碼參考的本科生是一份能快速上手的實(shí)踐資料。 最近不少學(xué)弟學(xué)妹在后臺(tái)問(wèn)我西電的多媒體數(shù)據(jù)上機(jī)實(shí)驗(yàn)怎么準(zhǔn)備尤其是代碼和數(shù)據(jù)集這塊頭緒很多。這周剛好幫一個(gè)同學(xué)完整梳理了一遍索性把這套折騰過(guò)好幾輪的方案整理出來(lái)從數(shù)據(jù)準(zhǔn)備、代碼結(jié)構(gòu)到踩坑記錄一次性講清楚。如果你正在補(bǔ)考或者下學(xué)期要選這門(mén)課這篇文章應(yīng)該能幫你省下不少瞎試的時(shí)間。1. 實(shí)驗(yàn)整體思路與選題拆解1.1 多媒體實(shí)驗(yàn)到底在考什么多媒體數(shù)據(jù)上機(jī)實(shí)驗(yàn)本質(zhì)上不是考你寫(xiě)多復(fù)雜的算法而是考三件事能不能把課上講的原理用代碼落實(shí)能不能正確處理真實(shí)的多媒體文件能不能把結(jié)果用可視化的方式講清楚。西電這門(mén)課的幾個(gè)固定模塊基本圍繞圖像、音頻、視頻三大類(lèi)展開(kāi)。圖像的讀取、灰度變換、直方圖均衡化、傅里葉變換、DCT變換、JPEG壓縮流程模擬音頻的采樣分析、頻譜繪制、濾波器的設(shè)計(jì)與應(yīng)用視頻的幀提取、運(yùn)動(dòng)估計(jì)或者簡(jiǎn)單的編解碼模擬另外還有信息隱藏、數(shù)字水印這類(lèi)相對(duì)進(jìn)階的方向大多數(shù)同學(xué)栽跟頭的地方不在算法本身而在數(shù)據(jù)集的預(yù)處理和格式兼容性上。BMP格式的像素排列、YUV的采樣方式、JPEG的量化表結(jié)構(gòu)這些細(xì)節(jié)才是實(shí)驗(yàn)真正想考核的點(diǎn)。1.2 數(shù)據(jù)集的選型邏輯我當(dāng)時(shí)選數(shù)據(jù)集的時(shí)候定的原則很簡(jiǎn)單官方、達(dá)標(biāo)、輕量。所謂官方就是盡量用課程指定或者教材配套的數(shù)據(jù)集達(dá)標(biāo)是確保文件格式能覆蓋實(shí)驗(yàn)要求輕量則是單文件不要?jiǎng)虞m幾百M(fèi)B否則上機(jī)調(diào)試時(shí)讀一次都要等半天。標(biāo)準(zhǔn)的選擇組合是這樣的圖像部分Lena圖是必備的另加2到3張不同特征的BMP圖像一張紋理豐富的、一張平坦區(qū)域的、一張帶明顯邊緣的方便對(duì)比不同處理的效果音頻部分用WAV格式采樣率8k、16k、44.1k各備一份內(nèi)容可以是一段語(yǔ)音加一段音樂(lè)方便觀察頻譜差異視頻部分小尺寸的YUV序列比如QCIF格式的大概幾秒就夠文件大小在幾十MB以?xún)?nèi)處理起來(lái)不會(huì)太吃力1.3 為什么推薦C語(yǔ)言配合Python雙軌實(shí)現(xiàn)很多同學(xué)糾結(jié)到底用C還是Python。我的建議是如果你只想快速出結(jié)果Python確實(shí)省事但我更推薦C語(yǔ)言完成核心算法部分Python只做結(jié)果可視化和對(duì)比驗(yàn)證。理由有三點(diǎn)課程的評(píng)分點(diǎn)在原理實(shí)現(xiàn)C語(yǔ)言能讓你對(duì)內(nèi)存布局、數(shù)據(jù)排列有直觀理解比如說(shuō)圖像的一維數(shù)組和二維坐標(biāo)如何映射這種細(xì)節(jié)在Python里會(huì)被完全封裝掉部分實(shí)驗(yàn)要求必須生成特定格式的文件如BMP或YUV用C寫(xiě)文件頭、寫(xiě)像素?cái)?shù)據(jù)比用庫(kù)函數(shù)更能體現(xiàn)你確實(shí)理解了格式規(guī)范最終報(bào)告需要展示中間過(guò)程的數(shù)值變化C語(yǔ)言打印中間結(jié)果非常直接Python處理時(shí)容易因?yàn)轭?lèi)型轉(zhuǎn)換和lib庫(kù)的封裝導(dǎo)致數(shù)值精度問(wèn)題解釋不清楚2. 圖像類(lèi)實(shí)驗(yàn)的核心實(shí)現(xiàn)細(xì)節(jié)2.1 BMP文件的讀取與像素訪(fǎng)問(wèn)BMP格式是所有圖像實(shí)驗(yàn)的基礎(chǔ)也是第一個(gè)容易翻車(chē)的點(diǎn)。BMP文件由文件頭、信息頭、調(diào)色板可選和像素?cái)?shù)據(jù)組成其中跟代碼關(guān)系最大的是位深和像素存儲(chǔ)方式。24位真彩BMP的像素排列是BGR順序不是RGB這地方處理不好顯示出的圖會(huì)紅藍(lán)顛倒。另外每一行的字節(jié)數(shù)必須是4的倍數(shù)不足時(shí)需要在行尾補(bǔ)齊。這個(gè)對(duì)齊規(guī)則很多人忽視了導(dǎo)致遍歷像素時(shí)出現(xiàn)錯(cuò)位。我當(dāng)時(shí)封裝了三個(gè)基本函數(shù)BITMAPFILEHEADER fileHeader; BITMAPINFOHEADER infoHeader; // 讀取文件頭和信息頭 fread(fileHeader, sizeof(BITMAPFILEHEADER), 1, fp); fread(infoHeader, sizeof(BITMAPINFOHEADER), 1, fp); // 計(jì)算行對(duì)齊字節(jié)數(shù) int lineBytes ((infoHeader.biWidth * infoHeader.biBitCount 31) / 32) * 4; // 計(jì)算像素?cái)?shù)據(jù)起始偏移 int dataOffset fileHeader.bfOffBits;讀取完頭信息后按照l(shuí)ineBytes跳行讀取像素?cái)?shù)據(jù)寫(xiě)入二維數(shù)組時(shí)要注意把BGR順序糾正為RGB這樣后續(xù)做灰度變換時(shí)邏輯才不亂。2.2 灰度變換的三種常用方法對(duì)比灰度變換有分量法、最大值法、加權(quán)平均法三種實(shí)驗(yàn)報(bào)告里建議把三種都做出來(lái)然后對(duì)比分析。加權(quán)平均法的系數(shù)在不同標(biāo)準(zhǔn)里有差異教材用的是0.299、0.587、0.114有的參考資料會(huì)用0.3、0.59、0.11精度上差別不大但要跟教材保持一致方便和理論值對(duì)照。實(shí)際處理時(shí)記得做Gamma校正的可選功能這樣能演示同一個(gè)算法在不同參數(shù)下的效果差異報(bào)告中多了對(duì)比維度分?jǐn)?shù)也會(huì)好看一些。2.3 直方圖均衡化的計(jì)算順序直方圖均衡化的代碼量不大但計(jì)算順序很容易搞錯(cuò)。正確順序是統(tǒng)計(jì)灰度級(jí)頻率、計(jì)算累計(jì)分布、映射到新灰度值、回填像素。如果把歸一化和映射混在一起做經(jīng)常會(huì)因?yàn)閿?shù)據(jù)類(lèi)型問(wèn)題導(dǎo)致結(jié)果圖整體偏暗或偏亮。我當(dāng)時(shí)用的是float類(lèi)型保存累計(jì)概率映射時(shí)乘以255并四舍五入。有個(gè)小細(xì)節(jié)均衡化前后對(duì)比圖要放在一起便于觀察灰度分布的變化。原始圖的直方圖如果集中在一個(gè)狹窄區(qū)間均衡化后的效果會(huì)特別明顯反之如果原圖本身就接近均勻分布變化就不大這時(shí)候換一張樣本圖比改代碼更有意義。3. 音頻實(shí)驗(yàn)與頻域分析實(shí)操3.1 WAV格式解析與采樣數(shù)據(jù)提取音頻實(shí)驗(yàn)的第一步是解析WAV文件頭。WAV頭有44字節(jié)的標(biāo)準(zhǔn)結(jié)構(gòu)前12字節(jié)是RIFF標(biāo)識(shí)和文件大小從第36字節(jié)開(kāi)始是數(shù)據(jù)子塊的標(biāo)識(shí)。有的文件可能包含額外信息導(dǎo)致數(shù)據(jù)區(qū)偏移不是固定的44字節(jié)所以不能硬編碼偏移量要先判斷data標(biāo)識(shí)再定位。我在做WAV解析時(shí)走了不少?gòu)澛芬驗(yàn)橛簿幋a偏移量去讀數(shù)據(jù)遇到帶LIST塊的WAV文件直接崩掉。后來(lái)改成掃描子塊的方式才穩(wěn)定建議你們一開(kāi)始就用掃描的方式別嫌麻煩。// 掃描找到data子塊 while (ftell(fp) fileSize) { char chunkId[4]; unsigned int chunkSize; fread(chunkId, 4, 1, fp); fread(chunkSize, 4, 1, fp); if (memcmp(chunkId, data, 4) 0) { // 數(shù)據(jù)區(qū)從這里開(kāi)始 break; } fseek(fp, chunkSize, SEEK_CUR); }16位采樣數(shù)據(jù)的取值范圍是-32768到32767做FFT之前最好歸一化到-1.0到1.0的范圍否則頻譜幅值會(huì)特別大畫(huà)圖時(shí)都擠在一起看不出特征。3.2 頻譜分析代碼的快速實(shí)現(xiàn)頻譜分析可以用FFT來(lái)實(shí)現(xiàn)也可以簡(jiǎn)化成離散傅里葉變換直接嵌套循環(huán)計(jì)算。如果樣本點(diǎn)數(shù)不多比如1024點(diǎn)直接算DFT也是可接受的代碼邏輯更簡(jiǎn)單直白方便在報(bào)告中講解。需要注意窗函數(shù)的選擇。直接截取一段信號(hào)做DFT會(huì)產(chǎn)生頻譜泄漏。加漢寧窗能顯著改善這個(gè)問(wèn)題代碼就一行的事for (int i 0; i N; i) { window[i] 0.5 * (1 - cos(2 * PI * i / (N - 1))); data[i] (double)sample[i] / 32768.0 * window[i]; }對(duì)比加窗和未加窗的頻譜圖你會(huì)看到主瓣附近的拖尾明顯減少了這個(gè)對(duì)比也值得寫(xiě)進(jìn)報(bào)告中。3.3 簡(jiǎn)單濾波器的參數(shù)設(shè)計(jì)濾波器實(shí)驗(yàn)一般要求實(shí)現(xiàn)低通或高通濾波然后對(duì)比濾波前后的波形和頻譜。最簡(jiǎn)單的方式是在頻域直接操作做FFT把超過(guò)截止頻率的部分置0或縮小再做逆變換。這種方法思路清晰也容易和理論對(duì)應(yīng)。截止頻率的選擇要結(jié)合采樣率來(lái)定。比如采樣率44.1kHz想保留4kHz以下頻段那FFT結(jié)果中超過(guò)4000/44100的位置就應(yīng)該被處理掉。注意處理時(shí)要同時(shí)操作對(duì)稱(chēng)的兩個(gè)頻率點(diǎn)因?yàn)镕FT結(jié)果是共軛對(duì)稱(chēng)的只處理一半會(huì)導(dǎo)致時(shí)域波形出現(xiàn)虛部殘留。4. 視頻實(shí)驗(yàn)的關(guān)鍵步驟與代碼框架4.1 YUV序列的讀取策略視頻實(shí)驗(yàn)用的數(shù)據(jù)集通常是原始YUV序列沒(méi)有壓縮每個(gè)像素的Y、U、V分量按固定格式排列。常見(jiàn)格式是4:2:0意味著每四個(gè)像素共享一組U和V分量。編碼時(shí)要精確計(jì)算每一幀的讀取大小。假設(shè)分辨率為width×heightY分量有width×height個(gè)字節(jié)U和V分量各有width×height/4個(gè)字節(jié)。一幀的總字節(jié)數(shù)就是1.5倍的width×height。我見(jiàn)過(guò)不少同學(xué)的代碼Y分量讀出來(lái)了但U和V分量的指針偏移算錯(cuò)導(dǎo)致播放出來(lái)的畫(huà)面顏色是花的。計(jì)算偏移時(shí)用整數(shù)乘法不要用浮點(diǎn)運(yùn)算避免精度誤差。4.2 幀間差分的代碼思路幀間差分是運(yùn)動(dòng)檢測(cè)里最簡(jiǎn)單的實(shí)現(xiàn)方式也是視頻實(shí)驗(yàn)的常見(jiàn)小題目。核心思路是逐幀計(jì)算兩個(gè)相鄰幀對(duì)應(yīng)位置像素的絕對(duì)差超過(guò)某個(gè)閾值就標(biāo)記為運(yùn)動(dòng)區(qū)域。for (int y 0; y height; y) { for (int x 0; x width; x) { int diff abs(frame1[y * width x] - frame2[y * width x]); diffFrame[y * width x] (diff threshold) ? 255 : 0; } }閾值怎么定經(jīng)驗(yàn)值是取整幀平均絕對(duì)差的1.5到2倍也可以固定設(shè)為30到50之間看實(shí)驗(yàn)要求的運(yùn)動(dòng)敏感度。完成差分后建議把結(jié)果保存成BMP或者PGM格式方便在報(bào)告中直觀展示運(yùn)動(dòng)區(qū)域。這里我提前準(zhǔn)備好了完整的YUV序列和轉(zhuǎn)換好的可視化代碼后面會(huì)一起分享。4.3 視頻播放器的簡(jiǎn)易實(shí)現(xiàn)方案有一個(gè)題目需要實(shí)現(xiàn)一個(gè)簡(jiǎn)易播放器把YUV文件逐幀顯示出來(lái)。這部分可以調(diào)用圖形庫(kù)來(lái)簡(jiǎn)化開(kāi)發(fā)Windows下可以用Win32 GDI也可以直接用SDL2SDL2的學(xué)習(xí)成本更低安裝也很簡(jiǎn)單。SDL2顯示YUV幀的流程是創(chuàng)建紋理將YUV數(shù)據(jù)拷貝到紋理然后渲染到窗口。核心代碼如下SDL_Texture* texture SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, width, height); SDL_UpdateTexture(texture, NULL, buffer, width); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer);這里有一個(gè)細(xì)節(jié)SDL_PIXELFORMAT_YV12對(duì)應(yīng)的是Y、V、U排列順序而常見(jiàn)的YUV420P是Y、U、V排列兩者容易混淆拷貝數(shù)據(jù)時(shí)要注意。如果畫(huà)面顏色不對(duì)優(yōu)先檢查分量順序。5. 現(xiàn)場(chǎng)調(diào)試記錄與常見(jiàn)問(wèn)題5.1 代碼通過(guò)但圖像輸出全黑這是出現(xiàn)頻率最高的問(wèn)題。原因通常是文件頭讀取失敗像素?cái)?shù)據(jù)的偏移沒(méi)有正確跳過(guò)文件頭導(dǎo)致讀進(jìn)來(lái)的全部是文件頭二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成灰度值后自然接近0。排查步驟是先打印fileHeader.bfOffBits的值確認(rèn)是否正確再檢查讀取像素?cái)?shù)據(jù)時(shí)fseek的偏移量是否用上了這個(gè)字段。如果手動(dòng)設(shè)置了偏移量為固定的54碰到帶調(diào)色板的8位BMP就會(huì)出問(wèn)題。5.2 直方圖均衡化后圖像偏色直方圖均衡化通常應(yīng)用在灰度圖上但如果你在RGB三個(gè)通道上分別做均衡化就會(huì)出現(xiàn)偏色。原因是三個(gè)通道的直方圖分布不同分別映射后原本的色平衡就被破壞了。解決辦法有兩種一是先把RGB轉(zhuǎn)成YCbCr只對(duì)Y分量做均衡化再轉(zhuǎn)回RGB二是如果堅(jiān)持在RGB上做需要統(tǒng)一使用同一個(gè)累計(jì)分布映射表而不是各自獨(dú)立映射。課程實(shí)驗(yàn)推薦用第一種方案不但在理論上是標(biāo)準(zhǔn)做法效果也更自然。5.3 音頻濾波結(jié)果有爆音或者噪音濾波后的信號(hào)在首尾兩端經(jīng)常會(huì)出現(xiàn)突變表現(xiàn)為咔嗒的爆音。原因是對(duì)數(shù)據(jù)加窗或做FFT時(shí)把首尾樣本截?cái)嗔藢?dǎo)致重建信號(hào)出現(xiàn)了不連續(xù)點(diǎn)。解決辦法是處理完后做一個(gè)淡入淡出處理也就是讓信號(hào)的首尾逐漸過(guò)渡到0。具體的做法可以前后各取大約10毫秒的數(shù)據(jù)做線(xiàn)性漸變效果立竿見(jiàn)影。5.4 VC6或VS編譯不通過(guò)報(bào)錯(cuò)缺頭文件不少學(xué)校上機(jī)環(huán)境還在用VC6或者老版本VS加載代碼時(shí)經(jīng)常報(bào)錯(cuò)缺某些頭文件。處理方式是檢查代碼里是否引用了C99以后才有的頭文件和函數(shù)如stdint.h中的類(lèi)型定義。如果沒(méi)有特殊要求可以直接在代碼里用int、unsigned char等基本類(lèi)型替代避免兼容性麻煩。還要注意scanf、fopen這類(lèi)函數(shù)在VS高版本下提醒使用安全版本可以在代碼開(kāi)頭加一句宏定義禁用安全警告#define _CRT_SECURE_NO_WARNINGS5.5 視頻播放卡頓嚴(yán)重SD播放器播放YUV文件卡頓問(wèn)題多半不是渲染性能而是文件讀取的方式太慢。如果逐字節(jié)fread讀數(shù)據(jù)每一幀要幾千次系統(tǒng)調(diào)用自然會(huì)卡。優(yōu)化方式是批量讀取一次fread讀入一整幀int frameSize width * height * 3 / 2; fread(frameBuffer, 1, frameSize, fp);如果還是卡可以考慮用雙緩沖或預(yù)讀下一幀的方式但課程實(shí)驗(yàn)的數(shù)據(jù)集通常不大批量讀取已經(jīng)足夠流暢。6. 數(shù)據(jù)集與代碼的配套使用指南6.1 文件結(jié)構(gòu)說(shuō)明我整理好的壓縮包內(nèi)部結(jié)構(gòu)如下image_set文件夾包含Lena圖、Baboon圖、Peppers圖等經(jīng)典BMP圖像以及對(duì)應(yīng)的灰度版本audio_set文件夾包含8k采樣率語(yǔ)音、44.1k采樣率音樂(lè)均為WAV格式video_set文件夾包含QCIF格式的YUV序列code文件夾內(nèi)包含image_process、audio_process、video_process三個(gè)子目錄report_template文件夾里面有實(shí)驗(yàn)報(bào)告的常用框架和對(duì)比圖模板數(shù)據(jù)集的選擇是經(jīng)過(guò)對(duì)比驗(yàn)證的Lena圖的紋理和漸變區(qū)域分布均衡直方圖均衡化效果明顯適合做灰度變換Baboon圖的紋理極其復(fù)雜適合做DCT變換、JPEG壓縮能直觀看出高頻分量對(duì)清晰度的影響Peppers圖則宜展示彩色圖像的通道操作和色彩空間轉(zhuǎn)換。6.2 代碼的使用方式代碼以全局目錄形式組織不依賴(lài)各實(shí)驗(yàn)之間的中間結(jié)果每個(gè)模塊都可以獨(dú)立運(yùn)行。image_process模塊把BMP圖像放在同一目錄下運(yùn)行main.exe按提示選擇功能編號(hào)即可依次執(zhí)行灰度化、直方圖均衡化、傅里葉變換、DCT變換和JPEG模擬壓縮。結(jié)果圖保存在result子目錄下audio_process模塊默認(rèn)讀取test.wav分析采樣率、位深、聲道數(shù)輸出波形和頻譜圖同時(shí)包含低通和高通濾波功能video_process模塊輸入一個(gè)YUV序列文件路徑和分辨率參數(shù)可提取指定幀、做幀差運(yùn)動(dòng)檢測(cè)或逐幀播放如果想要直接編譯運(yùn)行C源代碼需要用VS打開(kāi)工程文件注意配置好附加依賴(lài)項(xiàng)如果只有g(shù)cc環(huán)境也可以用命令行逐個(gè)編譯我在每個(gè)子文件夾里都放了編譯命令的說(shuō)明文件。6.3 報(bào)告輔助材料的準(zhǔn)備報(bào)告是實(shí)驗(yàn)得分的關(guān)鍵一環(huán)通常占評(píng)分的一半以上。不要只放代碼和運(yùn)行截圖還要有必要的中間數(shù)據(jù)表格和對(duì)比分析。圖像類(lèi)實(shí)驗(yàn)放直方圖均衡化前后的直方圖對(duì)比、DCT系數(shù)分布圖和重構(gòu)圖像的誤差數(shù)據(jù)音頻類(lèi)實(shí)驗(yàn)放濾波前后頻譜圖的對(duì)比以及采樣率與截止頻率的關(guān)系表視頻類(lèi)實(shí)驗(yàn)放幀差分的可視化結(jié)果標(biāo)注閾值選擇對(duì)檢測(cè)效果的影響7. 最后再分享幾個(gè)實(shí)用小技巧實(shí)驗(yàn)代碼里我加入了不少中文注釋方便逐行理解思路考試如果要手寫(xiě)核心算法看注釋就能回憶起來(lái)。另外所有的圖像操作都做了控制臺(tái)打印輸出每一步操作前后關(guān)鍵參數(shù)的變化比如灰度范圍、均值、方差等報(bào)告展示時(shí)非常有說(shuō)服力。最后一個(gè)技巧盡量把自己寫(xiě)的代碼和調(diào)用庫(kù)的代碼區(qū)分開(kāi)。有的實(shí)驗(yàn)直接用OpenCV的庫(kù)函數(shù)一行搞定確實(shí)省時(shí)間但進(jìn)度演示或答辯時(shí)被問(wèn)到具體實(shí)現(xiàn)容易露餡。建議核心算法自己手寫(xiě)只在顯示、格式轉(zhuǎn)換這些輔助環(huán)節(jié)用第三方庫(kù)這樣代碼量既不夸張含金量又夠答辯時(shí)心里也踏實(shí)。本文還有配套的精品資源點(diǎn)擊獲取