web-dev-qa-db-ja.com

HTML5キャンバス画像のコントラスト

HTML5キャンバスピクセル処理を通じて効果を適用する画像処理プログラムを書いています。しきい値処理、Vintaging、およびColorGradientピクセル操作を達成しましたが、信じられないほど画像のコントラストを変更できません。私は複数の解決策を試しましたが、画像の明るさが多すぎてコントラスト効果が少なくなりますこれらの効果をネイティブに実現しようとしているため、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モードです。つまり、data [i]は赤色です。したがって、data [i] = data [i] * 2の場合;そのピクセルの赤チャンネルの明るさは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キャンバスの画像コントラストに優れたアルゴリズムを提供できれば幸いです。

より高速なオプション( エッシャーのアプローチ に基づく)は次のとおりです。

_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. 元の_+/- 100_の代わりに_+/- 255_のcontrast範囲を使用することを選択しました。パーセンテージ値は、基本的な概念を理解していないユーザーやプログラマーにとってはより直感的に思えます。また、私の使用法は常にUIコントロールに関連付けられています。 -100%から+ 100%の範囲では、コントロール値を調整または説明する代わりに、直接ラベルを付けてバインドできます。

  2. 計算値は許容範囲をはるかに超える可能性があります -これは、ImageDataオブジェクトの基になる配列が_Uint8ClampedArray_であるためですが、このアルゴリズムには範囲チェックは含まれていません。 MSDNで説明されているように 、_Uint8ClampedArray_を使用すると、範囲チェックが処理されます。

「[0,255]の範囲外の値を指定した場合は、代わりに0または255が設定されます。」

使用法

基になる式はかなり対称的ですが(ラウンドトリップが可能)、ピクセルは整数値しか許可しないため、高レベルのフィルタリングではデータが失われることに注意してください。たとえば、画像の彩度を極端なレベル(> 95%程度)に下げるまでに、すべてのピクセルは基本的に均一なミディアムグレーになります(平均値128の数桁以内)。コントラストを再び上げると、色の範囲がフラットになります。

また、複数のコントラスト調整を適用する場合は、操作の順序が重要です。飽和値はすぐに「吹き飛ばされ」(クランプされた最大値の255を超える)、つまり、高度に飽和してから非飽和にすると、画像全体が暗くなります。ただし、ハイライト値とシャドウ値がクリップされるのではなくミュートされるため、彩度を下げてから彩度を上げることによるデータ損失はそれほど多くありません(以下の説明を参照)。

一般的に、複数のフィルターを適用する場合は、少なくとも画質に関しては、前の変更を元に戻そうとするのではなく、元のデータで各操作を開始し、各調整を順番に再適用することをお勧めします。パフォーマンスの速度やその他の要求は、状況ごとに異なる場合があります。

Mandrill contrast examples

コード例:

_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仕様 で示唆されているように、線の傾きと切片を選択するだけです。

<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オフセットをシフトします。水平に並べます。

Formula for slope of a line

ここでは、このグラフは入力値(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)だけオフセットする必要があります。

Contrast function slopes

したがって、y=m(x-a)+bについては、m以外のすべてがわかっています。ジオメトリクラスからさらに2つの重要なポイントを思い出してください。

  • 線の位置が変わっても、線の傾きは同じです。つまり、mは、abの値に関係なく、同じままです。
  • 線の傾きは、線上の任意の2点を使用して見つけることができます

傾きの説明を簡単にするために、座標の原点をx切片(-128)に移動し、abをしばらく無視してみましょう。元の線は(0,0)を通過し、線の2番目の点が(255,255)のx(入力)とy(出力)の両方の全範囲から離れていることがわかります。

新しい線を(0,0)でピボットさせるので、それを、最終的なコントラスト勾配mに従う新しい線上の点の1つとして使用できます。 2番目のポイントは、現在の端を(255,255)である程度移動することによって決定できます。単一の入力(contrast)に制限されており、線形関数を使用しているため、この2番目のポイントはグラフのx方向とy方向に等しく移動します。

Adjusting the contrast slope

4つの可能な新しいポイントの(x、y)座標は_255 +/- contrast_になります。 xとyの両方を増減すると、元の1:1の行が維持されるため、図のように_+x, -y_と_-x, +y_を見てみましょう。

急な線(-x、+ y)は、正のcontrast調整に関連付けられています。 (x、y)座標は(_255 - contrast_、_255 + contrast_)です。浅い線(負のcontrast)の座標も同じ方法で見つかります。 contrastの最大の意味のある値は255になります-垂直線(完全なコントラスト、すべて黒または白)または水平線になる前に(255,255)の初期点を変換できる最大値に注意してください線(コントラストなし、すべて灰色)。

これで、新しい線上の2つの点の座標((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に任意の範囲を使用できます。たとえば、_+/-100_とfactor = (100 + contrast)/(100.01 - contrast)contrast範囲は、255にスケーリングするステップを排除するために実際に使用しました。説明を簡単にするために、上部のコードに_255_を残しました。


「魔法」についての注意259

ソース記事 は「魔法」259を使用していますが、著者は理由を覚えていないことを認めています。

「これを自分で計算したのか、本やオンラインで読んだのか思い出せません。」.

259は実際には255またはおそらく256である必要があります-各ピクセルの各チャネルで可能なゼロ以外の値の数。元のfactor計算では、259/255はキャンセルされます-技術的には1.01ですが、最終値は整数であるため、すべての実用的な目的で1になります。したがって、この外側の項は破棄できます。ただし、実際には分母の定数に255を使用すると、数式にゼロ除算エラーが発生する可能性があります。わずかに大きい値(たとえば、259)に調整すると、結果に重大なエラーを発生させることなく、この問題を回避できます。エラーが少なく、(うまくいけば)初心者には「魔法」が少ないように見えるので、代わりに255.01を使用することにしました。

私が知る限り、 使用するものに大きな違いはありません -正のコントラストの増加が少ない低コントラスト値の狭帯域でのわずかな対称差を除いて、同じ値が得られます。両方のバージョンを繰り返しラウンドトリップして元のデータと比較したいのですが、この回答にはすでに時間がかかりすぎていました。 :)

21
brichins

Schahriar SaffarSharghによる回答を試した後、コントラストが動作するように動作していませんでした。私はついにこのアルゴリズムに出くわしました、そしてそれは魅力のように機能します!

アルゴリズムの詳細については、 この記事 とそのコメントセクションをお読みください。

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);

うまくいけば、これは誰かにとって時間の節約になるでしょう。乾杯!

26
Brian

暗闇と明かりを分離するか、技術的にはrgbスケールで127(R + G + B/3の平均)未満のものは黒で、127を超えるものは白であるため、この効果を使用する必要があることがわかりました。コントラストのレベルによって、値を引いた値を黒から10コントラストと言い、同じ値を白に追加します。

次に例を示します。RGBカラーの2つのピクセルがあります[105,40,200] | [255,200,150]つまり、最初のピクセルの場合、105 + 40 + 200 = 345、345/3 = 115、115は255の半分である127よりも小さいので、[0,0,0]に近いピクセルと見なします。したがって、10のコントラストをマイナスしたい場合は、平均で各色から10を取ります。したがって、各色の値を、この場合は115であった合計の平均で割り、コントラストで乗算し、から最終値を差し引く必要があります。その特定の色:

たとえば、ピクセルから105(赤)を取得するので、RGBの平均の合計で割ります。これは115で、コントラスト値10の10倍(105/115)* 10で、約9になります(切り上げる必要があります)。次に、その9を105から離して、色が96になるようにします。暗いピクセルに10のコントラストを付けた後の赤。

したがって、ピクセルの値を続行すると、[96,37,183]になります。 (注:コントラストのスケールはあなた次第です!しかし、私の最終的には、1から255のようなスケールに変換する必要があります)

明るいピクセルについても、コントラスト値を減算する代わりに加算することを除いて、同じことを行います。そして、255または0の制限に達すると、その特定の色の加算と減算を停止します。したがって、より明るいピクセルである私の2番目のピクセルは[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;
  }
}

この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;
}
2
Escher

ヴィンティングによって、あなたがLUTSを適用しようとしていると思います。最近、私はキャンバスウィンドウにカラートリートメントを追加しようとしています。キャンバスウィンドウに実際に「LUTS」を適用したい場合は、imageDataが返す配列をLUTのRGB配列に実際にマップする必要があると思います。

(光の錯覚から)例として、1D LUTの開始は次のようになります。注:厳密に言えば、これは3x 1D LUTであり、各色(R、G、B)は1DLUTです。

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

これは奇妙なLUTですが、R、G、またはB入力の特定の値に対して、R、G、およびB出力の特定の値があることがわかります。

したがって、ピクセルのRGBの入力値が3、1、0の場合、出力ピクセルは9、2、0になります。

この間、imageDataを操作した後、Uint8Arrayが返され、その配列の値が10進数であることに気付きました。ほとんどの3DLUTSは16進数です。したがって、このすべてのマッピングの前に、最初に配列全体で何らかのタイプの16進数から12進数への変換を行う必要があります。

1
Jay

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に翻訳できると思います。

1
karlphillip

これはあなたが探している式です...

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
    }
}

それが役に立てば幸い!

0
fforgoso