LoginSignup
2
5

More than 3 years have passed since last update.

フォトショになりたい Part 2: 色調補正

Last updated at Posted at 2019-10-08

色調補正

  • HSV
    • 色相(Hue)
    • 彩度(Saturation)
    • 明度(Value/Brightness)
    • 自然な彩度
  • ホワイトバランス
    • 色温度(temperature)
    • 色被り補正(deviation)

HSV

RGBとHSVは一つの色を別の座標空間で表現しているだけ。
変換式はRGBとHSV・HSBの相互変換ツールと変換計算式より。丸め誤差を除けば一応全単射なはず。
(以下の計算は、それぞれ0 to 255に正規化しています)。

色相

RGBによって貼られる平行六面体を$r+g+b$ベクトルから見たときの偏角
(詳しくはRGB正六面体 → HSL円柱を参照)

0-360度にするとImageData型(Uint8ClampedArray)に入りきらないので、0から255に納めている。

calcHue.js
function calcHue(r, g, b) { // 0 to 255
  const MAX = Math.max(r, g, b);
  const MIN = Math.min(r, g, b);
  if (MAX === MIN) {
    return 0;
  } if (MIN === b) {
    const h = 42.5 * ((g - r) / (MAX - MIN)) + 42.5;
    return h > 0 ? h : h + 255;
  } if (MIN === r) {
    const h = 42.5 * ((b - g) / (MAX - MIN)) + 127.5;
    return h > 0 ? h : h + 255;
  }
  const h = 42.5 * ((r - b) / (MAX - MIN)) + 212.5;
  return h > 0 ? h : h + 255;
}

彩度

RGBの最大値と最小値の比。

calcSaturation.js
function calc(r, g, b) { // 0 to 255
  const MAX = Math.max(r, g, b);
  const MIN = Math.min(r, g, b);
  return (MAX - MIN) / MAX * 255;
}

明度

RGBの最大値。

calcBrightness.js
const calcBrightness = (r, g, b) => Math.max(r, g, b);

まとめると

hsvCvt.js
function rgb2hsv(img) { // H: 0~255, S: 0~255, V: 0~255
  for (let i = 0; i < img.data.length; i += 4) {
    const r = img.data[i];
    const g = img.data[i + 1];
    const b = img.data[i + 2];
    const MAX = Math.max(r, g, b);
    const MIN = Math.min(r, g, b);
    img.data[i] = calcHue(r, g, b);
    img.data[i + 1] = (MAX - MIN) / MAX * 255;
    img.data[i + 2] = MAX;
  }
  return img;
}

function hsv2rgb(img) {
  for (let i = 0; i < img.data.length; i += 4) {
    const h = img.data[i];
    const s = img.data[i + 1];
    //MAX = v
    const MAX = img.data[i + 2];
    const MIN = MAX - ((s / 255) * MAX);
    if (h < 42.5) {
      img.data[i] = MAX;
      img.data[i + 1] = (h / 42.5) * (MAX - MIN) + MIN;
      img.data[i + 2] = MIN;
    } else if (h < 85) {
      img.data[i] = ((85 - h) / 42.5) * (MAX - MIN) + MIN;
      img.data[i + 1] = MAX;
      img.data[i + 2] = MIN;
    } else if (h < 127.5) {
      img.data[i] = MIN;
      img.data[i + 1] = MAX;
      img.data[i + 2] = ((h - 85) / 42.5) * (MAX - MIN) + MIN;
    } else if (h < 170) {
      img.data[i] = MIN;
      img.data[i + 1] = ((170 - h) / 42.5) * (MAX - MIN) + MIN;
      img.data[i + 2] = MAX;
    } else if (h < 212.5) {
      img.data[i] = ((h - 170) / 42.5) * (MAX - MIN) + MIN;
      img.data[i + 1] = MIN;
      img.data[i + 2] = MAX;
    } else {
      img.data[i] = MAX;
      img.data[i + 1] = MIN;
      img.data[i + 2] = ((255 - h) / 42.5) * (MAX - MIN) + MIN;
    }
  }
  return img;
}

自然な彩度

彩度にトーンカーブをかける
→すでに彩度が高いところはそのままで、低いところを上げたい
→とりあえず両対数スケールで実装
(詳しくはスケール対数曲線をトーンカーブとする画像補正を参照)

calcSaturation.js
const natural_s = (s, k) => log_log_scale(s, k);

HSV系全体の流れ

  1. RGBからHSVに変換
  2. HSV空間でいじる
  3. HSVからRGBに変換
editHSV.js
function editHSV(img, h_rotation, s_magnification, natural_s, v_magnification) {
  rgb2hsv(img);
  hsvCvt(img, h_rotation, s_magnification, natural_s, v_magnification);
  hsv2rgb(img);
  return img;
}

ホワイトバランス

こちらはRGB空間を弄る。
RGBそれぞれの感度の違いを再現するので、普通にRGBそれぞれを定数倍すればいいだけだと思う。

ただ、Photoshopには以下のスライダーがあったのでそれだけ実装した。

色温度

基本がわかる!色かぶりの補正【1】ホワイトバランスと色温度
多分青いか黄色いかという話なので、とりあえず$B$チャンネルをk倍した。

色被り補正

Photoshopの色被り補正は緑-マゼンタの軸で補正しているように見えるので、$G$チャンネルを定数倍した。
理論上これと露出をいじれば全パターン再現できているはず。

まとめ

  • HSV系は変換がめんどくさいが、HSV空間に持ち込んで色々いじった後RGBに戻す
  • 色温度・色被り補正はホワイトバランスをいじることに相当する
2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5