Простой хромакей по цветовой компоненте RGB

Все чаще и чаще нам на глаза попадается использование хромакея в самых неожиданных местах. Долго свербила мысль попробовать реализовать что-то свое.

Что же собой представляет хромакей, для тех, кто не знаком? Хромакей — технология совмещения изображений, широко используемая в кино-видеo-теле-производстве. В качестве «ключевых» цветов чаще всего используют ядовитозелёный и яркоголубой, только по тому что такие оттенки не встречаются в спектре цвета лица и волос человека. Для очень затемнённых сцен, типа подземных пещер, используют яркооранжевый цвет в качестве ключевого.

Подумав некоторое время и, зайдя с дальнего и не самого простого варианта с использованием модели HSL, сходу что-то универсальное реализовать не получилось. Надо сказать, это «некоторое» время весьма прилично затянулось. И тут совершенно неожиданно пришла в голову мысль попробовать с простого варианта, просто отбросив одну из компонент и по ее значению сделать альфа-канал. За основу был взят зеленый канал, как один из наиболее часто используемых в подобных задачах.
Ход мыслей был таков:
— всего имеем 256 градаций каждого канала
— все, что, примерно, ниже 30-го значения, можно отнести к «черному» зеленому
— вариант, где зеленая составляющая является доминирующей, почти наверняка является зеленым цветом (тут еще есть градации серого, близкого к серому и пр.)

Имея наобум взятое изображение из интернета


(взято по адресу vid8o.files.wordpress.com/2011/01/chromakey.jpg?w=620)

и аналогично взятый фон


(не знаю кому принадлежит)

идем реализовывать алгоритм для 24-битного изображения и 8-битной маски (альфа-канала):

byte const black_threshold = 30;

byte* chroma_p = chroma_data;
byte* alpha_p = alpha_data;

for (int counter = 0; counter < size; ++counter)
{
  byte b = *chroma_p++;
  byte g = *chroma_p++;
  byte r = *chroma_p++;

  // определим, зеленый ли пиксел перед нами
  if (g > r && g > b)
  {
    // если этот зеленый достаточно черный, то оставим его нетронутым в маске
    if (g < black_threshold)
      *alpha_p++ = 255;
    else
    {
      // вычислим разницу между компонентами для определения яркости маски
      byte m = g - max(r, b);
      *alpha_p++ = 255 - m;
    }
  }
}


Запускаем, получаем:

маска


и по маске наложенное изображение на наш фон


В итоге наша реализация показывает довольно неплохой, но очень грубый результат. В частности, мы видим на полученном изображении зеленый туман. Вглянем на гистограмму маски:



Видим, что основная часть маски в области прозрачности лежит в интервале 17-100. Поэтому растянем интервал значений 100-255 на интервал 0-255, «убив» тем самым пелену и получим вот такую гистограмму



и вот такую маску, более похожую на необходимую нам

.

В результате мы избавились от ненужной нам пелены

,

но видим оставшийся ореол вокруг маски и зеленую «засветку» тона, которая у нас естественно смотрелась на первоначальном зеленом фоне и совсем инородно смотрится на нашем новом фоне. В целом результат обошелся дешево, но остается ряд вопросов. Как бороться с ореолом? Можно попробовать сузить маску сверткой, или попробовать как-то иначе растянуть гистограмму маски.

А вот как бороться с засветом я пока не знаю, поэтому для дальнейщей работы пищи достаточно. Критика, советы и рекомендации приветствуются.

Так же буду признателен ссылкам на подобные работы, в том числе их реализацию при помощи GPU, OpenCV и пр.

UPD 1: увеличил в два раза размер обрабатываемого изображения и масок
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 10
  • +2
    Маску надо обработать функцией Threshold или Binary, тогда срежется правая часть гистограммы. На картинках нифига не разглядеть, хотелось бы крупнее. Зеленость цвета можно определять отношением интенсивностей каналов, красный и синий должны давать например не больше 5% общей яркости:

    if (r+b)/(r+g+b)<0.05 {...}
    • 0
      >Threshold или Binary, тогда срежется правая часть гистограммы
      так нам нужно, во-первых, срезать левый пик, а во-вторых, оставить остальной градиент, сместив его к черному, чтобы переход оставался сглаженным. Иначе, как я понимаю, получим на границах объектов очень рубленные края. Или я неверно понимаю?
      • 0
        Да, описка. Надо убрать левый пик. Threshold с этим отлично справится, если задать ему точку, заведомо находящуюся между пиками
        • 0
          в общем-то я так и делаю, вопрос наверное в реализации. Может есть смысл добавить этот код в пост?
          • 0
            Тогда надо будет сравнение хоть какое-нибудь сделать, типа наука. Код мне не жалко, пусть все пользуются
    • 0
      >На картинках нифига не разглядеть, хотелось бы крупнее.
      не знаю как поступить, заменить в посте картинки на другие, более крупные или как-то иначе? просто размер оригинальный, искусственно растягивать вроде не комильфо

      >Зеленость цвета можно определять отношением интенсивностей каналов
      спасибо, поэкспериментирую в этом направлении
      • 0
        ерунда какая-то, а не алгоритм.
        берете +- цвет фона. определяете границы. на границах сохраняете альфу. остальное убираете — вот вам и маска.
        • 0
          что значит +-цвет фона? на сколько брать +-? исходя из чего определить границы?
          вот, например, есть сильно неидеальный исходник:



          вот что дает предложенный алгоритм



          довольно сильный градиент по зеленому мой алгоритм убрал. И к тому же он достаточно быстр и прост в реализации. А как определить для этого случая Ваш алгоритм?
        • +1
          Было бы хорошо в начале статьи вставить 1 предложение, объясняющее, что такое хромакей. Это будет удобнее чем, лезть в википедию.
          • 0
            Спасибо, добавил кратенько.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.