-
71. Data: 2009-03-12 13:58:13
Temat: Re: hdr a Jpg
Od: Mateusz Ludwin <n...@s...org>
Stefan Nawrocki wrote:
> Ten fragment nie byl o HDR, ale o tym, że niektórzy sądzą, że zmiana liczby
> bitów (jpg - 8, RAW - 12) zmienia w istotny sposób zakres tonalny. Tak nie
> jest.
Zmienia - te dodatkowe bity w RAW idą w zakres a nie rozdzielczość. JPG z
aparatu ma zwykle 8bitów wyciętych ze środka 12bitowego rawa, a nie obciętych
przez odrzucenie najmniej znaczących bitów. Dopiero jeśli użyje się różnych fill
light i recovery, to mamy do czynienia z faktyczną kompresją.
>>> I druga sprawa - tak sklejona skala szarości podzielona przez odpowiedni
>>> współczynnik da się zobrazować na monitorze 8-bitowym, a straty będą
>>> polegały na utracie liczby dostępnych poziomów.
>>> Tak wygląda most na tym etapie:
>>> http://www.3n.com.pl/Nikon/most_1.jpg
>> Nieprawda, to nie jest obraz powstały przez odrzucenie nadmiarowych
>> bitów HDRI. Te cienie są powyciągane przez tonemapping.
>
> No cóż - nie lubię jak zarzuca mi się kłamstwo, więc musze się bronić :-).
> Wiesz - ja od 20 lat _tworzę_ oprogramowanie graficzne (www.3n.com.pl) i
> wiem jakim algorytmem przetwarzam swoje obrazy. Zwłaszcza, kiedy robię to za
> pomocą programów swojego autorstwa :-).
Uściślę - obrazek HDR zawiera dane liniowe, w żaden sposób nieprzetworzone.
Gdybyś po prostu obciął mniej znaczące bity z obrazka HDR, to dostałbyś wręcz
czarny most, bo był on na pewno zdrowo ponad 8EV ciemniejszy od nieba. Skoro
most jest wyraźnie widoczny, to te dane nie są liniowe, więc musiał tu zostać
zastosowany jakiś tonemapping inny niż kompresja liniowa.
Mapowanie tonów to każde odwzorowanie z przestrzeni tonalnej w przestrzeń
tonalną. Np. z 12-bitowej przestrzeni RGB w rawie do 8-bitowej przestrzeni RGB w
JPG (to co robi wywoływaczka po demozaikowaniu i uwzględnieniu parametrów
ustawionych "suwakami"), ale też z 8-bitowej przestrzeni RGB do 8-bitowej
przestrzeni RGB (korekcja krzywymi w dowolnym programie graficznym).
W przypadku HDRI robi się odwzorowanie z przestrzeni zmiennoprzecinkowej w
8-bitową RGB.
Tonemapper to po prostu funkcja przyjmująca w argumencie piksel z przestrzeni A
i zwracająca piksel z przestrzeni B (w przypadku operatorów jednorodnych), albo
przyjmująca obraz z przestrzeni A i zwracająca obraz z przestrzeni B (w
przypadku operatorów lokalnych).
> Jeśli masz kompilator C - to sam zobacz jak to działa :-).
> Strzałką zaznaczyłem współczynnik "jasności". Dla wartości 0.9 - wynik jest
> taki:
> http://www.3n.com.pl/Nikon/wynik_09.jpg
> Jak widzisz (jeśli znasz język C, jeśli nie - to wierz mi na słowo) - w
> algorytmie nie ma żadnego mapowania tonów, tylko opercje na poziomach (sumy,
> średnie, itp.).
OK, a możesz jakoś opisać to przekształcenie matematycznie, bo ciężko coś
wydobyć z tak "ciężkiego" kodu?
--
Mateusz Ludwin mateuszl [at] gmail [dot] com
-
72. Data: 2009-03-12 14:04:17
Temat: Re: hdr a Jpg
Od: Mateusz Ludwin <n...@s...org>
Jakub Jewuła wrote:
> Mateuszu, czekamy na blyskotliwa ropiste ;))))))
Jadziem:
public class GradientCompressionTonemapper implements IToneMapper {
private static final Logger log =
Logger.getLogger(GradientCompressionTonemapper.class
.toString());
private int levels = 3;
private double alpha = 0.1;
private double beta = 0.5;
private int iterations = 500;
private double omega = 1.3;
BufferedImage lastToneMap;
private double s = 0.3;
public BufferedImage generateToneMap(HDRImage sourceImage) {
double[][][] rawData = sourceImage.getRawData();
double[][] luminanceMap = JHDRIUtils.getLogLuminanceMap(sourceImage);
double[][] attenuation = getAttenuation(luminanceMap, 1, getLevels(),
alpha, beta);
double[][][] gradient = getGradient(luminanceMap, attenuation);
double[][] div = getDiv(gradient);
double[][] image = getImage(div);
int width = image.length;
int height = image[0].length;
lastToneMap = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
JHDRIUtils.normalize(image);
double maxLum = JHDRIUtils.getMaxChannelIntensity(sourceImage);
double minLum = JHDRIUtils.getMinChannelIntensity(sourceImage);
luminanceMap = JHDRIUtils.getLuminanceMap(sourceImage);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
// double ch = (int) (255 * ((image[x][y] - min) / (max -
min)));
int r = getColour(rawData[x][y][0], luminanceMap[x][y],
image[x][y], s);
int g = getColour(rawData[x][y][1], luminanceMap[x][y],
image[x][y], s);
int b = getColour(rawData[x][y][2], luminanceMap[x][y],
image[x][y], s);
lastToneMap.setRGB(x, y, b | (g << 8) | (r << 16) | 0xFF000000);
}
}
return lastToneMap;
}
private int getColour(double cIn, double lIn, double lOut, double s) {
double tmp = cIn / lIn;
tmp = Math.pow(tmp, s);
tmp *= lOut;
if (tmp > 1.0) {
tmp = 1.0;
}
if (tmp < 0.0) {
tmp = 0.0;
}
return (int) (255 * tmp);
}
public BufferedImage generateToneMap(HDRImage sourceImage, Rectangle rect) {
throw new UnsupportedOperationException("Not supported yet.");
}
private double[][] getAttenuation(double[][] luminanceMap, int level, int
maxLevel, double alpha, double beta) {
int width = luminanceMap.length;
int height = luminanceMap[0].length;
double[][] attenuationMap = new double[width][height];
double[][] gradientMap = new double[width][height];
double divider = Math.pow(2.0, level);
double mean = 0.0;
for (int x = 1; x < width - 1; x++) {
for (int y = 1; y < height - 1; y++) {
int xp1 = x + 1;
int yp1 = y + 1;
int xm1 = x - 1;
int ym1 = y - 1;
double gx = luminanceMap[xp1][y] - luminanceMap[xm1][y];
double gy = luminanceMap[x][yp1] - luminanceMap[x][ym1];
gx = gx / divider;
gy = gy / divider;
double mag = Math.sqrt(gx * gx + gy * gy);
gradientMap[x][y] = mag;
mean += mag;
}
}
mean = mean / ((double) (width * height));
for (int x = 1; x < width - 1; x++) {
for (int y = 1; y < height - 1; y++) {
double mag = gradientMap[x][y];
attenuationMap[x][y] = Math.pow(mag / (mean * alpha), beta - 1.0);
if (attenuationMap[x][y] > 1.0) {
attenuationMap[x][y] = 1.0;
}
}
}
for (int x = 0; x < width; x++) {
attenuationMap[x][0] = attenuationMap[x][1];
attenuationMap[x][height - 1] = attenuationMap[x][height - 2];
}
for (int y = 0; y < height; y++) {
attenuationMap[0][y] = attenuationMap[1][y];
attenuationMap[width - 1][y] = attenuationMap[width - 2][y];
}
if (level < maxLevel) {
double[][] scaledMap = getAttenuation(scale(luminanceMap, width /
2, height / 2), level + 1, maxLevel, alpha, beta);
scaledMap = scale(scaledMap, width, height);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
attenuationMap[x][y] *= scaledMap[x][y];
}
}
}
return attenuationMap;
}
private double[][][] getGradient(double[][] luminanceMap, double[][]
attenuation) {
int width = luminanceMap.length;
int height = luminanceMap[0].length;
double[][][] gradientMap = new double[width][height][2];
for (int x = 1; x < width - 1; x++) {
for (int y = 1; y < height - 1; y++) {
int xp1 = x + 1;
int yp1 = y + 1;
int xm1 = x - 1;
int ym1 = y - 1;
double gx = luminanceMap[xp1][y] - luminanceMap[xm1][y];
double gy = luminanceMap[x][yp1] - luminanceMap[x][ym1];
gx = gx * attenuation[x][y];
gy = gy * attenuation[x][y];
gradientMap[x][y][0] = gx;
gradientMap[x][y][1] = gy;
}
}
for (int x = 0; x < width; x++) {
gradientMap[x][0] = gradientMap[x][1];
gradientMap[x][height - 1] = gradientMap[x][height - 2];
}
for (int y = 0; y < height; y++) {
gradientMap[0][y] = gradientMap[1][y];
gradientMap[width - 1][y] = gradientMap[width - 2][y];
}
return gradientMap;
}
private double[][] getDiv(double[][][] gradient) {
int width = gradient.length;
int height = gradient[0].length;
double[][] div = new double[width - 1][height - 1];
for (int x = 1; x < width; x++) {
for (int y = 1; y < height; y++) {
div[x - 1][y - 1] = (double) (gradient[x][y][0] - gradient[x -
1][y][0] + gradient[x][y][1] - gradient[x][y - 1][1]);
}
}
return div;
}
private double[][] getImage(double[][] div) {
int width = div.length;
int height = div[0].length;
int n = width * height;
n = iterations;
double[][] image = new double[width][height];
for (int step = 0; step < n; step++) {
log.info("Step: " + step + "/" + n);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
double sigma = 0;
if (x < width - 1) {
sigma += image[x + 1][y];
}
if (x > 0) {
sigma += image[x - 1][y];
}
if (y < height - 1) {
sigma += image[x][y + 1];
}
if (y > 0) {
sigma += image[x][y - 1];
}
image[x][y] = (1 - omega) * image[x][y] + omega * (double)
(-(div[x][y] - sigma) / 4.0);
}
}
}
JHDRIUtils.normalize(image);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image[x][y] = (double) Math.exp(image[x][y]);
}
}
return image;
}
private double[][] scale(double[][] map, int width, int height) {
int mapWidth = map.length;
int mapHeight = map[0].length;
double[][] newMap = new double[width][height];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
double u = ((double) x) / ((double) width);
double v = ((double) y) / ((double) height);
u *= (double) mapWidth;
v *= (double) mapHeight;
int mapX = (int) Math.floor(u);
int mapY = (int) Math.floor(v);
double u_ratio = u - mapX;
double v_ratio = v - mapY;
double u_opposite = 1 - u_ratio;
double v_opposite = 1 - v_ratio;
int mapXp1 = mapX + 1;
int mapYp1 = mapY + 1;
mapX = inBounds(mapX, mapWidth);
mapY = inBounds(mapY, mapHeight);
mapXp1 = inBounds(mapXp1, mapWidth);
mapYp1 = inBounds(mapYp1, mapHeight);
double result = (map[mapX][mapY] * u_opposite +
map[mapXp1][mapY] * u_ratio) * v_opposite +
(map[mapX][mapYp1] * u_opposite + map[mapXp1][mapYp1] *
u_ratio) * v_ratio;
newMap[x][y] = result;
}
}
return newMap;
}
private int inBounds(int val, int max) {
if (val < 0) {
return 0;
}
if (val >= max) {
return max - 1;
}
return val;
}
public int getLevels() {
return levels;
}
public void setLevels(int levels) {
this.levels = levels;
}
public double getAlpha() {
return alpha;
}
public void setAlpha(double alpha) {
this.alpha = alpha;
}
public double getBeta() {
return beta;
}
public void setBeta(double beta) {
this.beta = beta;
}
@Override
public String toString() {
return "Gradient Compression Tonemapper";
}
public int getIterations() {
return iterations;
}
public void setIterations(int iterations) {
this.iterations = iterations;
}
public double getS() {
return s;
}
public void setS(double s) {
this.s = s;
}
}
--
Mateusz Ludwin mateuszl [at] gmail [dot] com
-
73. Data: 2009-03-12 14:06:33
Temat: Re: hdr a Jpg
Od: "Paweł W." <p...@p...onet.pl>
Janko Muzykant pisze:
>>> Słońce na niebie.
>> abpc radzi sobie świetnie z D3 i pojedynczą ekspozycją pod Słońce.
>
> Bo ma hdra sprzętowego :)
Miał. Jak miał S5Pro. Teraz ma D3 :P
Pozdrawiam,
Paweł W.
--
Gates i Stirlitz patrzą, jak faszyści palą książki:
- Cenzura - pomyślał Stirlitz.
- Walka z piractwem - pomyślał Gates.
| mail to: pawelek_79 |
| @poczta.onet.pl |
-
74. Data: 2009-03-12 14:15:09
Temat: Re: hdr a Jpg
Od: j...@a...at
On 12 Mrz., 14:31, "Stefan Nawrocki" <o...@3...com.pl> wrote:
> Użytkownik "Mateusz Ludwin" <n...@s...org> napisał w
wiadomościnews:gpaqfn$jnr$1@inews.gazeta.pl...
>
> > Stefan Nawrocki wrote:
> > > Wydaje mi się, że różne pojęcia, które w wątku się pojawiają nie do
> > > końca są dobrze interpretowane.
> > > Pierwsza sprawa - to liczba bitów przypadająca na składową koloru - czy
> > > to jest 8, czy 12 - co to tak naprawdę zmienia?
> > > Jeśli czerń odpowiada poziomowi 0, a biel poziomowi maksymalnemu
>
> > W HDRI nie ma poziomu maksymalnego - to jest jedno z podstawowych założeń
> > tego typu obrazów. Dodatkowe bity idą i w rozdzielczość i w zakres, bo
> > odcienie opisuje się liczbami zmiennoprzecikowymi.
>
> Ten fragment nie byl o HDR, ale o tym, że niektórzy sądzą, że zmiana liczby
> bitów (jpg - 8, RAW - 12) zmienia w istotny sposób zakres tonalny. Tak nie
> jest.
nie w istotny ( ale jednak widoczny ) i nie zakres tylko pojemnosc
tonalna
zakres tonalny jest zawsze ten sam od bieli do czerni powiedzmy
monitora lub papieru tak jak obserwujemy obraz.
Dla slowa 12 bitowego zostanie on podzielony na wiecej stopni niz dla
slowa 8 bitowgo - , beda one wezsze - odwzorowanie bedzie
subtelniejsze .
dla slowa np 2 bitowego ten sam zakres tonalny zostalby podzielony na
4 stopnie tzn przy takim samym zakresie bylyby znacznie mniejsza
pojemnosc tonalna.
Glebokosc slowa ma istotny wplyw na pojemnosc tonalna .
rzecz w tym ze pojemnosc tonalna zawarta w rawie jest wieksza od
pojemnosci tonalnej jak mozna przeniesc w jpeg - cale nieszczescie
polega na tym ze jpeg nie jest tylko jakims skomprymowanym sposobem
zapisu RAW , pozwalajacym zaoszczedzic miejsce bez utraty
szczegolow ,
nie -"wolajac" raw suwakami okreslasz co zostanie w jpeg zapisane , a
co nie .
pozniej koniec - co nie zostanie zapisane przepadnie. Po zapisaniu nie
wyciagniesz np szczegolow z czerni , chociaz w rawie one siedza - co
najwyzej w jpeg mozesz czern zrobic mniej czarna czyli jakos rozjasnic
do szarosci.
Trzebaby jeszcze rozrozniac pomiedzy dynamika wejscia i wyjscia
zakres tonalny wejscia jest zmienny wyjscia zawsze taki sam - tak w
przyblizeniu.
XXX
-
75. Data: 2009-03-12 14:16:40
Temat: Re: hdr a Jpg
Od: "Paweł W." <p...@p...onet.pl>
Mateusz Ludwin pisze:
> Jakub Jewuła wrote:
>
>> Mateuszu, czekamy na blyskotliwa ropiste ;))))))
>
> Jadziem:
>
> public class GradientCompressionTonemapper implements IToneMapper {
>
> private static final Logger log =
> Logger.getLogger(GradientCompressionTonemapper.class
.toString());
> private int levels = 3;
> private double alpha = 0.1;
Ludzie zlitujcie się. To grupa o fotografii cyfrowej, a nie o
programowaniu! :(
Pozdrawiam,
Paweł W.
--
Gates i Stirlitz patrzą, jak faszyści palą książki:
- Cenzura - pomyślał Stirlitz.
- Walka z piractwem - pomyślał Gates.
| mail to: pawelek_79 |
| @poczta.onet.pl |
-
76. Data: 2009-03-12 14:38:35
Temat: Re: hdr a Jpg
Od: Janko Muzykant <j...@w...pl>
Paweł W. pisze:
>>>> Słońce na niebie.
>>> abpc radzi sobie świetnie z D3 i pojedynczą ekspozycją pod Słońce.
>> Bo ma hdra sprzętowego :)
> Miał. Jak miał S5Pro. Teraz ma D3 :P
A, no to jestem nie na bieżąco.
No cóż - nie ma hadeera, zatem fotograf jest po prostu zdolny :)
--
pozdrawia Adam
różne takie tam: www.smialek.prv.pl
/nie ma już chleba, ale są czipsy/
-
77. Data: 2009-03-12 14:40:52
Temat: Re: hdr a Jpg
Od: Mateusz Ludwin <n...@s...org>
Janko Muzykant wrote:
> Paweł W. pisze:
>>>>> Słońce na niebie.
>>>> abpc radzi sobie świetnie z D3 i pojedynczą ekspozycją pod Słońce.
>>> Bo ma hdra sprzętowego :)
>> Miał. Jak miał S5Pro. Teraz ma D3 :P
>
> A, no to jestem nie na bieżąco.
> No cóż - nie ma hadeera, zatem fotograf jest po prostu zdolny :)
Może sobie kupił filtry...?
--
Mateusz Ludwin mateuszl [at] gmail [dot] com
-
78. Data: 2009-03-12 14:41:28
Temat: Re: hdr a Jpg
Od: "Stefan Nawrocki" <o...@3...com.pl>
Użytkownik "Mateusz Ludwin" <n...@s...org> napisał w wiadomości
news:gpb4dl$ck7$1@inews.gazeta.pl...
> Stefan Nawrocki wrote:
>
> > Ten fragment nie byl o HDR, ale o tym, że niektórzy sądzą, że zmiana
liczby
> > bitów (jpg - 8, RAW - 12) zmienia w istotny sposób zakres tonalny. Tak
nie
> > jest.
>
> Zmienia - te dodatkowe bity w RAW idą w zakres a nie rozdzielczość. JPG z
> aparatu ma zwykle 8bitów wyciętych ze środka 12bitowego rawa, a nie
obciętych
> przez odrzucenie najmniej znaczących bitów. Dopiero jeśli użyje się
różnych fill
> light i recovery, to mamy do czynienia z faktyczną kompresją.
http://www.optyczne.pl/125-s%C5%82ownik-Zakres_tonal
ny.html
Punkt 2 w uwagach na dole.
> Mapowanie tonów to każde odwzorowanie z przestrzeni tonalnej w przestrzeń
> tonalną.
Jeśli "mapowaniem" nazwiemy każde odwzorowanie - to rzeczywiście, jakieś tam
mapowanie jest. W programamch do obrobki HDR przyjęło się jednak rozdzielać
dwie fazy. W pierwszej fazie - tworzony jest obraz "surowy" i w drugiej -
zachodzi właśnie owo mapowanie, W Photomatixie są np. dwie metody mapowania
(Details Enhancer i Tone Compressor) i są one niezależne od piewszej fazy -
tworzenia obrazu "surowego".
Jeśli obraz "surowy" (w którym czarny odpowiada wartości 0, a biały -
wartości X) znormalizujemy (liniowo) do takiej postaci, w której czarnemu
odpowiada 0, a białemu 255 - to obraz taki możemy wyświetlić wprost na
monitorze. Jeśli przekształcenie nie jest liniowe - to też jest (w sensie
ogólnym) jakieś mapowanie, ale to jest tak samo jakby przekształcić liniowo,
a potem pokręcić gammą, jasnością i kontrastem. Takie przeksztalcenie działa
globalnie na cały obraz gdzie każdy piksel jest przetwarzany wg tej samej
reguły.
Istotą "mapowania" (takiego jak np. Details Enhancer w Photomatixie) jest
badanie otoczenia każdego piksela (budowana jest tzw. "maska" i
przekształcenie jest uzależnione od nasycenia tej maski) i zastosowanie
innego przekształcenia w zależności od sąsiedztwa. W tym rozwiązaniu piksel
o jakimś nasyceniu może być inaczej zmapowany w zależności od tego gdzie się
znajduje na obrazie. W tym sensie mój przykład (i algorytm) tego nie robi.
> OK, a możesz jakoś opisać to przekształcenie matematycznie, bo ciężko coś
> wydobyć z tak "ciężkiego" kodu?
W najprostszym rozwiązaniu dla trzech zdjęć ustalamy dwa poziomy (granice).
W ostatnim przykładzie granice były ustawione na 150, i 250.
Tzn. wszystko co jest wewnątrz tej granicy jest brane ze zdjęcia "dobrego".
Wszystko co powyzej - z niedoświetlonego, wszystko co poniżej - z
prześwietlonego. Tak wygląda to przeksztacenie:
http://www.3n.com.pl/Nikon/wynik_2.jpg
Oczywiście - widać "ostre" granice przejść. Trzeba więc obliczyć "odchyłkę"
od granicy i w obszarze granicznym połączyć płynnie piksele z obu zdjęć w
stosunku proporcjonalnym do tej odchyłki. Taka jest idea zaprezentowanego
algorytmu. Nie ma w nim uzależnienia koloru piksela od koloru sąsiednich
pikseli, a więc nie ma lokalnego pdobijania (lub zmniejszania) kontrastu
celem uwypuklenia pewnych obszarów.
Pozdrawiam
Stefan Nawrocki
-
79. Data: 2009-03-12 14:43:05
Temat: Re: hdr a Jpg
Od: Janko Muzykant <j...@w...pl>
Mateusz Ludwin pisze:
>> Mateuszu, czekamy na blyskotliwa ropiste ;))))))
> Jadziem:
> private int getColour(double cIn, double lIn, double lOut, double s) {
> double tmp = cIn / lIn;
> tmp = Math.pow(tmp, s);
> tmp *= lOut;
> if (tmp > 1.0) {
> tmp = 1.0;
> }
Ja też chcę, ja też!
clr a
movc a, @a+dptr
cjne a, #2, m191
sjmp m15
m191:
cjne a, #1, m13
acall selmel
clr a
movc a, @a+dptr
mel13:
mov rcap2h, a
inc dptr
clr a
movc a, @a+dptr
mov rcap2l, a
inc dptr
setb tr2
sjmp m16
(moje co prawda wygrywa melodyjki, ale też jest ładne :)
--
pozdrawia Adam
różne takie tam: www.smialek.prv.pl
/nieważne są fakty, ważne są emocje/
-
80. Data: 2009-03-12 14:43:48
Temat: Re: hdr a Jpg
Od: "Stefan Nawrocki" <o...@3...com.pl>
Poprawiłem link - może teraz zadziała:
http://www.optyczne.pl/125-słownik-Zakres_tonalny.ht
ml