Deinterlacing videa z Pentaxu K3 - dokončenie

20.10 | 15:00 | Mirecove dristy | Miroslav Bendík

V mojom minulom blogu o deinterlacingu som skončil pri použití filtra QTGMC. Dnešný blog bude obsahovať trochu teórie a výrazný tuning QTGMC.

Ako vyzerá prekladané video

Prekladané video sa na rozdiel od progresívneho videa skladá z polsnímkv s dvojnásobnou frekvenciou snímkov. Polsnímky obsahujú striedavo párne a nepárne riadky. Pri deinterlacingu sa buď skladajú 2 polsnímky do kompletného snímku a vznikne video s polovičnou frekvenciou snímkov (60i - 30p), alebo sa pre každý polsnímok dorátajú striedavo párne a nepárne riadky a vtedy vznikne video s plnou frekvenciou (60i - 60p).

Na nasledujúcej animácii sú 2 po sebe idúce snímky.

Pôvodná animácia
Obrázok 1: Pôvodná animácia

V prekladanom videu by boli 2 po sebe idúce snímky spojené do jedného snímku kde párne riadky by patrili jednému snímku a nepárne druhému. Statické časti obrazu zostávajú rovnaké ako pri progresívnom videu, ale pohyblivé časti sa rozštiepia na párne a nepárne riadky.

Správne prekladaný obraz
Obrázok 2: Správne prekladaný obraz

Pentax však zaznamenáva vždy len párne (alebo nepárne) snímky, takže reálne vertikálne rozlíšenie je polovičné.

Video nahrané pentaxom
Obrázok 3: Video nahrané pentaxom

Algoritmus

Našťastie existuje celá rodina algoritmov, ktoré pracujú len so snímkom s polovičným rozlíšením. Tieto algoritmy môžu mať pri korektne prekladanom obraze roztrasený obraz pretože pre každý polsnímok sa dopočítava obrázok z trochu rozdielnych dát. Z pentaxu by však statické časti mali byť pre oba polsnímky rovnaké a pri správne použitom algoritme by mal byť obraz stabilnejší.

Nasledujúci kód rieši väčšinu problémov s deinterlacingom z pentaxu.

import havsfunc as haf
import numpy as np
import os
import vapoursynth as vs


def modify_frame(n, f):
    frame = f.copy()

    for plane in range(frame.format.num_planes):
        write_array = np.array(frame.get_write_array(plane), copy=False)

        # Fix extra line on every second frame
        if n % 2 == 1:
            write_array[:] = np.roll(write_array, 1, axis=0)
            write_array[0,:] = write_array[1,:]

        # Double height frame
        copy = write_array.copy()[0:write_array.shape[0]//2,:] # Copy top half of frame
        write_array[0::2,:] = copy # Assign to odd and even lines
        write_array[1::2,:] = copy

    return frame


core = vs.get_core()
core.max_cache_size = 1024
clip = core.ffms2.Source(source=os.environ['VIDEO_SOURCE'])
clip = core.std.SelectEvery(clip=clip, cycle=2, offsets=0)
clip = core.std.AddBorders(clip=clip, bottom=clip.height)
clip = core.std.SeparateFields(clip)
clip = core.std.ModifyFrame(clip, clip, modify_frame)

#clip = core.nnedi3.nnedi3(clip=clip, field=0, nsize=0, nns=2, qual=2, etype=1)
clip = haf.QTGMC(
    Input=clip,
    Preset="Placebo",
    TFF=True,
    FPSDivisor=2,
    Lossless=0
)
clip.set_output()

Kód deinterlace.vpy sa používa nasledovne:

export VIDEO_SOURCE=video.mkv
vspipe ./deinterlace.vpy - --y4m|\
ffmpeg -i - -c:v libx264 -preset slow -crf 15 -r 60000/1001 -pix_fmt yuvj420p out.mkv

Analýza kódu

Prejdime si jednotlivé časti kódu. Na začiatku sa nastavujú parametre backendu.

core = vs.get_core()
core.max_cache_size = 1024

Klip sa načítava pomocou ffmpeg backendu.

clip = core.ffms2.Source(source=os.environ['VIDEO_SOURCE'])
Načítané video
Obrázok 4: Načítané video

Na mojom systéme ffmpeg načítava prekladané snímky ako 2 po sebe idúce rovnaké snímky. Tu si nie som istý, či to je korektné správanie, alebo bug. Nasledujúci riadok odstráni polovicu snímkov, takže vznikne video bez duplicitných snímkov.

clip = core.std.SelectEvery(clip=clip, cycle=2, offsets=0)

V ďalšom kroku sa zväčší klip na dvojnásobnú výšku (pridá sa čierny pás dole).

clip = core.std.AddBorders(clip=clip, bottom=clip.height)
Pridaný čierny pás
Obrázok 5: Pridaný čierny pás

Nasleduje rozdelenie snímku na dva samostatné polsnímky.

clip = core.std.SeparateFields(clip)
Rozdelený klip
Obrázok 6: Rozdelený klip

Každý druhý snímok je vertikálne posunutý o jeden pixel. Kvôli tomu obraz poskakuje hore a dole. Funkcia modify_frame pomocou numpy opravuje poskakovanie a zväčší snímok na plnú výšku (tu sa využije predtým pridaný čierny pás).

clip = core.std.ModifyFrame(clip, clip, modify_frame)

Funkcia sa volá pre každý snímok s parametrami n (číslo snímku) a f (inštancia snímku). Začíname kopírovaním snímku (nie je dobré modifikovať snímok zo vstupného prúpdu).

def modify_frame(n, f):
	frame = f.copy()

Kód sa bude spúšťať nad každou zložkou obrazu (jas a farba sú uložené samostatne).

	for plane in range(frame.format.num_planes):

Časť snímku sa konvertuje do numpy poľa.

		write_array = np.array(frame.get_write_array(plane), copy=False)

Pre nepárne snímky sa obraz posunie o jeden pixel smerom nadol. Prvý riadok nepárnych snímkov sa skopíruje z druhého riadka.

		if n % 2 == 1:
			write_array[:] = np.roll(write_array, 1, axis=0)
			write_array[0,:] = write_array[1,:]
Opravený klip
Obrázok 7: Opravený klip

Snímok sa roztiahne na plnú výšku. Nasledujúci kód priradí kópiu polovice snímku (copy) párnym a nepárnym riadkom. Výsledný snímok má dvojnásobné rozlíšenie.

		copy = write_array.copy()[0:write_array.shape[0]//2,:]
		write_array[0::2,:] = copy
		write_array[1::2,:] = copy
Dvojnásobná výška snímku
Obrázok 8: Dvojnásobná výška snímku

Výsledné video sa preženie deinterlacerom. Ja používam QTGMC pretože si vie celkom slušne poradiť s chybami pri kompresii. Prekladaný obraz sa totiž môže komprimovať samostatne pre párne a nepárne riadky a čistý nnedi3 filter pre takýto vstup vyprodukuje obraz v ktorom sa bude veľmi rýchlo meniť odtieň na väčších plochách. QTGMC tieto problémy čiastočne rieši.

Interne QTGMC používa neurónové siete (nnedi3 filter) a množstvo ďalších filtrov. Popis parametrov je v dokumentácii. Ja som pri testoch použil nasledujúce parametre:

Preset
Predvolené nastavenia parametrov (maximálna kvalita).
TFF
Príznak pre používanie párnych, alebo nepárnych riadkov. Keďže vychádzame z polovičného rozlíšenia je hodnota tohto parametra irelevantná.
FPSDivisor
Tento filter štandardne generuje pre každý vstupný snímok dva výstupné snímky. Parametrom FPSDivisor nastavujem aby pre každý snímok bol generovaný jeden výstupný snímok.
Lossless
Pri nastavení tohto parametra by sa do generovania výstupnej snímky zarátali oba polsnímky a výstupné rozlíšenie by kvôli tomu bolo polovičné (keďže vstup je polovičný).
Rekonštrukcia obrazu
Obrázok 9: Rekonštrukcia obrazu

Výsledky

Teraz sa pozrime na výsledky z predchádzajúceho blogu a porovnajme s vylepšenou verziou.

Staré QTGMC - statické časti sú nestabilné
Obrázok 10: Staré QTGMC - statické časti sú nestabilné
Oprava QTGMC
Obrázok 11: Oprava QTGMC

Po oprave zostávajú statické časti stabilné.

Staré QTGMC - pohyblivá časť
Obrázok 12: Staré QTGMC - pohyblivá časť
Oprava QTGMC
Obrázok 13: Oprava QTGMC

Pohyblivé časti vyzerajú tak isto v poriadku.

Prílohy

    • RE: Deinterlacing videa z Pentaxu K3 - dokončenie 31.10 | 22:19
      Avatar bedňa LegacyIce-antiX  Administrátor

      Kámo pekné čítanie.

      Ale prečo si nepožil 1080p/30fps a nepoužil naurálnu sieť na dopočítanie na 1080p/60fps? Určite by to bolo bez tejto zbytočnej práce. Mno hlavne keď prekladané video nie je prekladané. To by som im vrátil ako šmejd ktorý si môžu dovoliť čínske šmejdy kde sa to očakáva a nie značkový produkt.

      Pentax u mňa klesol na dno.

      Táto správa neobsahuje vírus, pretože nepoužívam MS Windows. http://kernelultras.org