Skip to content

Programmatischer Ansatz für die HDR-Fotografie

Denken Sie daran, dass ein Problem in der Informatik fast immer mehrere Lösungen haben kann, daher zeigen wir Ihnen die optimalste und beste.

Lösung:

Bearbeiten.

Ich habe ein Bild erzeugt, das "sauberer" aussieht als mein ursprünglicher Versuch, und die Bearbeitung ist auch schneller.

Wie zuvor beginnen wir damit, die Bilder in der Reihenfolge vom dunkelsten zum hellsten zu laden und die Artefakte der Ausrichtung wegzuschneiden.

files = [email protected]["memorial*.png"];
images = ImagePad[Import[#], {{-2, -12}, {-35, -30}}] & /@ files;

Aufbau des HDR-Bildes:

Wie bei Thies wird auch hier der Mittelwert über mehrere Bilder gebildet, um Pixelwerte zu erhalten. Die Intensitätsdaten werden aus den Bildern extrahiert, und niedrige oder hohe Werte werden auf Null gesetzt, um potenziell verrauschte oder gesättigte Pixel zu kennzeichnen. Das Belichtungsverhältnis zwischen zwei Bildern wird geschätzt, indem nur diejenigen Pixel berücksichtigt werden, die in beiden Bildern ungleich Null sind. Nach dem Ausgleich der Belichtungsunterschiede werden die Pixelwerte aller Bilder zu einem mittleren Bild kombiniert, wobei wiederum nur die Pixel verwendet werden, die nicht Null sind. Schließlich wird das mittlere Bild in HSB-Komponenten aufgeteilt.

data = ImageData[[email protected][#, "Intensity"]] & /@ images;
data = Map[Clip[#, {0.1, 0.97}, {0, 0}] &, data, {3}];

exposureratios = Module[{x, A, g}, 
  [email protected][Cases[Flatten[#, {{2, 3}, {1}}], {Except[0], Except[0]}, 1],
  x, x] & /@ Partition[data, 2, 1]];
exposurecompensation = 1/FoldList[Times, 1, exposureratios];

data = MapThread[Times, {exposurecompensation, Unitize[data] (ImageData /@ images)}];
data = Transpose[data, {3, 1, 2, 4}];
meanimage = Map[Mean[Cases[#, Except[{0., 0., 0.}]]] &, data, {2}];

{h, s, b} = ColorSeparate[ColorConvert[[email protected][meanimage], "RGB"], "HSB"];

Tone Mapping:

Wir haben jetzt einen Helligkeitskanal, der einen Wertebereich von 0 bis 1 enthält, aber mit einer sehr ungleichmäßigen Verteilung.

ImageHistogram[b]

Geben Sie hier Bildbeschreibung ein

Zunächst führe ich eine Histogramm-Entzerrung für die Helligkeitsdaten durch:

cdf = [email protected]@BinCounts[[email protected]@b, {0, 1, 0.00025}];
cdffunc = ListInterpolation[cdf, {{0, 1}}];
histeq = Map[cdffunc, ImageData[b], {2}];
ImageHistogram[[email protected]]

Geben Sie hier Bildbeschreibung ein

Als Nächstes wende ich eine Art doppelseitige Gamma-Anpassung an, um die Anzahl der sehr niedrigen und sehr hohen Werte zu reduzieren (wir wollen nicht zu viele tiefe Schatten oder helle Lichter).

b2 = Image[1 - (1 - (histeq^0.25))^0.5];
ImageHistogram[b2]

Geben Sie hier Bildbeschreibung ein

Endgültiges Bild:

Schließlich wende ich eine integrierte Sharpen Filter auf den neuen Helligkeitskanal an, um den lokalen Kontrast etwas zu verstärken, und wende eine Gamma-Anpassung auf den Sättigungskanal an, um ihn etwas bunter zu machen. Die HSB-Kanäle werden dann wieder zum endgültigen Farbbild zusammengefügt.

ColorCombine[{h, ImageAdjust[s, {0, 0, 0.75}], Sharpen[b2]}, "HSB"]

Geben Sie hier Bildbeschreibung ein

Ursprüngliche Version

Hier ein Versuch mit dem Bild der Stanford Memorial Church, bei dem ein lokaler Kontrastfilter für das Tone Mapping verwendet wird.

Laden Sie zunächst die Bilder und schneiden Sie sie zu, um die Artefakte an den Rändern einiger Bilder zu entfernen:

files = Reverse @ FileNames["memorial*.png"];
images = ImagePad[Import[#], -40] & /@ files;

Erstellen Sie dann kleine Graustufenversionen und verwenden Sie diese, um die Helligkeitsskalierung zwischen den Bildern zu schätzen

small = ImageData[ImageResize[ColorConvert[#, "Grayscale"], 50]] & /@ images;
imageratios = FoldList[Times, 1, 
  Table[a /. [email protected]
    FindMinimum[Total[(small[[i]] - a small[[i + 1]])^2, -1], {a, 1}], 
  {i, [email protected] - 1}]]

Wählen Sie nun das "beste" Bild aus, von dem Sie jeden Pixelwert nehmen, und skalieren Sie diesen Wert entsprechend. Ich habe das "beste" Bild für ein bestimmtes Pixel als dasjenige definiert, bei dem der Median der {R,G,B} Zahlen am nächsten bei 0,5 liegt.

data = Transpose[ImageData /@ images, {3, 1, 2, 4}];
bestimage = Map[Module[{best},
 best = Ordering[(Median /@ # - 0.5)^2, 1][[1]];
 #[[best]]*imageratios[[best]]] &, data, {2}];

Als Nächstes wird eine lokale Kontrastverbesserung auf den Helligkeitskanal des Bildes angewendet. Dies ist recht einfach und langsam. Für jedes Pixel sortiert der Filter die eindeutigen Werte in der Umgebung des Pixels und findet die Position des Pixels in dieser Liste. Der Pixelwert wird auf den Bruchteil der Listenposition gesetzt. Wenn zum Beispiel ein Pixel das hellste in seiner Umgebung ist, erhält es den Wert 1. Die size Wert in der Liste localcontrast Funktion muss mit dem Bereichsparameter in der ImageFilter.

localcontrast = With[{size = 20}, Compile[{{x, _Real, 2}}, 
Block[{a, b, val}, val = x[[size + 1, size + 1]];
a = Union[Flatten[x]];
b = Position[a, val][[1, 1]];
b/Length[a]]]];

{h, s, b} = ColorSeparate[ColorConvert[Image[bestimage], "RGB"], "HSB"];     
newb = ImageFilter[localcontrast, b, 20];

Kombinieren Sie schließlich den kontrastverstärkten Helligkeitskanal mit der ursprünglichen Sättigung und dem Farbton, um das endgültige Bild zu erhalten:

ColorCombine[{h, s, newb}, "HSB"]

Geben Sie hier Bildbeschreibung ein

Es ist nicht brillant, aber ich denke, der allgemeine HDRI-Effekt ist vorhanden. Die Kontrastverstärkung könnte wahrscheinlich etwas abgeschwächt werden, indem man den size Parameter erhöht wird, obwohl das langsamer ist.

Für den Anfang habe ich einen einfachen, intuitiven Ansatz ausprobiert, nämlich die Kombination der besten Teile aus jedem Bild für die verschiedenen Belichtungszeiten alle in einem HDR-Bild angepasst.

Beginnen wir mit dem Importieren aller Bilder

imageurls = "http://upload.wikimedia.org/wikipedia/commons/thumb/" <> # & /@
  {"0/09/StLouisArchMultExpEV-4.72.JPG/320px-StLouisArchMultExpEV-4.72.JPG",
   "c/c3/StLouisArchMultExpEV-1.82.JPG/320px-StLouisArchMultExpEV-1.82.JPG", 
   "8/89/StLouisArchMultExpEV%2B1.51.JPG/320px-StLouisArchMultExpEV-1.51.JPG",
   "8/8f/StLouisArchMultExpEV%2B4.09.JPG/320px-StLouisArchMultExpEV-4.09.JPG"};

FPImage = Image[#, "Real"]& (* change image rep. to floating point *)
inputimages = SortBy[
    Composition[FPImage, Import] /@ imageurls,
    (* sort images by increasing exposure *)
    Composition[Mean, Flatten, ImageData, First, ColorSeparate[#, "Intensity"] &]
  ]

Eingabebilder

wobei wir die interne Darstellung auf Fließkomma geändert haben, um spätere Rundungsfehler zu vermeiden, und bereits aufsteigend nach Belichtungszeiten sortiert haben.

Als Nächstes vergleichen wir benachbarte Bildpaare und versuchen, deren Belichtungsverhältnisse zu erraten.
(Wahrscheinlich gibt es eine robustere und effektivere Methode, aber für den ersten Versuch scheint sie zu funktionieren und einigermaßen genaue Werte zu liefern).

ScaledImageSquaredError[img1_Image, img2_Image, s_?NumericQ] :=
  Composition[Total, Flatten, ImageData, ImageApply[#^2 &, #] &,
              First, ColorSeparate[#, "Intensity"] &][
      ImageDifference[img2, [email protected][s # &, img1]]
    ];

(* Guess exposure ratio. img2 must be the brighter than img1 *)
GuessExposureRatio[img1_Image, img2_Image] := NArgMin[
  {ScaledImageSquaredError[img1, img2, [Alpha]],
   1 < [Alpha] < 10},
   [Alpha], Method -> {"NelderMead", "RandomSeed" -> 24}
]

exposureratios = GuessExposureRatio @@@ Partition[inputimages, 2, 1]
(* {4.66667, 5.89289, 3.74074} *)

(* brightness scaling factors to get all images to the same exposure level *)
unifyingexposures = #/Max[#] &@[email protected][Times, 1, [email protected]]
(* {1., 0.214286, 0.0363634, 0.00972091} *)

Als Nächstes wollen wir für jedes Bild Masken erstellen, die den verwertbaren Teil jedes Bildes markieren. Unser Kriterium ist die Helligkeit der einzelnen Pixel. Wir wollen nicht den Bereich mit geringer Helligkeit, weil das Signal-Rausch-Verhältnis möglicherweise schlecht ist, und wir wollen nicht die Helligkeiten nahe 1,0, weil die Werte möglicherweise abgeschnitten werden.

Daher definieren wir eine (irgendwie willkürliche und einstellbare) Helligkeitsgewichtungskurve

brightnessweights = Function[b, 
    Piecewise[
      {{Interpolation[{{{0}, 0, 0}, {{0.2}, 1, 0}, {{0.95}, 1, 0}, {{1}, 0, 0}},
                      InterpolationOrder -> 3][b],
        0 <= b <= 1}}, 0
    ]
  ]
Plot[brightnessweights[b], {b, 0, 1}, PlotRange -> All, ImageSize -> Small]

Helligkeitsgewichtskurve

und sehen, wie sich die verschiedenen Belichtungsbereiche überlappen und kombinieren

LogLinearPlot[
  Evaluate[brightnessweights[b/#] & /@ unifyingexposures],
  {b, 0.0002, 1},
  PlotRange -> All, Filling -> Axis
]

Kombinierte Helligkeitsregionen

Wir haben eine gute Überlappung, aber nicht zu viel, und keine Lücken in den Helligkeitsstufen, also sieht das gut aus.

Als Nächstes konstruieren wir die Gewichtskarten, indem wir die Helligkeitskurve auf die Helligkeiten jedes Bildpixels anwenden und dann durch die Summe aller Gewichtskarten an jedem Pixel normalisieren.

GenerateWeightMask = Composition[
    ImageApply[brightnessweights, #] &, First, ColorSeparate[#, "Intensity"] &
  ]
ImageTotal = Fold[ImageAdd, [email protected]#, [email protected]#] &

nrimages = Length[inputimages];
brightnessmasks = GenerateWeightMask /@ inputimages
scaledmasks = ImageMultiply[#, 1/nrimages] & /@ brightnessmasks;
masksum = ImageTotal[scaledmasks];
invmasksum = ImageApply[1/(nrimages #) &, masksum];
weightmasks = ImageMultiply[#, invmasksum] & /@ brightnessmasks

Helligkeitsmäger

Gewichtmasken

Jetzt multiplizieren wir unsere Eingangsbilder mit den Gewichtungsmasken und passen sie an die verschiedenen Belichtungsstufen an, um sie dann zu einem endgültigen HDR-Kompositbild zu kombinieren.

sameexposureimages = Function[{s, i}, ImageApply[s # &, i]] @@@
    Transpose[{unifyingexposures, inputimages}]
hdrimage = ImageTotal[
    ImageMultiply @@@ Transpose[{weightmasks, sameexposureimages}]
  ]

Hdrimage

das wir nun mit verschiedenen künstlichen Belichtungen betrachten können

Manipulate[ImageMultiply[hdrimage, [Alpha]], {{[Alpha], 1}, 1, 100}]

HDR -Bild mit benutzerdefinierter Belichtung

Außerdem können wir jetzt damit beginnen, den Dynamikbereich wieder zu komprimieren, indem wir Tone-Mapping-Algorithmen und Ähnliches anwenden.

aktualisieren.
Ein Beispiel für einen einfachen globalen Tonemapping-Algorithmus kann durch Anwendung von #/(#+C) & zu jeder Pixelintensität, die wie folgt aussieht:

Manipulate[
  ImageApply[#/(# + [Alpha]) &, hdrimage], {{[Alpha], 0.2}, 0.001, 0.2}
]

tonemapped HDR -Bild

Version 9 Antwort - integrierte Funktionalität verwenden

Die Symbole Image`CreateHDRI und Image`ToneMapHDRI waren in Version 8 vorhanden, schienen aber nichts zu bewirken. In Version 9 gibt es funktionierenden Code hinter ihnen. Dies ist alles undokumentiert und kann sich daher noch ändern, bevor es offiziell veröffentlicht wird, aber hier ist, was ich herausgefunden habe.

Image`CreateHDRI

Diese Funktion nimmt eine Liste von Bildern mit unterschiedlichen Belichtungen und setzt sie zu einem einzigen Bild mit hohem Dynamikbereich zusammen. Die grundlegende Verwendung ist einfach

Image`CreateHDRI[{image1, image2, ...}, options]

Die folgenden Standardoptionen sind definiert:

Options[Image`CreateHDRI]
(* {"ExposureTimes" -> {}, "EV" -> {},
    "GenerateCameraResponseFunction" -> True, "AlignTranslation" -> False} *)

"ExposureTimes" und "EV" ermöglichen es Ihnen, eine Liste mit bekannten Belichtungszeiten oder Belichtungswerten anzugeben. Wenn beide Optionen auf der leeren Standardeinstellung belassen werden, werden die Belichtungszeiten aus den Bildern berechnet.

"AlignTranslation" kann eingestellt werden auf True gesetzt werden, um die Bilder vor der Kombination auszurichten. Wie der Name schon sagt, bezieht sich die Ausrichtung nur auf die Translation, Skalierungs- und Rotationsfehler werden nicht berücksichtigt.

"GenerateCameraResponseFunction" erzeugt vermutlich eine Kamerareaktionsfunktion 🙂 Diese nimmt Unteroptionen an, die standardmäßig auf
{"Variance" -> 16., "PredefinedResponse" -> "Linear"}
.
Weitere Einstellungen für die vordefinierte Reaktionskurve sind "Log10" und "Gamma".
Ich bin mir nicht sicher, was die "Variance" Einstellung bewirkt, oder was passiert, wenn "GenerateCameraResponseFunction" auf False gesetzt ist.

Image`ToneMapHDRI

Diese Funktion nimmt ein einzelnes HDR-Bild und wendet einen der verschiedenen Tonemapping-Algorithmen an, die in der Option "Methode" angegeben sind. Die Voreinstellung ist:

Options[Image`ToneMapHDRI]
(* {Method -> "PhotographicToneReproduction"} *)

Die verfügbaren Algorithmen sind:

algos = Image`HDRImageProcessingDump`$ToneMapAlgos
(* {"AdaptiveLog", "Photoreceptor", "FattalGradientDomain", 
     "PhotographicToneReproduction", "ContrastDomain"} *)

Jeder Algorithmus hat eine Reihe von Unteroptionen, die Standardeinstellungen sind:

{#, Image`HDRImageProcessingDump`defaultsuboptions[#]}& /@ algos

(* {{"AdaptiveLog", {"Bias" -> 0.8, "Gamma" -> 1., "Saturation" -> 1.}},
 {"Photoreceptor", {"LuminousIntensity" -> 0., "ChromaticAdaptation" -> 0., "LightAdaptation" -> 1., "OverallContrast" -> 0.}},
 {"FattalGradientDomain", {"Threshold" -> 0.1, "Order" -> 0.8, "ParamX" -> Automatic, "Saturation" -> 0.8}},
 {"PhotographicToneReproduction", {"Key" -> 0.18, "Sharpening" -> 8., "UseLocalOperator" -> False, "NumberOfScales" -> 8, "SmallestScale" -> 1, "LargestScale" -> 43, "Saturation" -> 0.8}},
 {"ContrastDomain", {"ContrastScaleFactor" -> 0.3, "UseContrastEqualization" -> True, "Saturation" -> 0.8}}} *)

Da ich kein Experte für HDR-Tone-Mapping-Algorithmen bin, kann ich nicht wirklich kommentieren, was diese Parameter steuern, aber es ist einfach genug, mit den Werten herumzuspielen und die Ergebnisse zu sehen.

Beispiel

Hier ist ein Beispiel für die Verarbeitung der Bilder der Stanford Memorial Church mit der neuen Funktion:

(* load the source images *)
files = [email protected]["memorial*.png"];
images = ImagePad[Import[#], {{-2, -12}, {-35, -30}}] & /@ files;

(* create the HDR image *)
i = Image`CreateHDRI[images];

(* apply a tone-mapping algorithm *)
Image`ToneMapHDRI[i, Method -> {"FattalGradientDomain", {"Threshold" -> 0.3}}]

Geben Sie hier Bildbeschreibung ein

Denken Sie daran, dass Sie sich für die Option entscheiden können, Ihre Erfahrung zu erläutern, wenn sie hilfreich war.



Nutzen Sie unsere Suchmaschine

Suche
Generic filters

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.