HTML5 холст контраст изображения


Я пишу программу обработки изображений, которая применяет эффекты через обработку пикселей холста HTML5. Я достиг порогового значения, винтажа и цветовых манипуляций с пикселями, но невероятно, что я не могу изменить контраст изображения! Я пробовал несколько решений, но я всегда получаю слишком много яркости в изображении и меньше контрастного эффекта , и я не планирую использовать какие-либо библиотеки Javascript, так как я пытаюсь достичь этих эффектов изначально.

В основной код манипуляции пикселами:

var data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
 //Note: data[i], data[i+1], data[i+2] represent RGB respectively
data[i] = data[i];
data[i+1] = data[i+1];
data[i+2] = data[i+2];
}

Пример манипуляции пикселями

Значения находятся в режиме RGB, что означает, что данные [i] имеют красный цвет. Таким образом, если data[i] = data[i] * 2; яркость будет увеличена в два раза для красного канала этого пикселя. Пример:

var data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
 //Note: data[i], data[i+1], data[i+2] represent RGB respectively
 //Increases brightness of RGB channel by 2
data[i] = data[i]*2;
data[i+1] = data[i+1]*2;
data[i+2] = data[i+2]*2;
}

*примечание: Я не прошу вас, ребята, чтобы вы завершили код! Это было бы просто одолжением! Я прошу алгоритм (даже псевдокод), который показывает, как контраст в манипуляции пикселями возможен! Я бы так и сделал. рад, если кто-то может предоставить хороший алгоритм для контраста изображения в HTML5 canvas.

7 18

7 ответов:

Более быстрый вариант (основанный на подходе Эшера ):

function contrastImage(imgData, contrast){  //input range [-100..100]
    var d = imgData.data;
    contrast = (contrast/100) + 1;  //convert to decimal & shift range: [0..2]
    var intercept = 128 * (1 - contrast);
    for(var i=0;i<d.length;i+=4){   //r,g,b,a
        d[i] = d[i]*contrast + intercept;
        d[i+1] = d[i+1]*contrast + intercept;
        d[i+2] = d[i+2]*contrast + intercept;
    }
    return imgData;
}
Вывод аналогичен нижеприведенному; эта версия математически та же, но работает гораздо быстрее.

Оригинальный ответ

Вот упрощенная версия с объяснением подхода , уже обсуждавшегося (который был основан на этой статье):

function contrastImage(imageData, contrast) {  // contrast as an integer percent  
    var data = imageData.data;  // original array modified, but canvas not updated
    contrast *= 2.55; // or *= 255 / 100; scale integer percent to full range
    var factor = (255 + contrast) / (255.01 - contrast);  //add .1 to avoid /0 error

    for(var i=0;i<data.length;i+=4)  //pixel values in 4-byte blocks (r,g,b,a)
    {
        data[i] = factor * (data[i] - 128) + 128;     //r value
        data[i+1] = factor * (data[i+1] - 128) + 128; //g value
        data[i+2] = factor * (data[i+2] - 128) + 128; //b value

    }
    return imageData;  //optional (e.g. for filter function chaining)
}

Примечания

  1. Я решил использовать contrast диапазон +/- 100 вместо оригинал +/- 255. Процентное значение кажется более интуитивным для пользователей или программистов, которые не понимают основных понятий. Кроме того, мое использование всегда привязано к элементам управления пользовательского интерфейса; диапазон от -100% до +100% позволяет мне помечать и связывать значение элемента управления напрямую, а не настраивать или объяснять его.

  2. Этот алгоритм не включает проверку диапазона, хотя вычисленные значения могут значительно превышать допустимый диапазон - это связано с тем, что массив, лежащий в основе Объект ImageData - это Uint8ClampedArray. как объясняет MSDN , с помощью Uint8ClampedArray проверка диапазона обрабатывается для вас:

" если вы указали значение, выходящее за пределы диапазона [0,255], то вместо него будет установлено значение 0 или 255."

Использование

Обратите внимание, что хотя лежащая в основе формула достаточно симметрична (допускает цикличность), данные теряются при высоких уровнях фильтрации, поскольку пиксели допускают только целочисленные значения. Например, к тому времени, когда вы де-насыщаете изображение до экстремальных уровней (>95% или около того), все пиксели в основном имеют однородный средний серый цвет (в пределах нескольких цифр от среднего возможного значения 128). Повторное увеличение контраста приводит к выравниванию цветовой гаммы.

Кроме того, порядок операций важен при применении множественных регулировок контраста-насыщенные значения "выдуваются" (превышают зажатое максимальное значение 255) быстро, то есть сильно насыщают, а затем де-насыщение приведет к более темному изображению в целом. Де-насыщение и тогда насыщение, однако, не имеет такой большой потери данных, потому что значения подсветки и тени становятся приглушенными, а не обрезанными (см. объяснение ниже).

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

Примеры контраста мандрилла

Пример Кода:

function contrastImage(imageData, contrast) {  // contrast input as percent; range [-1..1]
    var data = imageData.data;  // Note: original dataset modified directly!
    contrast *= 255;
    var factor = (contrast + 255) / (255.01 - contrast);  //add .1 to avoid /0 error.

    for(var i=0;i<data.length;i+=4)
    {
        data[i] = factor * (data[i] - 128) + 128;
        data[i+1] = factor * (data[i+1] - 128) + 128;
        data[i+2] = factor * (data[i+2] - 128) + 128;
    }
    return imageData;  //optional (e.g. for filter function chaining)
}

$(document).ready(function(){
  var ctxOrigMinus100 = document.getElementById('canvOrigMinus100').getContext("2d");
  var ctxOrigMinus50 = document.getElementById('canvOrigMinus50').getContext("2d");
  var ctxOrig = document.getElementById('canvOrig').getContext("2d");
  var ctxOrigPlus50 = document.getElementById('canvOrigPlus50').getContext("2d");
  var ctxOrigPlus100 = document.getElementById('canvOrigPlus100').getContext("2d");
  
  var ctxRoundMinus90 = document.getElementById('canvRoundMinus90').getContext("2d");
  var ctxRoundMinus50 = document.getElementById('canvRoundMinus50').getContext("2d");
  var ctxRound0 = document.getElementById('canvRound0').getContext("2d");
  var ctxRoundPlus50 = document.getElementById('canvRoundPlus50').getContext("2d");
  var ctxRoundPlus90 = document.getElementById('canvRoundPlus90').getContext("2d");
  
  
  var img = new Image();
  img.onload = function() {
    //draw orig
    ctxOrig.drawImage(img, 0, 0, img.width, img.height, 0, 0, 100, 100); //100 = canvas width, height
    
    //reduce contrast
    var origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, -.98);
    ctxOrigMinus100.putImageData(origBits, 0, 0);
    
    var origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, -.5);
    ctxOrigMinus50.putImageData(origBits, 0, 0);
    
    // add contrast
    var origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, .5);
    ctxOrigPlus50.putImageData(origBits, 0, 0);
    
    var origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, .98);
    ctxOrigPlus100.putImageData(origBits, 0, 0);
    
    
    //round-trip, de-saturate first
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, -.98);
    contrastImage(origBits, .98);
    ctxRoundMinus90.putImageData(origBits, 0, 0);
    
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, -.5);
    contrastImage(origBits, .5);
    ctxRoundMinus50.putImageData(origBits, 0, 0);
    
    //do nothing 100 times
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    for(i=0;i<100;i++){
      contrastImage(origBits, 0);
    }
    ctxRound0.putImageData(origBits, 0, 0);
    
    //round-trip, saturate first
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, .5);
    contrastImage(origBits, -.5);
    ctxRoundPlus50.putImageData(origBits, 0, 0);
    
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, .98);
    contrastImage(origBits, -.98);
    ctxRoundPlus90.putImageData(origBits, 0, 0);
  };
  
  img.src = "data:image/png;base64,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";
  
});
canvas {width: 100px; height: 100px}
div {text-align:center; width:120px; float:left}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div>
  <canvas id="canvOrigMinus100" width="100" height="100"></canvas>
  -98%
</div>

<div>
  <canvas id="canvOrigMinus50" width="100" height="100"></canvas>
  -50%
</div>

<div>
  <canvas id="canvOrig" width="100" height="100"></canvas>
  Original
</div>

<div>
  <canvas id="canvOrigPlus50" width="100" height="100"></canvas>
  +50%
</div>

<div>
  <canvas id="canvOrigPlus100" width="100" height="100"></canvas>
  +98%
</div>

  <hr/>

<div style="clear:left">
  <canvas id="canvRoundMinus90" width="100" height="100"></canvas>
  Round-trip <br/> (-98%, +98%)
</div>

<div>
  <canvas id="canvRoundMinus50" width="100" height="100"></canvas>
  Round-trip <br/> (-50%, +50%)
</div>

<div>
  <canvas id="canvRound0" width="100" height="100"></canvas>
  Round-trip <br/> (0% 100x)
</div>

<div>
  <canvas id="canvRoundPlus50" width="100" height="100"></canvas>
  Round-trip <br/> (+50%, -50%)
</div>

<div>
  <canvas id="canvRoundPlus90" width="100" height="100"></canvas>
  Round-trip <br/> (+98%, -98%)
</div>

Объяснение

(отказ от ответственности-я не специалист по изображениям и не математик. Я пытаюсь дать разумное объяснение с минимальными техническими деталями. Некоторые махания руками ниже, например 255=256, чтобы избежать проблем с индексацией, и 127,5=128, для упрощения чисел.)

Так как для данного пикселя возможное число ненулевые значения для цветового канала-255 , "неконтрастное", среднее значение пикселя-128 (или 127, или 127,5, если хотите поспорить, но разница незначительна). Для целей этого объяснения величина"контраста" - это расстояние от текущего значения до среднего значения (128) . Настройка контраста означает увеличение или уменьшение разницы между текущим значением и средним значением.

Задача, которую решает алгоритм, состоит в том, чтобы:

  1. выбрал постоянный коэффициент для регулировки контраста по
  2. для каждого цветового канала каждого пикселя масштабируйте "контраст" (расстояние от среднего) на этот постоянный коэффициент

Или, как намекается в CSS spec, просто выбирая наклон и перехват линии:

<feFuncR type="linear" slope="[amount]" intercept="-(0.5 * [amount]) + 0.5"/>

Обратите внимание на термин type='linear'; мы выполняем линейную настройку контраста в цветовом пространстве RGB , в отличие от квадратичной функции масштабирования, регулировка на основе яркости илисогласование гистограммы .

Если вы помните из класса геометрии, формула для линии y=mx+b. y - это конечное значение, которое мы ищем, наклон m - это контраст (или factor), x является начальным значением пикселя, а b - перехватом оси y (x=0), которая сдвигает линию по вертикали. Напомним также, что поскольку y-перехват не находится в начале координат (0,0), то формула также может быть представлена в виде y=m(x-a)+b, где именно a - смещение x, смещающее линию по горизонтали.

Формула для наклона линии

Для наших целей этот график представляет входное значение (ось x) и результат (ось y). Мы уже знаем, что b, y-Перехват (для m=0, без контраста) должен быть 128 (что мы можем проверить против 0.5 из спецификации - 0.5 * полный диапазон 256 = 128). x-это наше исходное значение, поэтому все, что нам нужно, это вычислить наклон m и смещение x a.

Во-первых, склон m - это "подъем над пробегом", или (y2-y1)/(x2-x1) - поэтому нам нужны 2 точки, которые, как известно, находятся на нужной линии. Нахождение этих точек требует объединения нескольких вещей:

  • наша функция принимает форму графа перехвата линий
  • y-перехват находится на b = 128 - независимо от наклона (контраста).
  • максимальное ожидаемое значение ' y ' равно 255, а минимальное-0
  • диапазон возможных значений ' x ' составляет 256
  • нейтральное значение всегда должно оставаться нейтральным: 128 => 128 независимо от наклона
  • регулировка контраста 0 не должна приводить к изменению между входом и выходом; то есть наклон 1:1.
Принимая все это вместе, мы можем сделать вывод, что независимо от применяемого контраста (наклона) наша результирующая линия будет центрирована на (и вращаться вокруг) 128,128. Поскольку наш y-перехват ненулевой, x-перехват также ненулевой; мы знаем, что X-диапазон имеет ширину 256 и центрирован в середине, поэтому он должен быть смещен на половину возможного диапазон: 256/2 = 128.

Функция контраста наклоны

Итак, теперь для y=m(x-a)+b мы знаем все, кроме m. Вспомним еще два важных момента из класса геометрии:
  • линии имеют одинаковый наклон, даже если их расположение меняется; то есть m остается неизменным независимо от значений a и b.
  • наклон прямой можно найти, используя любые 2 точки На линии

Чтобы упростить обсуждение наклона, давайте переместим начало координат к x-перехвату (-128) и игнорировать a и b на мгновение. Наша исходная линия теперь повернется через (0,0), и мы знаем, что вторая точка на линии лежит далеко от полного диапазона как x (вход), так и y (выход) в (255,255).

Мы позволим новой линии развернуться на (0,0), поэтому мы можем использовать ее как одну из точек на новой линии, которая будет следовать за нашим конечным контрастным наклоном m. Вторая точка может быть определена путем перемещения текущего конца в (255,255) на некоторую величину; так как мы ограниченная одним входом (contrast) и использующая линейную функцию, эта вторая точка будет одинаково перемещаться в направлениях x и y на нашем графике.

Регулировка наклона контраста

Координаты (x,y) 4 возможных новых точек будут 255 +/- contrast. Поскольку увеличение или уменьшение x и y удержит нас на исходной линии 1: 1, давайте просто посмотрим на +x, -y и -x, +y, как показано.

Более крутая линия (-x, +y) связана с положительным contrast регулированием; это (x, y) координаты такие (255 - contrast,255 + contrast). Координаты более мелкой линии (отрицательные contrast) находятся таким же образом. Обратите внимание, что самое большое значимое значение contrast будет 255 - самое большее, что начальная точка (255,255) может быть переведена до получения вертикальной линии (полный контраст, все черное или белое) или горизонтальной линии (нет контраста, все серое).

Итак, теперь у нас есть координаты двух точек на нашей новой прямой - (0,0) и (255 - contrast,255 + contrast). Мы подключаем это к уравнение наклона, а затем подключите его к уравнению полной линии, используя все части из предыдущего:

y = m(x-a) + b

m = (y2-y1)/(x2-x1) =>
((255 + contrast) - 0)/((255 - contrast) - 0) =>
(255 + contrast)/(255 - contrast)

a = 128
b = 128

y = (255 + contrast)/(255 - contrast) * (x - 128) + 128 QED

Математик заметит, что результирующее m или factor является скалярным (безразмерным) значением; вы можете использовать любой диапазон, который вы хотите для contrast, если он соответствует константе (255) в factor расчет. Например, диапазон contrast +/-100 и factor = (100 + contrast)/(100.01 - contrast), который я действительно использую, чтобы исключить шаг масштабирования до 255; я просто оставил 255 в коде вверху, чтобы упростить объяснение.


Примечание о "магии" 259

Исходная статья использует "магию" 259, хотя автор признает, что не помнит, почему:

" я не могу вспомнить, рассчитал ли я это сам, или я прочитал это в книге или онлайн.".

259 действительно должно быть 255 или, возможно, 256-число возможных ненулевых значений для каждого канала каждого пикселя. Обратите внимание, что в исходном вычислении factor 259/255 отменяет - технически 1.01, но конечные значения являются целыми числами, поэтому 1 для всех практических целей. Так что этот внешний термин можно отбросить. На самом деле использование 255 для константы в знаменателе, однако, вводит возможность деления на нулевую ошибку в Формуле; корректировка немного большее значение (скажем, 259) позволяет избежать этой проблемы, не внося существенных ошибок в результаты. Я решил использовать 255.01 вместо этого, так как ошибка ниже, и это (надеюсь) кажется менее "волшебным" новичку.

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

После попытки ответа Шахрияра Саффаршарга, он вел себя не так, как должен был бы вести себя контраст. Я наконец-то наткнулся на этот алгоритм, и он работает как заклинание!

Для получения дополнительной информации об алгоритме прочитайтеЭту статью и раздел комментариев.

function contrastImage(imageData, contrast) {

    var data = imageData.data;
    var factor = (259 * (contrast + 255)) / (255 * (259 - contrast));

    for(var i=0;i<data.length;i+=4)
    {
        data[i] = factor * (data[i] - 128) + 128;
        data[i+1] = factor * (data[i+1] - 128) + 128;
        data[i+2] = factor * (data[i+2] - 128) + 128;
    }
    return imageData;
}

Использование:

var newImageData = contrastImage(imageData, 30);

Надеюсь, что это будет экономия времени для кого-то. Ура!

Я выяснил, что вы должны использовать эффект, отделяя темные и светлые цвета или технически все, что меньше 127 (среднее значение R+G+B / 3) в шкале rgb, является черным, а больше 127-белым, поэтому по уровню контраста вы минус значение, скажем, 10 контраста от черных и добавляете то же значение к белым!

Вот пример:: У меня есть два пикселя с цветами RGB, [105,40,200] | [255,200,150] Так что я знаю, что для моего первого пикселя 105 + 40 + 200 = 345, 345/3 = Сто пятнадцать и 115 меньше моей половины 255, которая равна 127, поэтому я считаю пиксель ближе к [0,0,0], поэтому если я хочу минус 10 контраста, то я забираю 10 от каждого цвета в среднем Таким образом, я должен разделить значение каждого цвета на среднее значение итога, которое было 115 для этого случая, и умножить его на мой контраст и вычеркнуть конечное значение из этого конкретного цвета:

Например, я возьму 105 (красный) из моего пикселя, поэтому я делю его на общее среднее значение RGB. что составляет 115 и умножается на мой контраст значение 10, (105/115)*10, что дает вам что-то около 9 (вы должны округлить его!) а затем уберите эти 9 от 105, чтобы цвет стал 96, таким образом, мой красный после 10 контрастов на темном пикселе.

Итак, если я продолжу, значения моего пикселя станут [96,37,183]! (Примечание: шкала контраста зависит от вас! но мой в конце концов вы должны преобразовать его в какой-то масштаб, например, от 1 до 255)

Для более светлых пикселей я также делаю то же самое, но вместо вычитания значения контраста я добавляю его! и если вы достигнете предела 255 или 0, то вы прекратите сложение и вычитание для этого конкретного цвета! поэтому мой второй пиксель, который является более светлым, становится [255,210,157]

По мере того как вы добавляете больше контраста, он осветляет более светлые цвета и затемняет более темные и, следовательно, добавляет контраст к вашей картине!

Вот пример кода Javascript (я его еще не пробовал):

var data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
 var contrast = 10;
 var average = Math.round( ( data[i] + data[i+1] + data[i+2] ) / 3 );
  if (average > 127){
    data[i] += ( data[i]/average ) * contrast;
    data[i+1] += ( data[i+1]/average ) * contrast;
    data[i+2] += ( data[i+2]/average ) * contrast;
  }else{
    data[i] -= ( data[i]/average ) * contrast;
    data[i+1] -= ( data[i+1]/average ) * contrast;
    data[i+2] -= ( data[i+2]/average ) * contrast;
  }
}

Вы можете взглянуть на документы OpenCV, чтобы увидеть, как вы можете сделать это: регулировка яркости и контрастности.

Затем идет демонстрационный код:

 double alpha; // Simple contrast control: value [1.0-3.0]
 int beta;     // Simple brightness control: value [0-100]

 for( int y = 0; y < image.rows; y++ )
 { 
      for( int x = 0; x < image.cols; x++ )
      { 
          for( int c = 0; c < 3; c++ )
          {
              new_image.at<Vec3b>(y,x)[c] = saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta );
          }
      }
 }

Который, как я полагаю, вы способны перевести на javascript.

С помощью vintaging я предполагаю, что вы пытаетесь применить LUTS..В последнее время я пытаюсь добавить цветовые решения к окнам холста. Если вы действительно хотите применить "LUTS" к окну canvas, я считаю, что вам нужно на самом деле сопоставить массив, который imageData возвращает в массив RGB LUT.

(от иллюзии света) В качестве примера начало 1D LUT может выглядеть примерно так: Примечание: строго говоря, это 3x 1D LUTs, так как каждый цвет (R, G, B) является 1D LUT

R, G, B 
3, 0, 0 
5, 2, 1 
7, 5, 3 
9, 9, 9

Что означает, что:

For an input value of 0 for R, G, and B, the output is R=3, G=0, B=0 
For an input value of 1 for R, G, and B, the output is R=5, G=2, B=1 
For an input value of 2 for R, G, and B, the output is R=7, G=5, B=3 
For an input value of 3 for R, G, and B, the output is R=9, G=9, B=9

Что странно, но вы видите, что для заданного значения R, G или B на входе есть заданное значение R, G и B на выходе.

Таким образом, если бы пиксель имел входное значение 3, 1, 0 для RGB, выходной пиксель был бы 9, 2, 0.

Во время этого я также понял после игры с imageData, что он возвращает Uint8Array и что значения в этом массиве являются десятичными. Большинство 3D-лут являются шестнадцатеричными. Таким образом, вы сначала должны сделать некоторый тип преобразования hex в dec на всем массиве, прежде чем все это сопоставление.

Эта реализация javascript соответствует определению SVG / CSS3 "контраст" (и следующий код будет отображать ваше изображение на холсте идентично):

/*contrast filter function*/
//See definition at https://drafts.fxtf.org/filters/#contrastEquivalent
//pixels come from your getImageData() function call on your canvas image
contrast = function(pixels, value){
    var d = pixels.data;
    var intercept = 255*(-value/2 + 0.5);
    for(var i=0;i<d.length;i+=4){
        d[i] = d[i]*value + intercept;
        d[i+1] = d[i+1]*value + intercept;
        d[i+2] = d[i+2]*value + intercept;
        //implement clamping in a separate function if using in production
        if(d[i] > 255) d[i] = 255;
        if(d[i+1] > 255) d[i+1] = 255;
        if(d[i+2] > 255) d[i+2] = 255;
        if(d[i] < 0) d[i] = 0;
        if(d[i+1] < 0) d[i+1] = 0;
        if(d[i+2] < 0) d[i+2] = 0;
    }
    return pixels;
}

Это формула, которую вы ищете ...

var data = imageData.data;
if (contrast > 0) {

    for(var i = 0; i < data.length; i += 4) {
        data[i] += (255 - data[i]) * contrast / 255;            // red
        data[i + 1] += (255 - data[i + 1]) * contrast / 255;    // green
        data[i + 2] += (255 - data[i + 2]) * contrast / 255;    // blue
    }

} else if (contrast < 0) {
    for (var i = 0; i < data.length; i += 4) {
        data[i] += data[i] * (contrast) / 255;                  // red
        data[i + 1] += data[i + 1] * (contrast) / 255;          // green
        data[i + 2] += data[i + 2] * (contrast) / 255;          // blue
    }
}

Надеюсь, это поможет!