PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : MaskTool & Kanten stabilisieren


Kika
25. February 2006, 23:22
Hallo,

ich habe angefangen, mich auch mal mit den MaskTools zu befassen.
Hintergrund ist, dass ich S-VHS-C-Tapes habe, bei denen mein Recorder zwar die Farbprobleme, nicht aber das horizontale "Wackeln" der Kanten feiner Objekte beseitigen kann. Also wollte ich mir einen Filter schreiben, der nur die Kanten stabilisiert, nicht aber die Schärfe angreift. So richtig gelungen ist das nicht. Eine der Varianten (ich habe viele ausprobiert) sieht so aus:

Function stabivhs(clip clp) {
vid1=clp.separatefields().undot()
vid2=vid1.TemporalSoften(3,4,8,15,2)
mask=vid1.edgeMask(18,18,10,10)
vid=MaskedMerge(vid1,vid2,mask)
return(vid.weave()) }


Die Kanten werden tatsächlich etwas stabiler, das Verschmieren durch den TemporalSoften hält sich in Grenzen, aber die Schärfe leidet dennoch etwas.
Das geht doch bestimmt noch besser, oder?
Probehalber habe ich mittels Logic() die Masken mehrerer aufeinander folgender Frames kombiniert, das Ergebnis war nicht unbedingt besser. Wahrscheinlich benötige ich aber genau das, zusammen mit einem anderen - und besseren - Filter. Selbst einen rein horizontalen Blur habe ich ausprobiert - brachte nicht viel.

Noch was: Als ich mir die erzeugten Masken von EdgeMask und MotionMask ansah, waren die teileweise grün/weis, teilweise schwarz/weiß - das hat mich etwas verwirrt. Ist das normal oder braucht's da noch ein Greyscale()? In den diversen Scripten, die MaskTools benutzen, wird Greyscale aber nur ganz selten benutzt. Und bevor ich mir jetzt den Sonntag auch noch um die Ohren schlage, frage ich lieber mal nach. Das wird nämlich wichtig, wenn ich zusätzlich noch Overlay auf die Masken anwenden möchte, um eine Gewichtung zu erzielen.

BTW: Inpand/Expand sowie Deflate/Inflate sind mir auch nicht so ganz klar.

Nachtrag: Das hier habe ich auch ausprobiert, ist in diesem Fall aber auch keine echte Lösung.
http://forum.gleitz.info/showthread.php?t=25566

Didée
26. February 2006, 04:09
Ein kurzes Sample wär' mal wieder hilfreich ... vor'm inneren Auge hätte ich folgende theoretische Abfolge:

1) vertikales Blurren
2) vertikale Kanten über horizontale Links-Rechts-Differenz maskieren (das ist eine Hälfte von FineEdge, im iiP-Script enthalten)
3) diese Maske horizontal expand()-ieren, und
4) [optional*]
5) über mehrere Frames "AND"-Verknüpfen (hier: mit "logic("min") )
6) eventuell nochmal horizontal expandieren
7) auf maskierte Bereiche temporalen Filter anwenden

Mit 1) wird der horizontale Jitter weggeschmiert, damit 2) die Kanten besser findet. Mit 5) wird die Kanten-Maskierung dort reduziert, wo die Kanten in der Frame-Abfolge nicht am selben Platz sind (also in Bewegung). 3) erweitert hierfür die Toleranz gegen kleine Bewegung. Optional könnte man das ganze in 4) über Bewegungskompensation per MVTools verbessern. Nach 5) ist die Maskierung wahrscheinlich "dünn", das zeigt sich dann. Wenn ja, nochmal horizontal expandieren (6).
Mit dem temporalen Filter in 7) muss man spielen - TemporalSoften geht immer, ist vielleicht aber zu einfach. Würde mal einen temporalen Medianfilter probieren ("medianblurT" aus medianblur.dll, gibts auf Warpenterprises), eventuell mit vorrausgehendem FluxSmoothT(255).

So oder so ähnlich:
source=last

blur(0,1)
EdgeMask(0,255,0,255,"8 0 -8 16 0 -16 8 0 -8",divisor=4)
mt_expand(mode="horizontal")

## [ Suche Bewegungsvektoren ]
## [ interleave(Kompensationen der Edgemasks) ]

# folgendes entsprechend des temporalen Radius' - hier: +/- 2 Frames
last .logic(last.trim(1.0),"min").logic(last.trim(2,0),"min")
\ .logic(last.duplicateframe(0),"min")
\ .logic(last.trim(0,1)+last,"min")
expand(mode="horizontal").fity2uv()
edge=last

source

## [ interleave(Kompensationen von source) ]

# *eines* der folgenden aktivieren:
# temporalsoften(2,48,48,24,2)
# medianblurT(frames=2) # Parameter nachschauen, weissgradnichsogenau
# FluxSmoothT(255).medianblurT(2)

MaskedMerge(source,last,edge,U=3,V=3)

## [ selectevery(x,1) ]

Das grobe Gerüst für den Einsatz von Bewegungskompensation steht in den ##-Zeilen. Genaues Vorgehen steht in der MVTools-Doku, oder kann auch aus MCNR_simple2 'rausgezogen werden.

Womöglich könnte man sich die Masken-Verknüpferei auch sparen, indem man vertikale Unschärfe und Edgemask vermischt, und das als "Analyse-Clip" in TTempSmooth gibt:
source = last

blur(0,1)
edgeV = EdgeMask(0,255,0,255,"8 0 -8 16 0 -16 8 0 -8",divisor=4)
\ .mt_expand(mode="horizontal")
strange = merge(last.blur(1),edgeV.fity2uv(), 0.5) # 0.25~0.66, probieren

source
TTempSmooth(2,16,16,4,4,5,pfclip=strange)
MaskedMerge(source,last,edgeV.fity2uv(),U=3,V=3)
... das ginge natürlich auch mit Bewegungskompensation.

Alles freihändig getippt, sind bestimmt Fehler drin. :)

Viel Spaß beim probieren, ich bin erst Sonntagabend wieder zu Hause. ;)

***

Die grün-schwarz-Verwirrung ist normal, weil die meisten MaskTools-Befehle defaultmäßig nur auf Luma arbeiten, und der Inhalt der Chroma-Ebenen hinterher rein zufällig ist ("Schrott"). Mit ",U=3,V=3" wird auch Chroma aktiv verarbeitet.

Maskierungen oder maskierte Ergebnisse mittels Overlay() nochmals zu wichten heisst, Eulen nach Athen zu tragen. In den meisten Fällen macht man stattdessen einfach die EdgeMask von vornherein entsprechend dunkler, und gut.

Kika
26. February 2006, 04:58
@Didée

Du schläfst auch nie, oder? ;)
OK, da hast Du mir 'ne Menge zum Nachdenken und Ausprobieren gegeben. Auf die Idee, zu diesem Zweck die MaskTools mit den MVTools zu kombinieren, bin ich nicht gekommen - interessanter Gedanke. :)

Die grün-schwarz-Verwirrung ist normal
Gut zu wissen, ein Hinweis in der Doku wäre da hilfreich gewesen. Nur zur Sicherheit: Es zählen also definitiv nur die Luma-Informationen? Ich kann also als Maske alles Mögliche verwenden, solange die Luma-Werte das repräsentieren, was ich möchte?

Maskierungen oder maskierte Ergebnisse mittels Overlay() nochmals zu wichten heisst, Eulen nach Athen zu tragen. In den meisten Fällen macht man stattdessen einfach die EdgeMask von vornherein entsprechend dunkler, und gut.

Hm, dann hab' wohl ausnahmsweise mal ich zu kompliziert gedacht. Aber gut zu wissen.
Die Idee war, einen genauer definierten Bereich zu schaffen, in dem die Mittelung geschehen soll. Mit Inpand etc. geht das ja nicht so richtig genau. Daher wollte ich die Maske bicubisch verkleinern/vergrößern und mit der Original-Maske per Overlay auf einen Durchschnitt bringen.

vertikales Blurren
Da hab' ich 'nen Aussetzer. Wieso vertikal? Es geht doch um horizontales Zittern. Minimal zwar, aber störend (besonders, wenn ich aus 4:3-Source 16:9-anamorph machen möchte).

vertikale Kanten über horizontale Links-Rechts-Differenz maskieren (das ist eine Hälfte von FineEdge, im iiP-Script enthalten)


Bahnhof? OK, es ist spät (früh?) ... Wozu man Deine Scripte oder Teile davon missbrauchen kann ... faszinierend. ;)

TemporalSoften geht immer, ist vielleicht aber zu einfach
Den anzuwenden, kam mir logisch vor, immerhin geht es ja um eratisches Zittern - das dann temporal zu mitteln lag da irgendwie auf der Hand.

Würde mal einen temporalen Medianfilter probieren
Ist der nicht ein wenig heftig? Die Schärfe des Source-Materials stimmt, stark wirkende Temporal-Filter auf den Chroma-Anteil verbieten sich von selbst, da das der Recorder schon tut - das würde zu heftigem Schmieren und "lebenden Wänden" führen. Aber ein Median? Versaut der mir nicht die Details?
Na ja, Versuch, macht kluch ... ;) Ich werd's ausprobieren.

Alles freihändig getippt
Autsch, da fühlt man sich so klein ... ;)

MaskedMerge(source,last,edge,U=3,V=3)
Merde, die letzten beiden Parameter stehen in der Doku, die ich hab', gar nicht drin.

Ich seh' schon, da hab' ich einiges zu tun. Aber immerhin scheinen meine grundlegenden Gedanken dazu gar nicht mal so verkehrt gewesen zu sein, es gibt also Hoffnung für mich. ;)

Danke.

P.S.:

Ein kurzes Sample wär' mal wieder hilfreich ...
Zu privat, viel zu privat ... :D
Im Ernst, da sind Personen drauf, die nicht so in die Öffentlichkeit wollen.

BTW: Deine Beispiele haben eine Art der Syntax/Notation, die ich so noch nicht kannte. Das macht es etwas schwierig, aus Deinen Beispielen zu lernen. Gibt's da irgendwo eine genauere Doku zu?

scharfis_brain
26. February 2006, 11:34
interessanter thread: http://forum.doom9.org/showthread.php?t=106025

Kika
26. February 2006, 12:53
@scharfis_brain
Den kannte ich schon. So extrem ist das aber in diesem Fall hier nicht. Es ist wirklich nur ein minimales horizontales Zittern an feinen Kanten und Linien.
Aber interessant ist der Thread allemal.

Kika
27. February 2006, 11:01
@Didée
Dein Script funktioniert schon mal besser als meins, viele Fehler waren auch nicht drin. ;)
Aber jetzt komme ich mit den Versionen von MaskTools durcheinander. Die, die ich habe, kennt einige Befehle bzw. Parameter gar nicht, die Du benutzt hast.
Beispielsweise expand(mode="horizontal")

Was war das denn jetzt? MaskTools, MT_MaskTools oder was ganz anderes?

Didée
27. February 2006, 11:33
Och, ich hab kein Problem damit, beide Versionen von MaskTools wild durcheinanderzuwürfeln. Einfach MaskTools.dll und mt_masktools.dll alle beide importieren, spannen, dann schnalzen lassen. :)

Kika
27. February 2006, 11:55
Die MT-Fassung habe ich mir aber noch nicht näher angeschaut.
Da stellt sich aber die Frage, ob man die MaskTools überhaupt noch benötigt, wenn man doch eh MT_MaskTools verwendet?

Didée
27. February 2006, 13:04
Braucht (bräuchte) man auch nicht. MaskTools v2 kann eigentlich alles, was MaskTools v1.5.8 auch kann ... ausser FitY2UV(), das gibt's nicht mehr. Das kann aber inzwischen in MT2 auf andere Art & Weise erreicht werden, ist sogar eleganter.

Ist nur so, dass ich bei den MT2 selber noch desöfteren nachschauen muss/müsste, wie manche Sachen geschrieben werden müssen ... die Syntax der alten MaskTools dagegen sitzt tiefer als die "Mitternachtsformel". :)
Ausserdem ist mir - während Script-Entwicklungen - YV12lutxy immer noch sympatischer als mt_lutxy. Bei komplizierten & durch viel Herum-Spielerei extrem aufgeblasenen Scripten macht es für mich einen *gravierenden* Unterschied, ob ich nach <refresh> ca. 10-15 Sekunden (MT1.5.8), oder 30 Sekunden bis zu einer Minute (MT2) warten muss, bis Avisynth das Script ENDLICH durchgerödelt und neu gerendert hat ... :zz:

Kika
27. February 2006, 13:52
Also ist Dir der Mischmasch lieber als die v2 ...
OK, dann muss ich mich halt mit beiden herumschlagen (nicht dass eines allein nicht schon kompliziert genug wäre ;) )

Eines noch, diese Zeile aus Deinem Script:

EdgeMask(0,255,0,255,"8 0 -8 16 0 -16 8 0 -8",divisor=4)

Die funktionier bei mir (ohne v2) nur, wenn ich EdgeMask durch dgEdgeMask ersetze - war das so im Sinne des Erfinders oder war da generell mt_edge gemeint?
Dasselbe gilt für expand, in der von Dir eingesetzten Form (erstes Script) geht das wohl auch nur mit mt_expand.

Didée
27. February 2006, 14:37
Ach ja, richtig. Für eigene Kernels braucht man in 1.5.8 ja die DEdgeMask()-Funktion. War halt schon spät ...
Man kann das natürlich auch mit MT2 machen, da sind EdgeMask und DEgeMask zu einer einzigen Funktion verschmolzen. Dafür ist die Normalisierung kein eigener Parameter mehr, sondern muss als letzter Wert in den Kernel-String hineingeschrieben werden. Ausserdem ist dieser Faktor nicht mehr völlig frei, sondern wird intern auf die nächste Potenz-von-zwei gerundet.
... alles ganz einfach, nicht wahr? ("Wie war das Leben, ehedem, mit MaskTools alt, doch so bequem ...") :D

Die in/expand-Funktionen mit speziellen "Ausrichtungen" sind neu in MaskTools v2, genau.

Kika
27. February 2006, 15:35
Jungejunge, sollte ich je meinen Memoiren schreiben, gibt's darin mit Sicherheit ein Kapitel "AVISynth oder der Schrei nach Aspirin"! :D

Ansonsten denke ich mal, dass ich's jetzt verstanden habe. Dann kann's ja losgehen, danke.

Kika
1. March 2006, 23:31
Ach Mist, jetzt dachte ich, ich hät's verstanden - denkste!

Dieses Konstrukt hier:
last .logic(last.trim(1,0),"min").logic(last.trim(2,0),"min")
\ .logic(last.duplicateframe(0),"min")
\ .logic(last.trim(0,1)+last,"min")

Wie geht das? Ich dachte, Logic benötigt 2 Parameter, nicht nur einen?
Und müssten sich die einzelnen Logic-Befehle nicht auf die Ursprungsmaske beziehen statt auf die bereits bearbeitete? Verwirrung hoch drei ...

Und dann meine Testzeile:

edge=logic(edge1.trim(1,0),mode="and").logic(edge1.duplicateframe(0),mode="and")

Das funktioniert irgendwie auch, wobei mir aber nicht klar ist, warum auch hier der zweite Parameter (bzw. Clip) fehlen darf.

Es wird aber noch verrückter, denn ich habe versucht, einen zweiten Clip hinzuzufügen:

edge=logic(edge1.trim(1,0),edge1,mode="and").logic(edge1.duplicateframe(0),mode="and")

das funktioniert auch, aber das folgende funktioniert nicht (gibt 'ne Fehlermeldung von wegen unzulässiger Parameter für Logic):

edge=logic(edge1.trim(1,0),mode="and").logic(edge1.duplicateframe(0),edge1,mode="and")

Jetzt steh' ich da, ich armer Tor und bin so schlau wie einst zuvor ... :confused:


Nachtrag: OK, OK, Nachdenken hilft manchmal extrem.
Bei Dedées Variante funktioniert das, da das einleitende last.irgendwas() den ersten Clip und somit auch den verzweifelt gesuchten Parameter ersetzt. Soweit ist es jetzt klar.
Das erklärt aber nicht, warum meine Variante überhaupt funktioniert hat - woher die Fehlermeldung kommt, ist mir auch nicht klar. Und warum das:

edge.logic(edge.trim(1,0),mode="and").logic(edge.duplicateframe(0),mode="and")
\.mt_expand(mode="horizontal").fity2uv()

völlig andere Ergebnisse bringt als das:

edge.logic(edge.trim(1,0),mode="and").logic(edge.duplicateframe(0),mode="and")
edge=edge.mt_expand(mode="horizontal").fity2uv()

verstehe ich noch weniger.

Ich glaube, ich sollte jetzt schlafen gehen ... ;)

Didée
2. March 2006, 01:03
... weil Avisynth doch die "implicit last" Variable verwendet, wenn kein Argument ausdrücklich angegeben wird.

logic( clip1, clip2, "max" )

kann auch geschrieben werden als

clip1 .logic( clip2, "max" )

oder eben auch als

clip1
this = that # in "last" steht immer noch clip1
foo = bar # "last" ist immer noch clip1
x = y # und immer noch
logic(clip2,"max") # == logic(last,clip2,"max") / und "last" ist immer noch - was?

Dein letztes Beispiel hat nicht funktioniert, weil Du die Filter-Kette am Ende mit einem Argument überladen hast:

clip .logic(a,"mode") .logic(b,"mode") [...] .logic(x,y,"mode")

Die Kette fängt ganz vorne an: das erste Logic() hat "clip" und "a" als Clip-Argumente. Das zweite Logic() hat als Clip-Argumente: {Ergebnis des ersten Logic()} und "b". Undsoweiterundsofort ... jedes Logic nimmt das Ergebnis des Vorangegangenen als Erstes der zwei benötigten Clip-Argumente. Wenn dann, ganz am Ende, zwei Variablen in ein Logic() hineingeschrieben werden, dann würde daraus ja ein Befehl mit *drei* Clip-Argumenten.
(Dies alles natürlich nur bei der Punkt-Verknüpften Ein-Zeilen-Schreibweise.)

Hmh, war das jetzt verständich oder nicht? Weiss auch nicht ... (vielleicht mal JoeB fragen ;) )

Übrigens: Vorsicht mit der "AND" Verknüpfung innerhalb von Logic(). Das Ergebnis hiervon ist nur für binäre Masken (jedes Pixel ist entweder 0 oder 255) eindeutig. Bei "freien" Masken mit Graustufen ist das Ergebnis einer "AND"-Operation nicht eindeutig. Die Entsprechung des binären AND für Graustufen-Masken ist ... die "max" Verknüpfung (weswegen ich auch genau diese verwendet hatte).
^Das^ ist Quatsch, es ist natürlich andersrum: AND ~> "min", und OR ~> "max"
Generell ist die Anwendung von binären Masken für "Bildbearbeitungs"-Zwecke oftmals recht gefährlich, wegen dem nicht-vorhandenem Übergangsbereich zwischen "bearbeiteten" und "nicht-bearbeiteten" Bereichen. Ich verwende in 99% aller Fälle nur "weiche", also Graustufen-Masken.

Kika
2. March 2006, 01:19
Hmh, war das jetzt verständich oder nicht?

War es, da kann ich Dich beruhigen - jetzt hab' ich's fast kapiert (hoffe ich zumindest). Aber um das wirklich zu verinnerlichen, muss ich wohl noch viel experimentieren.
Eine Sache ist aber noch unklar:

Dein letztes Beispiel hat nicht funktioniert, weil Du die Filter-Kette am Ende mit einem Argument überladen hast

Im Klartext würde das bedeuten, dass die von Dir Eingangs Deines Postings erwähnte Regel in jedem Fall greift, obwohl die grundsätzliche Syntax sowas wie mein Beispiel doch eigentlich erlauben würde?

Übrigens: Vorsicht mit der "AND" Verknüpfung innerhalb von Logic().

Schon klar, aber bevor ich wirklich ernsthaft damit arbeite, möchte ich erstmal genau verstehen, wie die einzelnen Befehle exakt wirken. Und so ganz schlau wird man diesbezüglich aus der Doku zu den MaskTools nicht.
Denn dort heist es bei MaskedMerge ja, dass clip1 genommen wird, wenn die Maske an dieser Stelle den Wert 0 hat und clip2, wenn die Maske den Wert 255 hat. In jedem anderen Fall würde ein Blending zwischen clip1 und clip2 ausgeführt werden. Das würde aber doch bedeuten, dass andere als binäre Masken gar keinen Sinn machen würden. Ich zitiere mal:

So, if the mask is 255, the pixel will be the pixel from the overlay_clip, if the mask is 0, the pixel will be from the base_clip, and in between, it will be blended between both clips.

An diesem Punkt schwimme ich also noch ziemlich. Bedeutet das etwa, dass ein gewichtetes Blending in Abhängigkeit des Wertes der Maske benutzt wird, wenn die Maske einen anderen Wert als 0 oder 255 hat? Dann wäre ich bisher von falschen Voraussetzungen ausgegangen. Bei näherer Überlegung käme mir das allerdings logisch vor.

Das zweite Logic() hat als Clip-Argumente: {Ergebnis des ersten Logic()} und "b". Undsoweiterundsofort ...

Das war der Punkt, der mir an der Vorgehensweise nicht ganz klar war. Ziel ist doch gewesen, eine gemittelte Maske aus den Frames n-2 bis n+2 zu erzeugen. Müsste man in diesem Fall nicht n mit n+1, n mit n+2, n mit n-1 und n mit n-2 mitteln und aus den Einzelergebnissen eine neue Maske erzeugen?
So, wie ich das verstehe, mittelst Du aber n mit n+1, das Ergebnis daraus mit n+2, das Egebnis daraus mit n-1 und letztendlich dieses Ergebnis mit n-2.

Sorry, wenn ich Dich nerven sollte, aber das interessiert mich wirklich.

P.S.
Der letzte Punkt in meinem Nachtrag ist mir allerdings immer noch nicht klar. Zitat:

Und warum das:


Zitat:
edge.logic(edge.trim(1,0),mode="and").logic(edge.duplicateframe(0),mode="and")
\.mt_expand(mode="horizontal").fity2uv()

völlig andere Ergebnisse bringt als das:


Zitat:
edge.logic(edge.trim(1,0),mode="and").logic(edge.duplicateframe(0),mode="and")
edge=edge.mt_expand(mode="horizontal").fity2uv()

verstehe ich noch weniger.

Didée
2. March 2006, 03:39
Zur Beruhigung: nein, Du nervst überhaupt nicht. Problem ist nur, dass man (ich zumindest) solche Sachen am besten erklären kann, wenn man zusammen an einem Tisch sitzt, bewaffnet mit Stiften und viel Papier. Über Postings in Foren ist sowas meist sehr umständlich, und seeehr langwierig zu erklären. Lang lebe der Bleistift!

Bedeutet das etwa, dass ein gewichtetes Blending in Abhängigkeit des Wertes der Maske benutzt wird, wenn die Maske einen anderen Wert als 0 oder 255 hat? Dann wäre ich bisher von falschen Voraussetzungen ausgegangen. Bei näherer Überlegung käme mir das allerdings logisch vor.
Genau so isses. Vielleicht einfach mal umdenken von 0...255 zu 0%...100%. Wenn die Maske an einer Stelle die Helligkeit 64 hat, dann wird MaskedMerge an dieser Stelle ein Pixel
output = (input_1 * (255-64) + input_2 * 64) / 255
produzieren ... oder eben, prozentual gesehen: 75% input_1 plus 25% input_2.


Ziel ist doch gewesen, eine gemittelte Maske aus den Frames n-2 bis n+2 zu erzeugen. Müsste man in diesem Fall nicht n mit n+1, n mit n+2, n mit n-1 und n mit n-2 mitteln und aus den Einzelergebnissen eine neue Maske erzeugen?
So, wie ich das verstehe, mittelst Du aber n mit n+1, das Ergebnis daraus mit n+2, das Egebnis daraus mit n-1 und letztendlich dieses Ergebnis mit n-2.

Jjj..jein. Das eigentliche Ziel wäre: den temporalen Filter über so viele Frames (und nicht "weiter") anzuwenden, wie die Kanten-Maskierung statisch bleibt. Zu diesem Zweck ist meine ursprüngliche Methode aber auch nur ein Kompromiss, weil Bewegung in einem "entfernten" Frame den temporalen Filter auch in den "näheren" Frames deaktivieren wird. (Sollte aber trotzdem brauchbar arbeiten.) Deswegen ja auch mein alternativ-Vorschlag mit TTempSmooth, weil hier jeder Frame *einzeln* gewichtet werden kann, in Abhängigkeit von den zeitlichen Pixel-Ähnlichkeiten im Analyse-Clip.

Ein Problem ist ja, das der normale TemporalSoften-Trick (bearbeite nur Pixel, die sich sehr ähnlich sind) in diesem Fall nicht greift: wenn eine "harte" Kante da ist, sagen wir mal: ein Helligkeits-Übergang von 50 zu 150, und diese kante "zittert", dann wollen bzw. müssen wir ja Pixel miteinander verrechnen, die sehr unterschiedlich sind. Deswegen brauchen wir hier ja die ganze Maskierung: Über Pixel-Ähnlichkeiten kann nicht entschieden werden, ob Pixel miteinander verrechnet werden dürfen, oder nicht. Also muss diese Entscheidung über die Maskierung getroffen werden. Und deswegen darf die maske nicht einfach nur "zeitlich verschmiert" werden, weil dadurch auch nicht-zugehörige Pixel am Ende "ein bisschen" mit-verrechnet werden würden.

Gedanke weitergesponnen: das nächste Problem ist, dass durch einfache "Vermittlung" aller Werte einer "zitternden" Kante diese auch unscharf wird, wenn die räumliche Entfernung, über die sich das Zittern erstreckt, größer als 1 bis 1.5 Pixel ist.
Primitiv-Beispiel:

00 99 00
00 99 00
00 99 00 (Frame N)

99 00 00
99 00 00
99 00 00 (Frame N-1)

00 00 99
00 00 99
00 00 99 (Frame N+1)

Alle drei miteinander vermittelt:

33 33 33
33 33 33
33 33 33

... und aus der Kante ist Matsch geworden ...

Das ist der Grund, warum ich vorschlug, einen temporalen Medianfilter zu probieren: Der berechnet keinen arithmetischen Mittelwert, sondern nimmt denjenigen tatsächlich vorhandenen Wert, der am wenigsten vom Gesamt-Durchschnitt abweicht, flapsig ausgedrückt.
Man müsste es halt probieren - die Gefahr der Artefaktbildung ist bei Medianfiltern größer, dafür ist deren Ergebnis immer "scharf", und nicht "blurry".


Zum Nachtrag (den ich erst jetzt gesehen habe) :
Und warum das:


Zitat:
edge.logic(edge.trim(1,0),mode="and").logic(edge.duplicateframe(0),mode="and")
\.mt_expand(mode="horizontal").fity2uv()

völlig andere Ergebnisse bringt als das:


Zitat:
edge.logic(edge.trim(1,0),mode="and").logic(edge.duplicateframe(0),mode="and")
edge=edge.mt_expand(mode="horizontal").fity2uv()

verstehe ich noch weniger.
Und wiederrum wegen "last", bzw. wegen der Variablen-zuweisung.
Im ersten Beispiel machst Du

edge.logic.logic.expand

d.h. die expand-Operation wird auf das Ergebnis der Kette edge.logic.logic angewendet.

Im zweiten Beispiel nimmst Du edge, und machst etwas damit (nämlich: .logic.logic). Das Ergebnis weist Du aber keiner Variablen zu, d.h. das Ergebnis von edge.logic.logic landet in der Variablen "last". Die Variable "edge" selbst bleibt aber unverändert. Deswegen arbeitet die zweite Zeile mit "expand" auf dem unveränderten edge-Clip...

Damit beide das selbe Ergebnis liefern, müsstest Du

edge=edge.logic(edge.trim(1,0),mode="and").logic(edge.duplicateframe(0),mode="and")
edge=edge.mt_expand(mode="horizontal").fity2uv()

schreiben. Womit aber die ursprüngliche, unveränderte "edge"-Maske fortan nicht mehr verfügbar wäre, weil sie ja über edge=edge.tralala() verändert wurde. Was man üblicherweise umgeht, indem man neue Variablen einführt, z.B. edge2=edge.tralala() mit nachfolgendem edge2=edge2.expand() etc.

Es ist, wie es immer ist: es führen viele Wege nach Rom ... mein "Stil" in Avisynth-Scripten mag vielleicht nicht immer der sauberste und/oder lesbarste sein, zugegeben ... obwohl ich (meistens) darauf achte, die Sachen so lesbar und strukturiert wie möglich zu halten. Aber, da meine Scripte irgendwie immer große & komplexe Elefanten werden (wird mit der Zeit immer schlimmer, weiss auch nicht wie's kommt :D ), versuche ich Scripte immer so zu gestalten, dass die Avisynth-interne Prozesskette so wenig komplex wie möglich wird. Dazu gehört unter anderem auch, das Erstellen von "Hilfs-Clips" so weit wie möglich zu vermeiden.

So ist - in komplexeren Zusammenhängen - vielleicht

(es sei MachEtwas() ein 3-clip-filter, wie z.B. MaskedMerge)

source = whatever
Hilfsding1 = source.FilterABC()
Hilfsding2 = source.FilterXYZ()
Ergebnis = MachEtwas(source,Hilfsding1,Hilfsding2)

wahrscheinlich besser lesbar, und bestimmt leichter nachvollziehbar, als

whatever
Ergebnis = MachEtwas(FilterABC(),FilterXYZ())

aber die saubere 1. Lösung braucht halt mehr Ressourcen. Ist kein Problem bei "kleinen" Scripten, oft aber bei "großen".

Ooops, sorry für die Kleine Abschweifung. Vielleicht sollte ich mich jetzt auch mal Richtung Heia-Bett orientieren ...

*schnarch*

Kika
2. March 2006, 10:16
Genau so isses. Vielleicht einfach mal umdenken von 0...255 zu 0%...100%.

Wie ich selbst schon schrieb, kommt mir das so auch logischer vor.
Ich schätze, der Teil der Doku über die binären Masken hat mich einfach auf die falsche Spur gebracht.

Der Teil mit den Filtern ist jetzt auch klar (Median & Co).

Und wiederrum wegen "last", bzw. wegen der Variablen-zuweisung.

OK, verstanden. Wenn ich also wegen der Syntax mal unsicher bin, füge ich besser zusätzliche Hilfs-Variablen ein. Wenn dann alles so klappt, wie es soll, kann man das Script ja immer noch optimieren.
Frei nach dem Motto: Mal sollte den Stil des Meisters erst dann kopieren, wenn man die Technik des Meisters verstanden hat. ;)

Denn insgesamt habe ich jetzt mehr Zeit damit zugebracht, die Syntax zu verstehen als die eigentlichen Funktionen, um die es ging.
Aber um auf das Beispiel aus dem Nachtrag zurück zu kommen: Beispiel 1 war also korrekt, Beispiel 2 verkehrt - was witzig ist, weil das erste Beispiel auch zuerst entstand. Manchmal sollte man sich selbst mehr vertrauen. ;)

Zu Deiner Bemerkung über Deinen Stil, Scripte zu schreiben: Nun, für Leute wie mich, die mit Programmierung wenig bis nichts zu tun haben, ist es in der Tat schwieriger nachzuvollziehen. Aber wenn die Syntax einer Programmiersprache sowas ausdrücklich erlaubt, würde ich das nicht als unsauber bezeichnen.
Ich schätze mal, dass andere, die sich mit der Syntax besser auskennen, keine Probleme damit haben.

Ich werde mir diesen Thread hier jedenfalls ausdrucken und an die Wand nageln. Da steht nämlich jetzt nicht nur viel wissenswertes über die MaskTools drin, sondern auch viel über die Syntax von AVISynth. Prädikat: wertvoll! :)

Gruß,
Kika

Kika
6. March 2006, 11:08
Um das hier mal abzuschließen:

Es klappt jetzt ziemlich gut. Die Syntax hab' ich auch verstanden, insgesamt hat mit diese Aktion also ziemlich viel gebracht. Ich kann sogar jetzt eigene Monster entwickeln. ;) Das ist aber ein mühsames Geschäft.

Also nochmal danke für die Hilfe.

scharfis_brain
6. March 2006, 12:52
kannst Du das Script und vorher-nachher BIlder posten?

Kika
6. March 2006, 14:26
Bald, ich benutze Variationen der beiden Scripte von Didée, beide haben vor und Nachteile, je nachdem, wie der Source aussieht.

Das erste stabilisiert die Kanten, kann aber bei viel Bewegung zum Nachziehen der Kanten führen.

Das zweite ist sehr langsam, stabilisiert nicht ganz so gut, verschmiert die Kanten, zieht aber nicht nach.

Nun wollte ich beides kombinieren, dazu brauche ich aber die Bewegungserkennung (siehe anderer Thread). Außerdem möchte ich es noch mit ein paar anderen Denoisern versuchen.

Kika
11. March 2006, 20:15
So, die angehängte RAR-Datei enthält ein Vorher- und ein Nachher-Bild sowie das Script, das ich benutzt habe. Es ist eine einfachere Variante des Scripts von Didée, die aber mit meinem Source am besten funktionierte. Es ging ja nur um das Beseitigen eines geringen horizontalen Jitters.
Einfach mal auf die senkrecht herunterhängenden Seile achten, dann sieht man sofort, um was es geht und was das Script bewirkt.

Kika
14. March 2006, 23:34
Hm, mach' ich halt den Alleinunterhalter, aber eventuell interessiert's ja wen. ;)

Ein Anwendungsgebiet für binäre Masken habe ich inzwischen gefunden. Und wieder sind es Kanten, um die es geht - diesmal aber die von Animes.
Meine Nichte hat mir 8 Jahre alte Drittgenerationen-VHS-Tapes zugeschickt, mit der Bitte, die auf DVD zu überspielen, da der DVD-Rekorder ihrer Eltern damit nicht klarkommt (macht völligen Matsch draus).
Aber auch mein S-VHS-Rekorder hat damit arge Probleme. Sauber Deinterlacen ist nicht mehr drin, daher muss LeakKernelDeint genügen. Das deshalb, weil der Rekorder immer wieder mal die Phase verliert.

Das hier ist das erste Testscript, das ich dazu entworfen habe:

segmentedavisource("d:\capture.avi",true,"RGB24").assumetff()
converttoyv12().leakkerneldeint(order=1, sharp=true, twoway=true, threshold=4).assumeframebased()
work=last

#gib' mir saubere Kanten vom Luma bitte

clean=work.temporalsoften(2,32,32,40,2)
fity2uv(clean).greyscale().blur(1,0.5).levels(32,1.0,235,0,255)
maske=last.dEdgeMask(0,255,0,255,"8 0 -8 16 0 -16 8 0 -8",divisor=4)
\.binarize(90,upper=false).fity2uv()

# Nach dieser Aktion stehen die Kanten wie 'ne 1
maskedmerge(work,clean,maske)

# Zeit für die Filterei
Convolution3d (preset="animeHQ")

bicubicresize(352,560,0,0.6).undot() #560 ist schon richtig, ist gecroppt.
addborders(0,8,0,8)

Klappt verblüffend gut. Die Werte sind aber zum Teil extrem, der Source ist halt übel. Aber andere Filtermethoden haben mir die Kanten bei Scrolling jedes Mal völlig verschmiert, dito kleine Details.
Das Script ist noch nicht fertig, ich arbeite gerade noch einem speziellen Filter. Der Witz ist aber, dass die Schärfe praktisch nicht leidet, das Bild aber viel stabiler wird. TMPGEnc kommt damit prima klar. ;)
Das Ergebnis sieht auf dem TV jetzt schon besser aus als das Original. :)

Mit "normalen", also nicht binären Masken, klappt das in diesem Fall nicht so gut, weil ich ja möchte, dass nur die schwarzen Kanten bearbeitet werden. Wahrscheinlich setze ich hinten noch ein MaskedMerge dran, diesmal mit einem Median-Filter, um das Rauschen zu eliminieren. Das sollte dann mit der invertierten Maske eigentlich ganz gut klappen.
Besser wäre allerdings eine invertierte Maske der anderen Art, um die Kanten noch besser zu stabilisieren. Nämlich eine, die die ursprüngliche Kante nimmt, sie invertiert, aber gleichzeitig darum herum eine Kante der Pixelweite X (enventuell auch Y) erzeugt. Also quasi eine hohle Kante. Aber wie ich die erzeugen kann ... keine Ahnung. Nachtrag ... doch, doch, das geht. :)

BTW: MaskedMerge kann man ja auch durch Overlay ersetzen ... ?!

Didée
15. March 2006, 13:25
Och, die Alleinunterhalter-Threads sind oftmals die besten ... :D

Zu den genannten Problemen & der Maskiererei hätte ich schon noch was. Muss aber noch warten bis später, hab gerade nicht genug Zeit dafür.

Könntest Du das mit der "hohlen Kante" derweil etwas genauer erklären? (Bild?) Aus Deiner Beschreibung geht nämlich, für mich, nur eine Maskierung der Line selbst, "plus etwas breiter", hervor. Unter "hohl" würde ich eine Maskierung der Umgebung der Linie, aber ohne die Linie selbst, verstehen.

Zum letzten Script:
Convolution3D(...)
LeakKernelDeint(...)
war ja wohl ein Versehen, oder? Du willst doch bestimmt keinen *spatio*-temporalen Filter *vor* dem Deinterlacer anwenden ... ?

Dann mag ich noch besonders
FitY2UV().Greyscale()
weil Du damit erst Y auf U & V 'rüberkopierst, und anschließend U & V löschst. :)

Kika
15. March 2006, 14:48
war ja wohl ein Versehen, oder?
Ja, schon korrigiert, man sollte halt nicht nach Mitternacht noch mit 'nem Texteditor und langen Scriptzeilen hantieren. War ja sogar noch der Punkt hinter converttoyv12() drin... ;)

weil Du damit erst Y auf U & V 'rüberkopierst, und anschließend U & V löschst.
Jaja, ich weiß. Sieht so aber irgendwie besser aus, wenn man sich zwischendurch die Wirkung der Filter auf die Maske ansieht. Es irritiert mich immer, wenn die Masken so bunt sind. :)
Und ich wollte jede erdenkliche Fehlerquelle ausschließen. So weiß ich, dass ich wirklich nur Luma habe und sonst nichts.

Unter "hohl" würde ich eine Maskierung der Umgebung der Linie, aber ohne die Linie selbst, verstehen.
Genau das meinte ich auch. Eine Idee dazu, wie ich das machen könnte, kam mir heute in der Mittagspause. Probieren kann ich das erst zu Hause.

Kika
8. April 2006, 00:23
Nach langer Zeit mal wieder ein Update: Das mit der "hohlen Kante" habe ich nicht hinbekommen, bzw. nicht so, wie ich es wollte.

Damit nochmal klar wird, was ich vorhatte: Bei Trickfilmen hängt der subjektive Eindruck von Schärfe sehr sehr stark von der Qualität der Umrandungslinien ab. Hat man die klar definiert und ohne Treppchen, hat man eingentlich schon gewonnen, denn dann kann man das Rauschen aus den Farbflächen entfernen, ohne subjektiv empfundene Details zu verlieren. Soweit die Theorie, die Praxis... tja.

Ansonsten funktioniert das Script für Homevideos ja gut genug, um es einsetzen zu können, was ich inzwischen bei mehreren Clips von S-VHS auch gemacht habe. Allerdings gibt's eine Einschränkung: Die Videos müssen knackig scharf sein und nur unter einem leichten Jitter leiden, für Videos schlechter Qualität (also z.B. nur VHS) ist das nicht geeignet. Da muss man brutaler vorgehen. Für meine Zwecke reicht's aber völlig aus: Es tut, was es sollte.
Bleibt halt das Trickfilmproblem. Und da stellt sich eben die Frage, nach einer Methode, wirklich nur die Kanten zu isolieren, nichts anderes.
Mein bisheriges Zwischenergebnis liegt darin, binäre Masken zu verwenden, die geben klare Kanten, allerdings mit Aliasing. Alle Versuche, das "weicher" hinzubekommen, führten zum Verschmieren bei Bewegungen.

krieger2005
3. September 2006, 20:28
So wie ich das hier gesehen habe ist der Vorschlag von Didée von der ersten Seite noch nicht 100% umgesetzt worden. Daher habe ich mich mal rangemacht und hier ist das Ergebnis:
frames=10
source=last

blur(0,1.58)
DEdgeMask(0,255,0,255,"8 0 -8 16 0 -16 8 0 -8",divisor=4)
mt_expand(mode="horizontal")

Dim("bc", frames, last)
Dim("fc", frames , last)
global clip=last
global inter="last"
global tr="last"
global dtmp="last"
global dr=""
for2("j=1", "j<=" + string(frames), "j=j+1",
\"""
bc.set(j, Comp(clip,true, j,1) )
fc.set(j, Comp(clip,false,j,1) )
global inter = "bc" + string(j) + "," + inter + "," + "fc" + string(j)
global tr = tr + ".logic(last.trim(" +string(j)+ ",0),str)"
global dtmp=dtmp+".duplicateFrame(0)"
global dr=dr+".logic(" +dtmp+ ",str)"
""")

eval( "interleave(" + inter + ")" )
str="min" eval( tr+dr )
#mt_expand(mode="horizontal").fity2uv()
Blur(1,0).fity2uv()
#fity2uv()
edge=selectevery(frames*2+1,frames)

Dim("bd", frames, last)
Dim("fd", frames , last)
global inter="source"
global clip=source
for2("k=1", "k<=" + string(frames), "k=k+1",
\"""
bd.set(k, Comp(clip,true, k,2) )
fd.set(k, Comp(clip,false,k,2) )
global inter = "bd" + string(k) + "," + inter + "," + "fd" + string(k)
""")

eval( "interleave(" + inter + ")" )
temporalsoften(frames,255,255,10,2)
#medianblurT(0,0,0,frames, mc=false)
#FluxSmoothT(255).medianblurT(1,1,1,frames, mc=false)
selectevery(frames*2+1,frames)
ec=last

MaskedMerge(source,ec,edge,U=3,V=3)

Hier wird das Array-Framework von hier (http://forum.doom9.org/showthread.php?t=106880) benutzt. "frames" bestimmt die Anzahl an Frames, über welche die Kanten versucht werden zu stabilisieren.