Stworzenie maski dla programowo koloryzowanego obrazka

0

Nie wiem gdzie ten wątek założyć, bo w sumie dotyczy grafiki, ale i może zahaczyć o programowanie, więc ląduje tutaj, w kategorii niejako zbiorczej.

Załóżmy, że w programie ma być widoczny obrazek, który to da się koloryzować (nie mylić z kolorowaniem). Ma on przedstawiać określoną powierzchnię - dla przykładu niech będzie nią powierzchnia dachu, czyli dachówki. Pobrałem sobie przykładową teksturę z dachówkami - wygląda tak:

texture.png

Pierwszym pomysłem jest przerobienie tej tekstury na półprzezroczysta maskę. Dzięki temu będzie można wypełnić obszar danym kolorem, a następnie namalować na tym obszarze obraz maski. Dlatego też w programie Gimp przygotowałem sobie taką maskę. Pierwszy ktok to desaturacja (przerobienia obrazu na skalę szarości), następnie regulacja poziomów (rozjaśnienie pewnych obszarów do białości), a ostatni to zmiana koloru na alfę (tu: białego). Krok po kroku wygląda to tak:

mask-creation.png

Po zmianie białego koloru na alfę, otrzymujemy gotową, półprzezroczystą maskę. Dla podglądu - mask.png - możecie się pobawić. Taką maskę można wykorzystać w wymyślony sposób - najpierw malować jednolite tło wybranym kolorem, następnie namalować maskę. Kilka przykładów:

final-examples.png

Wygląda bardzo fajnie - idealnie nie jest, ale przygotowałem ją na szybko i można lepiej. Niestety problem pojawia się, jeśli pod maską znajdzie się ciemny kolor - szczegółowość grafiki spada, a dla czarnego tła kompletnie nie widać kształtu dachówek. Od lewej - szare tło, ciemnoszare i prawie czarne:

gray-examples.png

Tak więc pomysł nie taki dobry, bo spełnia oczekiwania połowicznie - dla jasnych kolorów jest dobrze, ale dla ciemnych i bardzo ciemnych bardzo źle. Wszystko dlatego, że na gotowej masce, półprzezroczyste są tylko czarne piksele, które finalnie przyciemniają kolor tła. Jeśli zmienię czarny kolor na alfę to sytuacja będzie odwrotna - półprzezroczyste będą jedynie białe piksele, więc dla ciemnych teł będzie widać szczegóły, a dla jasnych nie.

Dlatego też chciałbym się dowiedzieć, czy istnieje sposób na przygotowanie takiej maski, aby również dla ciemnych kolorów było po pierwsze widać szczegóły, a po drogie, aby wybrany kolor odzwierciedlany był wiernie? A jeśli półprzezroczysta maska nie nadaje się do tego, to w jaki sposób wykonać taki efekt?

Dodam, że nie muszę korzystać z półprzezroczystej maski. Równie dobrze owe koloryzowanie może działać w ten sposób, że na wejściu dostanie się 24-bitowy obraz (np. w skali szarości), i na podstawie jasności odcieni, obraz ten zostanie przemalowany i wypluty na wyjściu. Problem w tym, że nie wiem w jaki sposób ustalać odcień wybranego koloru na podstawie jasności odcieni szarego, w taki sposób, aby faktura była widoczna zarówno dla koloru białego, czarnego i dowolnego innego.

Ma ktoś jakieś doświadczenie z obróbką obrazów? Prosiłbym o jakieś wskazówki, linki, pomysły - cokolwiek. Mam też pewien pomysł na programowe przemalowywanie obrazu-wzorca w locie, który w międzyczasie spróbuję zaimplementować. Jednak do tego czasu może znajdzie się ktoś ze swoim pomysłem, za co będę wdzięczny.

0

Może na jakieś forum grafików?
Nie da się zrobić dwóch lub trzech masek i nałożyć je na siebie tak żeby dawały w miarę zbliżony efekt dla białego i czarnego koloru ?

2

Przekonwertuj obrazek do przestrzeni YCbCr, a potem zrób co chcesz ze składowymi Cb i Cr. Kolor się zmieni z zachowaniem szczegółów. Możesz też zrobić jakieś filtracje na składowej Y, żeby coś wygładzić, pominąć.
Nie wiem co chcesz docelowo osiągnąć, więc tak tylko strzelam z tym rozwiązaniem.

0
gk1982:

Nie da się zrobić dwóch lub trzech masek i nałożyć je na siebie tak żeby dawały w miarę zbliżony efekt dla białego i czarnego koloru ?

Póki co próbowałem z jedną maską - efekt podałem w pierwszym poście. Tyle że maska przygotowana w ten sposób będzie dobrze wyglądać tylko dla w miarę jasnych kolorów. Jeśli kolor jest jasny to maska przyciemnia jego fragmenty i w ten sposób uwidocznia jego realną fakturę. Natomiast jeżeli kolor jest ciemny lub czarny to nie da się go bardziej przyciemnić - stąd brak widoczności szczegółów.

Aby to miało prawo działać, pewne fragmenty maski musiały by rozjaśniać tło (efekt oświetlenia), a inne przyciemniać (efekt cienia). Problem w tym, że trudno jest przygotować maskę w taki sposób, aby końcowa grafika wyglądała realistycznie.

chodnik:

Przekonwertuj obrazek do przestrzeni YCbCr, a potem zrób co chcesz ze składowymi Cb i Cr. Kolor się zmieni z zachowaniem szczegółów.

Brzmi zachęcająco - poczytam na ten temat.

Nie wiem co chcesz docelowo osiągnąć, więc tak tylko strzelam z tym rozwiązaniem.

Wszystko opisałem w pierwszym poście. Potrzebuję przedstawić powierzchnię (np. dachówki) pomalowaną wybranym kolorem w taki sposób, aby wyglądało to sensownie i wiernie. Wyobraź sobie, że poniższa grafika to zdjęcie fragmentu dachu na Twoim domu:

texture.png

Teraz wyobraź sobie, że wychodzisz na dach, malujesz go farbą o innym kolorze, po czym robisz zdjęcie dokładnie tego samego fragmentu w dokładnie takim samym nasłonecznieniu. Szczegółowość pozostaje bez zmian (nadal widać zarysy dachówek, fragmenty bardziej i mniej oświetlone), jednak kolor jest inny.

To co mnie interesuje to właśnie owe malowanie dachu farbą.

1

Na SO proponują zmianę RGB na HSV co również wydaje mi się rozsądnym rozwiązaniem, które może się powieść.

1

Wyobrażam sobie, że to ma być próbnik kolorów, który zaprezentuje jak będzie wyglądał kolor na dachówce (albo innej teksturze). Zatem masz dachówkę pomalowaną kolorem A i płytki z kolorami A, B, C, ...
Używając zmiany przestrzeni naYCbCr czy HSV będziesz mógł sobie dostroić kolor dachówki do koloru próbnika. Można do tego użyć techniki o nazwie "histogram matching"
https://www.mathworks.com/matlabcentral/answers/268037-what-willbe-best-image-density-matching-technique
W linku są pokazane obrazki po przeniesieniu kolorów z jednego na drugi. Być może to właśnie o to ci chodzi. Daj znać jak efekty i czy coś się przydało z tego.

0

Wyobrażam sobie, że to ma być próbnik kolorów, który zaprezentuje jak będzie wyglądał kolor na dachówce (albo innej teksturze). Zatem masz dachówkę pomalowaną kolorem A i płytki z kolorami A, B, C, ...

Tak, o to chodzi. Ujmując ogólnie, na wejściu mam obraz (teksturę lub maskę) i kolor, na wyjściu mam otrzymać pokoloryzowaną bitmapkę, która to zostanie wyświetlona na ekranie (w odpowiednich komponentach).

No nic, trochę informacji jest, więc można coś więcej działać - dziękuję za sugestie.

7

Odświeżam wątek, albowiem rozwiązałem swój problem :]

Skupiłem się jednak na stworzeniu maski, dlatego że "koloryzowanie" powierzchni za jej pomocą będzie o wiele prostsze, a poza tym, bardzo łatwo będzie malować jedynie zadane fragmenty - wystarczy w tle namalować jednokolorowy wielokąt i całość przykryć maską. No i efektywność tego rozwiązania będzie wysoka, co też jest dla mnie ważne.

Niestety Gimpa nie udało mi się zmusić do posłuszeństwa, więc naklepałem trochę kodu. Przygotowałem sobie takie małe, testowe narzędzie, za którego pomocą możliwe jest przekonwertowanie zwykłej, kolorowej i nieprzezroczystej tekstury, na przezroczystą, dwukolorową maskę. Wystarczy załadować bitmapkę, a program wypluje półprzezroczystą maskę w postaci 32-bitowego PNG. Do dyspozycji jest też trochę ustawień, aby móc idealnie dostroić konwerter. Głównie zależało mi na przygotowaniu kafli o wymiarach 148x148, więc takie ten testowy program obsługuje.

Program wygląda w ten sposób:

ttmconv.png

Strzałki określają drogę, jaką konwerter pokonuje. Określa się obraz źródłowy (z lewej), który przerabiany jest na skalę szarości (u dołu), ta tekstura łączona jest z kolorem tła (z prawej), czego wynikiem jest półprzezroczysta maska nad kolorem tła (u góry). Ostatnim krokiem jest zapis maski do pliku.

Program składa się z czterech obrazków, jako czterech kroków konwersji:

  • Source image - wejściowa, kolorowa tekstura. Obsługiwane są jedynie 24-bitowe bitmapy o wymiarach 148x148 (większe zostaną przycięte, a dla mniejszych, pusta przestrzeń zostanie wypełniona białym kolorem). Kliknięcie w ten obraz powoduje pobranie koloru piksela i ustawienie go jako Background color.
  • Texture image - wejściowa tekstura, przekonwertowana na skalę szarości. Z racji tej, że wyjściowa maska jest dwukolorowa (czarne piksele przyciemniają kolor, białe rozjaśniają - stopień przyciemnienia/rozjaśnienia na podstawie kanału alpha), wygodniej jest bazować na teksturze w odcieniach szarości (każdy piksel opisują składowe RGB o takiej samej wartości). Kliknięcie w ten obraz powoduje pobranie odcienia szarości i ustawienie go jako Transparent value.
  • Background color - kolor tła dla podglądu maski.
  • Mask preview - podgląd wygenerowanej maski nad wybranym kolorem tła.

Dostępnych jest też kilka opcji:

  • Brightness - stopień jasności tekstury (tej w skali szarości). Wartości ujemne oznaczają przyciemnienie tekstury, a dodatnie jej rozjaśnienie. Zakres jest duży, nieprocentowy, więc można precyzyjnie wybrać jasność. 0 oznacza, że podczas konwersji na odpowiednik w skali szarości, jasność nie zostanie zmieniona.

  • Transparent Value - odcień szarości, który określa piksele całkowicie przezroczyste. Wszystkie piksele o wartościach składowych mniejszych niż Transparent Value zostaną w masce przyciemnione, a o wyższych rozjaśnione (na podstawie odpowiednio obliczonego stopnia przezroczystości).

  • Transparency multiplier - mnożnik stopnia przezroczystości. Wyliczony stopień przezroczystości mnożony jest przez tą wartość, co umożliwia wyostrzenie faktury powierzchni. Im wyższy mnożnik, tym mocniejsze wyostrzenie.

  • Load from file... - otwiera okno dialogowe do wyboru obrazu źródłowego z dysku.

  • Change color... - otwiera okno dialogowe do wyboru koloru tła dla podglądu maski.

  • Save mask to file... - otwiera okno dialogowe do zapisu obrazu maski na dysku.

Jeśli chodzi o możliwości tego narzędzia to tyle - teraz może trochę o kodzie. Za konwersję kolorowego obrazu na teksturkę w skali szarości odpowiada metoda CreateTexture. Jest ona tylko wrapperem i wywołuje jedną z trzech właściwych metod:

  • CreateDarkerTextureFromImage - jeśli tekstura w skali szarości ma zostać przyciemniona,
  • CreateOriginalTextureFromImage - jeśli jasność tekstury w skali szarości nie będzie zmieniana,
  • CreateBrighterTextureFromImage - jeśli tekstura w skali szarości ma zostać rozjaśniona.

Te metody odróżnia jedynie konwersja kolorowego piksela na odpowiedni odcień w skali szarości - reszta jest chamsko skopiowana. Natomiast za konwersję tekstury w skali szarości na półprzezroczystą maskę odpowiada metoda CreateMask. Jej kod poniżej:

procedure TMainForm.CreateMask();
var
  LTextureLine: PRGBTripleArr;
  LMaskLine: PRGBQuadArr;
  LLineIdx, LPixelIdx: Integer;
  LGrayShade, LTransparentValue: UInt8;
  LAlpha: Integer;
  LMultiplier: Double;
begin
  LTransparentValue := CTransparentValueSpin.Value;
  LMultiplier := CTransparencyMultiplierSpin.Value;

  FTextureImage.BeginUpdate();
  FMask.BeginUpdate();
  try
    for LLineIdx := 0 to FTextureImage.Height - 1 do
    begin
      LTextureLine := FTextureImage.ScanLine[LLineIdx];
      LMaskLine := FMask.ScanLine[LLineIdx];

      for LPixelIdx := 0 to FTextureImage.Width - 1 do
      begin
        LGrayShade := LTextureLine^[LPixelIdx].R;

        with LMaskLine^[LPixelIdx] do
        begin
          FillChar(B, 3, UInt8(LGrayShade > LTransparentValue) * 255);

          LAlpha := Round(Abs(LGrayShade - LTransparentValue) * LMultiplier);
          A := Min(Max(LAlpha, 0), 255);
        end;
      end;
    end;
  finally
    FTextureImage.EndUpdate();
    FMask.EndUpdate();
  end;
end;

Dla każdego piksela z wejściowego obrazu (czyli FTextureImage) pobierana jest wartość składowej R (wszystkie składowe mają taką samą wartość, więc nie ma to znaczenia). Jeśli wartość składowej jest wyższa niż Transparent value to piksel zostaje pomalowany na biało, w przeciwnym razie na czarno. Ostatnim krokiem jest określenie stopnia przezroczystości. Najpierw obliczana jest bezwzględna różnica pomiędzy wartością składowej a Transparent value, wynik mnożony jest o wartość mnożnika. Na koniec tak otrzymana wartość zostaje "upchnięta" w jednym bajcie (jeśli wynik jest mniejszy niż 0 to otrzymuje wartość 0, jeśli jest większy niż 255 to otrzymuje wartość 255, a jeśli mieści się w zakresie to pozostaje bez zmian).

To w sumie tyle, jeśli chodzi o funkcjonalność tego narzędzia. Wybrany sposób nie jest idealny, jednak pozwoli mi na wygodne stworzenie dobrych masek - muszę tylko dokładniej pobawić się parametrami i potestować na dużej liczbie kolorów. W załączniku ttmconv.zip podaję pełne źródła narzędzia, gdyby ktoś chciał skorzystać. W środku archiwum jest plik wykonywalny, więc aby się pobawić apką, nie trzeba nic kompilować.

Jeśli ktoś chciałby skorzystać z jakiegoś kawałka kodu to w tym programie znajdzie:

  • typy danych umożliwiające odczyt/modyfikację pikseli 24- i 32-bitowych,
  • funkcję konwersji koloru na odpowiednik w skali szarości,
  • funkcję przyciemniającą i rozjaśniającą kolor,
  • funkcję generującą półprzezroczystą maskę na podstawie tekstury w skali szarości,
  • inne duperele.
0

Fajnie, że napisałeś jak to ostatecznie zrobiłeś. Dla mnie wniosek jest taki, że czasem lepiej użyć prostego rozwiązania, jeżeli okaże się wystarczające, zamiast zagłębiać się w jakieś bardziej złożone metody. Muszę teraz wdrożyć tą filozofię do aktualnego projektu.

0

@chodnik: w tym przypadku oba sposoby (czyli stworzenie maski na podstawie źródłowej tekstury oraz alternatywa, czyli konwersja na HSL/HSV) są dość proste do zrobienia. Mnie bardziej zależało na stworzeniu maski, niż na konwersji obrazu, dlatego że widziałem już takie maski w akcji i efekt ich użycia mnie zadowala. Poza tym łatwiej kolorować jedynie zadane fragmenty obrazu (nieregularne wielokąty), co w przypadku konwersji kolorów będzie nieco trudniejsze.

Wydaje mi się, że znalazłem zadowalające mnie rozwiązanie tego problemu. Zapewne i tak sprawdzę jak będzie wyglądać koloryzowanie za pomocą konwersji na HSL/HSV - z czystej ciekawości. Jak nie spróbuję to się nie dowiem :]

0

Raczej miałem na myśli technikę "histogram matching", która jest bardziej złożona przy kodowaniu. Konwersja przestrzeni kolorów to tylko podejście do tego, co zaprezentowałeś, różnice są tylko we współczynnikach. W każdym razie twoje podejście jest prostsze i wystarczające. Mam takie przemyślenia filozoficzne po 2 tygodniach prób rozwiązania problemu z obrazkiem. I mam nadzieję, że znajdę niebawem jakieś proste rozwiązanie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1