Как выполнить билинейную интерполяцию по значениям RGB? - PullRequest
8 голосов
/ 12 января 2012

figure

Учитывая координаты черного пикселя, я мог бы интерполировать значения координат синего пикселя с помощью математического уравнения y = mx + c.Но как насчет новых значений пикселей RGB?Как мне получить средневзвешенное значение RGB для синих пикселей, учитывая, что значения RGB для черного пикселя даны как таковые на рисунке?Заранее спасибо.

Ответы [ 3 ]

12 голосов
/ 12 января 2012

(Это может занять много времени. Я постараюсь сделать его коротким, и в этом случае мне, вероятно, придется вернуться к своему ответу, чтобы ответить на вопросы.) Интерполяция цветового пространства в RGB часто использует трилинейную интерполяцию, которая может быть построенаповерх пары билинейных интерполяций.Но нет необходимости использовать трилинейную интерполяцию.Фактически, другие интерполанты часто лучше, например, симплициальный (или тетраэдрический) интерполант обычно предпочтительнее по различным причинам, чем трилинейный.Есть несколько таких тетраэдрических рассечений решетки, которые можно использовать.Один довольно стандартный.(Я не буду вдаваться в подробности, по крайней мере пока.) Кроме того, нет никаких причин, по которым человек ДОЛЖЕН интерполировать в RGB, а не в другом пространстве, хотя можно утверждать, что у RGB есть свои особые проблемы, обычно связанные синтерполяция нейтралов и ближних нейтралов.

Характеристика, которая имеет отношение к RGB и интерполяции, состоит в том, что нейтраль определяется как точка, такая, что R = G = B.У трилинейного интерполятора будет максимальная ошибка вдоль этой нейтральной оси, и он обычно будет иметь характерную (зубчатую) форму для ошибок вдоль нейтрального пути через цветовое пространство.

Так как же мы интерполируем в 3-м?Я предполагаю, что каждый интерполирует в правильной решетке точек в цветовом пространстве.В этом случае можно идентифицировать куб, который содержит любую отдельную точку.Если вы интерполируете внутри рассеянного набора точек, то простейшим решением обычно является построение триангуляции этих точек, а затем выполнить симплициальную (линейную) интерполяцию внутри любого данного тетраэдра.Интерполанты более высокого порядка здесь проблематичны, так как они могут вызвать проблемы с цветом в некоторых обстоятельствах.Например, не хотелось бы видеть развороты вдоль градиентов.Это может произойти, поскольку вызов является серьезной проблемой для сплайн-интерполантов в областях с относительно высокой кривизной.И если используется картографирование гаммы, то такие переходы, безусловно, будут проблемой.Даже если не требуется сопоставление гаммы, все же есть проблемы гаммы, которые необходимо решить.

Существует несколько способов построения триангуляции доменов из разбросанных данных.Альфа-формы основаны на триангуляции Делоне и являются разумным выбором.Но если предположить, что у вас есть правильная решетка и вы хотите выполнить трилинейную интерполяцию, проблема сводится к интерполяции внутри простого куба в 3-й точке.

Обратите внимание, что трилинейная интерполяция на самом деле не является линейной интерполяцией, равно как и билинейная интерполяция.Эти схемы линейны ТОЛЬКО вдоль осей решетки, но вдоль любого другого пути через цветовое пространство они имеют полиномиальный характер.Таким образом, трилинейный интерполант будет демонстрировать поведение кубического полинома вдоль главной диагонали или по большинству общих путей через куб.Мы можем убедить себя в том, что трилинейная интерполяция НЕ является действительно линейной, поскольку мы интерполируем 8 точек.в 3-й точке 4 точки определяют истинно линейный интерполант как функцию этих независимых переменных, но у нас есть 8 точек, которые определяют куб.То есть мы будем рассматривать отображение из одного пространства RGB в другое как действительно 3 независимых отображения, таким образом, RGB -> UVW (здесь я выбрал UVW для представления некоторого общего другого цветового пространства, которое может быть или не быть символом RGB.)

Хитрость в том, что мы строим трилинейный интерполант путем интерполяции пары билинейных интерполантов. Мы строим эти билинейные интерполанты, линейно интерполируя между парой точек вдоль одного ребра, а затем выполняя третью интерполяцию между ними. Так что на самом деле мы можем рассматривать трилинейный интерполант как составной из 7 простых линейных интерполяций. Интересно, что можно показать, что не имеет значения, по каким осям мы сначала выполняем интерполяцию. Таким образом, мы можем сначала интерполировать вдоль R, затем B, затем по осям G или выбрать любой другой порядок - трилинейный интерполант будет уникальным и одинаковым для любого выбранного порядка. (То же самое относится и к билинейному интерполанту.)

Итак, хитрость в том, как нам выполнить линейную интерполяцию между двумя триадами точек? Во-первых, нам нужно определить, где на отрезке между этими точками мы лежим. Например, рассмотрим две точки в нашем цветовом пространстве, которые лежат вдоль красной (R) кромки куба. Я буду использовать те же значения, которые вы показали для этих точек, таким образом:

Q1 = [66, 51, 77]
Q2 = [55, 66, 77]

Это значения, между которыми мы будем интерполировать, по сути, выходные данные нашего отображения, но нам также нужно знать, где эти точки лежат во входном RGB-пространстве. Итак, предположим, что эти координаты, основанные на координатах куба, из которого они пришли, являются:

P1 = [0, 0, 0]
P2 = [1, 0, 0]

Это юнит-куб в 3-м, как я его написал, поэтому остальные точки будут лежать на

P3 = [0, 1, 0]
P4 = [1, 1, 0]
P5 = [0, 0, 1]
P6 = [1, 0, 1]
P7 = [0, 1, 1]
P8 = [1, 1, 1]

Конечно, любой общий куб тоже работает, и нет никаких оснований считать его настоящим кубом. Любая 3-я правильная, прямоугольная 4-сторонняя призма будет работать и здесь. Вы всегда можете превратить вещи в единичный куб.

Теперь предположим, что мы хотим интерполировать вдоль этого края куба между P1 и P2 в область, определенную Q1 и Q2? Укажите точку вдоль этого края. Вы можете видеть, что только R изменяется вдоль этого края между этими точками, поэтому мы заботимся только о значении R в точке, в которую мы интерполируем. Думайте об этом в процентах от расстояния по краю. Интерполяция - это просто средневзвешенное значение двух конечных точек, линейная комбинация. Таким образом, для точки с красным значением r по краю от 0 до 1 в красном канале наша интерполяция будет

Q(r) = Q1*(1-r) + Q2*r

Как вы можете видеть, когда r равно 1/2, то есть на полпути вдоль края, наш интерполант уменьшится до

Q(1/2,0,0) = (Q1 + Q2)/2

Логически, значение средней точки будет средним из двух конечных точек. Вы выполняете интерполяцию для КАЖДОГО выходного канала независимо.

Q(1/2,0,0) = ([66, 51, 77] + [55, 66, 77])/2 = [60.5, 58.5, 77]

Это работает для восстановления конечных точек? Конечно, это так. Когда r = 0 или r = 1, вы можете видеть, что он возвращает точно соответствующий Q1 или Q2.

Опять же, вы выполняете эту интерполяцию вдоль каждого из четырех красных ребер для трилинейного интерполятора. Затем вы делаете еще ДВА интерполяции, возможно, вдоль зеленых краев четырех результатов, которые мы получили выше. Наконец, вы делаете еще одну интерполяцию вдоль синего края, чтобы получить трилинейный интерполант. Опять же, не имеет значения, в каком порядке вы выбираете оси интерполяции. Результат будет математически таким же.

Если бы вы остановились на билинейной интерполяции, то есть три таких линейных интерполяции. Да, это правда, что билинейный интерполант или трилинейный интерполант также могут быть выполнены как взвешенная комбинация всех 4 (или 8) углов прямоугольника (или куба). Это можно оставить на будущее.

9 голосов
/ 12 января 2012

Вы интерполируете значения независимо, выполняя каждое вычисление для R, G и B. Например, интерполяция на полпути между (200,50,10) и (0,0,0) выходами (100,25,5).

1 голос
/ 10 января 2017
/*
  resize an image using bilinear interpolation
*/
void bilerp(unsigned char *dest, int dwidth, int dheight, unsigned char *src, int swidth, int sheight)
{
  float a, b;
  float red, green, blue, alpha;
  float dx, dy;
  float rx, ry;
  int x, y;
  int index0, index1, index2, index3;

  dx = ((float) swidth)/dwidth;
  dy = ((float) sheight)/dheight;
  for(y=0, ry = 0;y<dheight-1;y++, ry += dy)
  {
    b = ry - (int) ry;
    for(x=0, rx = 0;x<dwidth-1;x++, rx += dx)
    {
      a = rx - (int) rx;
      index0 = (int)ry * swidth + (int) rx;
      index1 = index0 + 1;
      index2 = index0 + swidth;     
      index3 = index0 + swidth + 1;

      red = src[index0*4] * (1.0f-a)*(1.0f-b);
      green = src[index0*4+1] * (1.0f-a)*(1.0f-b);
      blue = src[index0*4+2] * (1.0f-a)*(1.0f-b);
      alpha = src[index0*4+3] * (1.0f-a)*(1.0f-b);
      red += src[index1*4] * (a)*(1.0f-b);
      green += src[index1*4+1] * (a)*(1.0f-b);
      blue += src[index1*4+2] * (a)*(1.0f-b);
      alpha += src[index1*4+3] * (a)*(1.0f-b);
      red += src[index2*4] * (1.0f-a)*(b);
      green += src[index2*4+1] * (1.0f-a)*(b);
      blue += src[index2*4+2] * (1.0f-a)*(b);
      alpha += src[index2*4+3] * (1.0f-a)*(b);
      red += src[index3*4] * (a)*(b);
      green += src[index3*4+1] * (a)*(b);
      blue += src[index3*4+2] * (a)*(b);
      alpha += src[index3*4+3] * (a)*(b);

      red = red < 0 ? 0 : red > 255 ? 255 : red;
      green = green < 0 ? 0 : green > 255 ? 255 : green;
      blue = blue < 0 ? 0 : blue > 255 ? 255 : blue;
      alpha = alpha < 0 ? 0 : alpha > 255 ? 255 : alpha;

      dest[(y*dwidth+x)*4] = (unsigned char) red;
      dest[(y*dwidth+x)*4+1] = (unsigned char) green;
      dest[(y*dwidth+x)*4+2] = (unsigned char) blue;
      dest[(y*dwidth+x)*4+3] = (unsigned char) alpha;
    }
    index0 = (int)ry * swidth + (int) rx;
    index1 = index0;
    index2 = index0 + swidth;     
    index3 = index0 + swidth;   

    red = src[index0*4] * (1.0f-a)*(1.0f-b);
    green = src[index0*4+1] * (1.0f-a)*(1.0f-b);
    blue = src[index0*4+2] * (1.0f-a)*(1.0f-b);
    alpha = src[index0*4+3] * (1.0f-a)*(1.0f-b);
    red += src[index1*4] * (a)*(1.0f-b);
    green += src[index1*4+1] * (a)*(1.0f-b);
    blue += src[index1*4+2] * (a)*(1.0f-b);
    alpha += src[index1*4+3] * (a)*(1.0f-b);
    red += src[index2*4] * (1.0f-a)*(b);
    green += src[index2*4+1] * (1.0f-a)*(b);
    blue += src[index2*4+2] * (1.0f-a)*(b);
    alpha += src[index2*4+3] * (1.0f-a)*(b);
    red += src[index3*4] * (a)*(b);
    green += src[index3*4+1] * (a)*(b);
    blue += src[index3*4+2] * (a)*(b);
    alpha += src[index3*4+3] * (a)*(b);

    red = red < 0 ? 0 : red > 255 ? 255 : red;
    green = green < 0 ? 0 : green > 255 ? 255 : green;
    blue = blue < 0 ? 0 : blue > 255 ? 255 : blue;
    alpha = alpha < 0 ? 0 : alpha > 255 ? 255 : alpha;

    dest[(y*dwidth+x)*4] = (unsigned char) red;
    dest[(y*dwidth+x)*4+1] = (unsigned char) green;
    dest[(y*dwidth+x)*4+2] = (unsigned char) blue;
    dest[(y*dwidth+x)*4+3] = (unsigned char) alpha;
  }
  index0 = (int)ry * swidth + (int) rx;
  index1 = index0;
  index2 = index0 + swidth;     
  index3 = index0 + swidth;   

  for(x=0, rx = 0;x<dwidth-1;x++, rx += dx)
  {
    a = rx - (int) rx;
    index0 = (int)ry * swidth + (int) rx;
    index1 = index0 + 1;
    index2 = index0;     
    index3 = index0;

    red = src[index0*4] * (1.0f-a)*(1.0f-b);
    green = src[index0*4+1] * (1.0f-a)*(1.0f-b);
    blue = src[index0*4+2] * (1.0f-a)*(1.0f-b);
    alpha = src[index0*4+3] * (1.0f-a)*(1.0f-b);
    red += src[index1*4] * (a)*(1.0f-b);
    green += src[index1*4+1] * (a)*(1.0f-b);
    blue += src[index1*4+2] * (a)*(1.0f-b);
    alpha += src[index1*4+3] * (a)*(1.0f-b);
    red += src[index2*4] * (1.0f-a)*(b);
    green += src[index2*4+1] * (1.0f-a)*(b);
    blue += src[index2*4+2] * (1.0f-a)*(b);
    alpha += src[index2*4+3] * (1.0f-a)*(b);
    red += src[index3*4] * (a)*(b);
    green += src[index3*4+1] * (a)*(b);
    blue += src[index3*4+2] * (a)*(b);
    alpha += src[index3*4+3] * (a)*(b);

    red = red < 0 ? 0 : red > 255 ? 255 : red;
    green = green < 0 ? 0 : green > 255 ? 255 : green;
    blue = blue < 0 ? 0 : blue > 255 ? 255 : blue;
    alpha = alpha < 0 ? 0 : alpha > 255 ? 255 : alpha;

    dest[(y*dwidth+x)*4] = (unsigned char) red;
    dest[(y*dwidth+x)*4+1] = (unsigned char) green;
    dest[(y*dwidth+x)*4+2] = (unsigned char) blue;
    dest[(y*dwidth+x)*4+3] = (unsigned char) alpha;
  }

   dest[(y*dwidth+x)*4] = src[((sheight-1)*swidth+swidth-1)*4];
   dest[(y*dwidth+x)*4+1] = src[((sheight-1)*swidth+swidth-1)*4+1];
   dest[(y*dwidth+x)*4+2] = src[((sheight-1)*swidth+swidth-1)*4+2];
   dest[(y*dwidth+x)*4+3] = src[((sheight-1)*swidth+swidth-1)*4+3];
}  

Код поддерживается здесь

https://github.com/MalcolmMcLean/babyxrc/blob/master/src/resize.c

...