使用CNN預(yù)測HEVC的CU分割 (1) -- 構(gòu)建數(shù)據(jù)集

HEVC自帶的編碼器在決定CTU的最佳分割深度的時候會花費(fèi)很多時間盏求,我們的目標(biāo)是訓(xùn)練卷積神經(jīng)網(wǎng)絡(luò)來根據(jù)輸入的幀預(yù)測這一幀圖像上面的所有CTU的最佳分割深度,這樣會比HEVC自帶的編碼器快很多亿眠。

我們需要做的主要有:

  • 構(gòu)建用于訓(xùn)練神經(jīng)網(wǎng)絡(luò)的數(shù)據(jù)集
  • 數(shù)據(jù)預(yù)處理
  • 構(gòu)建卷積神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)碎罚,用深度學(xué)習(xí)框架實(shí)現(xiàn)
  • 訓(xùn)練及改進(jìn)模型

我自己生成的數(shù)據(jù)集在GitHub,包含了訓(xùn)練纳像、測試荆烈、驗(yàn)證集:

GitHub - wolverinn/HEVC-CU-depths-dataset: A dataset that contains the Coding Unit image files and their corresponding depths for HEVC intra-prediction.

現(xiàn)在我主要做了構(gòu)建數(shù)據(jù)集和數(shù)據(jù)預(yù)處理的工作。HEVC的編碼器的輸入是一幀一幀的圖像竟趾,輸出是圖像中的每個CTU的最佳分割深度耙考。因此需要構(gòu)建的數(shù)據(jù)集就是圖像以及該圖像對應(yīng)的分割信息。

HEVC部分源碼剖析中潭兽,通過分析HEVC編碼器部分的源代碼,我們已經(jīng)可以對輸入的每一幀圖像斗遏,輸出它的CTU分割信息:

PartitionInfo

向HEVC的編碼器送入一個YUV文件山卦,這個YUV文件會被分解成很多幀圖像(frame),每個圖像又分成若干個CTU,每個CTU會有一個16x16的分割信息账蓉。因此產(chǎn)生數(shù)據(jù)集的思路就是:使用HEVC的編碼器處理YUV文件枚碗,就可以得到這個YUV文件的每一幀的CTU的分割信息,再用FFmpeg命令:

ffmpeg -video_size 832x480 -r 50 -pixel_format yuv420p -i BasketballDrill_832x480_50.yuv output-%d.png

將這個YUV文件分解成一幀一幀的圖像铸本,就得到了用作神經(jīng)網(wǎng)絡(luò)輸入的圖像肮雨。

但是神經(jīng)網(wǎng)絡(luò)的訓(xùn)練要求一個很大的數(shù)據(jù)集,所以顯然不能人工一個個地去產(chǎn)生每個YUV文件的CTU分割信息和對應(yīng)的每一幀圖像箱玷。需要一個可以批量生成數(shù)據(jù)的腳本怨规,獲取YUV文件,調(diào)用HEVC編碼器锡足,然后使用FFmpeg從YUV文件中提取出每一幀波丰。并且由于圖像和分割信息是單獨(dú)存放,我們還需要注意命名的問題舶得,確保每張圖片和分割信息可以對應(yīng)掰烟。

首先是如何自動化調(diào)用HEVC的編碼器,先對源代碼進(jìn)行修改沐批,使其能夠輸出格式化的分割信息到txt文件纫骑,然后進(jìn)行編譯。HEVC編碼器部分在編譯完成后對應(yīng)的可執(zhí)行文件是TAppEncoder.exe九孩,它接受-c config_file作為輸入?yún)?shù)先馆,我們可以在config_file中指定輸入的YUV文件的一些參數(shù)如幀率,寬度高度等捻撑。然后運(yùn)行編碼器獲取輸出的txt文件磨隘。

自動化調(diào)用HEVC編碼器并輸出格式化的分割信息到txt文件的代碼如下:

import os

# this script needs to be in the same directory of the two config files and the encoder.exe: WORKSPACE_PATH
YUV_FILE_PATH = "E:\\HM\\trunk\\workspace\\yuv-resources"
WORKSPACE_PATH = os.getcwd()
CtuInfo_FILENAME = "BasketballdrillCU.txt"

def gen_cfg(yuv_filename):
    FrameRate = yuv_filename.split('_')[2].strip(".yuv")
    SourceWidth = yuv_filename.split('_')[1].split('x')[0]
    SourceHeight = yuv_filename.split('_')[1].split('x')[1]
    with open('bitstream.cfg','w') as f:
        f.write("InputFile : {}\\{}\n".format(YUV_FILE_PATH,yuv_filename))
        f.write("InputBitDepth : 8\n")
        f.write("InputChromaFormat : 420\n")
        f.write("FrameRate : {}\n".format(FrameRate))
        f.write("FrameSkip : 0\n")
        f.write("SourceWidth : {}\n".format(SourceWidth))
        f.write("SourceHeight : {}\n".format(SourceHeight))
        f.write("FramesToBeEncoded : 15000\n")
        f.write("Level : 3.1")

encoding_cmd = "TAppEncoder.exe -c encoder_intra_main.cfg -c bitstream.cfg"
for i,yuv_filename in enumerate(os.listdir(YUV_FILE_PATH)):
    gen_cfg(yuv_filename)
    os.system(encoding_cmd)
    os.rename(CtuInfo_FILENAME,"v_{}.txt".format(str(i)))

然后再使用FFmpeg獲取YUV文件中的幀,在for循環(huán)中加入:

    gen_frames_cmd = "ffmpeg -video_size {} -r {} -pixel_format yuv420p -i {}\\{} {}\\img-train\\v_{}_%d_.jpg".format(yuv_filename.split('_')[1],yuv_filename.split('_')[2].strip(".yuv"),YUV_FILE_PATH,yuv_filename,WORKSPACE_PATH,str(i))
    os.system(gen_frames_cmd)

這樣我們就能得到命名規(guī)范的數(shù)據(jù)集了:

dataset

接下來需要考慮數(shù)據(jù)預(yù)處理的問題顾患,需要預(yù)處理的數(shù)據(jù)包括圖片數(shù)據(jù)和分割信息的數(shù)據(jù)番捂。當(dāng)我們向神經(jīng)網(wǎng)絡(luò)中送入圖片時,圖片大小最好是2的整數(shù)次冪或者一個較大的整數(shù)次冪的倍數(shù)江解,這樣有利于神經(jīng)網(wǎng)絡(luò)的處理设预。這里我采用的方式是論文 "Fast CU Depth Decision for HEVC Using Neural Networks" 中所使用的每次送入一個CTU大小,也就是64x64 犁河,所以鳖枕,使用Pillow庫讀取一張完整的圖片之后,需要定位到此次要提取的CTU桨螺,然后將這個CTU裁剪下來宾符,使用Image.crop()函數(shù)。

定位CTU及裁剪的圖像處理代碼如下:

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.data as data
import torch.optim as optim
from torch.autograd import Variable
from torchvision import datasets, transforms
import os
import numpy as np
from PIL import Image
import time
import math

BATCH_SIZE=512
EPOCHS=10 # 總共訓(xùn)練批次
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 讓torch判斷是否使用GPU
IMG_WIDTH = 1920
IMG_HEIGHT = 1080
transform = transforms.Compose([
    transforms.ToTensor(),  # 將圖片轉(zhuǎn)換為Tensor,歸一化至[0,1]
    # transforms.Normalize(mean=[.5, .5, .5], std=[.5, .5, .5])  # 標(biāo)準(zhǔn)化至[-1,1]
])

class ImageSet(data.Dataset):
    def __init__(self,root):
        # 所有圖片的絕對路徑
        self.img_files = []
        self.root = root
        for imgs in os.listdir(root):
            self.img_files.append(imgs)
        self.transforms=transform

    def __getitem__(self, index):
        ctu_number_per_img = math.ceil(IMG_WIDTH/64)*math.ceil(IMG_HEIGHT/64)
        img_index = index//ctu_number_per_img
        ctu_number = index%ctu_number_per_img
        whole_img = Image.open(os.path.join(self.root,self.img_files[img_index]))
        img_row = ctu_number//math.ceil(IMG_WIDTH/64)
        img_colonm = ctu_number%math.ceil(IMG_WIDTH/64)
        start_pixel_x = (img_colonm-1)*64
        start_pixel_y = img_row*64
        img = whole_img.crop((start_pixel_x,start_pixel_y,start_pixel_x+64,start_pixel_y+64))
        video_number = self.img_files[img_index].split('_')[1]
        frame_number = self.img_files[img_index].split('_')[2]
        if self.transforms:
            data = self.transforms(img)
        else:
            img = np.asarray(img)
            data = torch.from_numpy(img)
        label = from_ctufile(video_number,frame_number,str(ctu_number))
        return data,label

    def __len__(self):
        return len(self.img_files)

這里繼承了pytorch中的data.Dataset類灭翔,是為了訓(xùn)練神經(jīng)網(wǎng)絡(luò)加載數(shù)據(jù)的時候比較方便魏烫,加載時直接使用:

train_loader = data.DataLoader(ImageSet("./img-train/"),batch_size=BATCH_SIZE,shuffle=True)

這樣,在類ImageSet中的函數(shù)__getitem__()返回的“data”就是經(jīng)過裁剪的圖片數(shù)據(jù)。

接下來還需要處理作為“l(fā)abel”的CTU分割信息文件哄褒,每個CTU的分割信息都是16x16的矩陣稀蟋,由于4x4已經(jīng)是最小的分割了,因此我們只需要從CTU的分割矩陣中提取出16個分割深度信息就可以了呐赡,可以直觀地看一下:

one CTU

在這個矩陣中退客,每個4x4的小塊只需要提取出一個深度信息就夠了,最后這個16x16的矩陣可以提取出16個分割深度信息链嘀。除此之外萌狂,我們還需要確保之前裁剪的CTU和這次提取的CTU是同一個幀內(nèi)部的同一個CTU,提取label的函數(shù)在__getitem__()中的調(diào)用是:

label = from_ctufile(video_number,frame_number,str(ctu_number))

使用video_number, frame_number, ctu_number來定位至某個YUV文件下的某一幀的某一個CTU管闷,這個提取label的函數(shù)定義為:

def from_ctufile(video_number,frame_number,ctu_number):
    ctu_file = "v_{}.txt".format(video_number)
    frame_detected = 0
    ctu_detected = 0
    converting = 0
    label_list = []
    with open(ctu_file, 'r') as f:
        for i, line in enumerate(f):
            if ctu_detected == 1 and "ctu" in line:
                label = torch.FloatTensor(label_list)  # https://pytorch-cn.readthedocs.io/zh/latest/package_references/Tensor/
                return label
            if frame_detected == 0:
                if "frame" in line:
                    current_frame = line.split(':')[1]
                    if int(frame_number)-1 == int(current_frame):
                        frame_detected = 1
                    else:
                        continue
                else:
                    continue
            elif ctu_detected ==0:
                if "ctu" in line:
                    current_ctu = line.split(':')[1]
                    if int(ctu_number) == int(current_ctu):
                        ctu_detected = 1
                    continue
            else:
                if (converting) % 4 == 0:
                    line_depths = line.split(' ')
                    for index in range(16):
                        if index % 4 == 0:
                            label_list.append(line_depths[index])
                    converting += 1
                else:
                    converting += 1

在含有整個YUV文件的分割信息的txt文件中粥脚,先定位frame,再定位CTU包个,然后進(jìn)行轉(zhuǎn)換刷允,得到長度為16的label

至此,數(shù)據(jù)準(zhǔn)備工作差不多完成了碧囊,還差一步树灶,就是YUV文件的獲取,要構(gòu)建數(shù)據(jù)集糯而,需要大量的高度和寬度相同的YUV文件天通,可以先嘗試從網(wǎng)上下載現(xiàn)有資源,如果不夠的話可以用FFmpeg自己通過其它格式的視頻生成

我自己生成的數(shù)據(jù)集GitHub地址:

HEVC-CU-depths-dataset

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末熄驼,一起剝皮案震驚了整個濱河市像寒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓜贾,老刑警劉巖诺祸,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異祭芦,居然都是意外死亡筷笨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門龟劲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胃夏,“玉大人,你說我怎么就攤上這事昌跌⊙鲑鳎” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵蚕愤,是天一觀的道長答恶。 經(jīng)常有香客問我囊榜,道長,這世上最難降的妖魔是什么亥宿? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮砂沛,結(jié)果婚禮上烫扼,老公的妹妹穿的比我還像新娘。我一直安慰自己碍庵,他們只是感情好映企,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著静浴,像睡著了一般堰氓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苹享,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天双絮,我揣著相機(jī)與錄音,去河邊找鬼得问。 笑死囤攀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宫纬。 我是一名探鬼主播焚挠,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼漓骚!你這毒婦竟也來了蝌衔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蝌蹂,失蹤者是張志新(化名)和其女友劉穎噩斟,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叉信,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亩冬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了硼身。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硅急。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖佳遂,靈堂內(nèi)的尸體忽然破棺而出营袜,到底是詐尸還是另有隱情,我是刑警寧澤丑罪,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布荚板,位于F島的核電站凤壁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏跪另。R本人自食惡果不足惜拧抖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望免绿。 院中可真熱鬧唧席,春花似錦、人聲如沸嘲驾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辽故。三九已至徒仓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間誊垢,已是汗流浹背掉弛。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彤枢,地道東北人狰晚。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像缴啡,于是被迫代替她去往敵國和親壁晒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容