第13回 HSB表現

ピクセルの色を表すには、いままで使っていたRGBの値を使う方法のほかに、色相、彩度、明度の3つの情報を使う方法 (HSB表現) がある。 なお、明度をValueという値であらわし、HSV表現といういいかたをすることも多い。

これを下のような図で表わしたものを色立体とよび、この3つの座標で表わされる空間を色空間という。

今回のプログラムの最終的な機能

プログラムを実行して少し待つとコンソールに「完了」が表示され、実行画面上に元画像と色立体の明度70の断面の円の画像が表示される。
dataフォルダには12枚の画像が作られる。
そのあとでキーボードの0~6のキーを押すとこれらの画像が画面に表示される。
(すべての画像は自動的に作成されるので、キーボードを使うのは結果確認のためだけ)
キー 画像(全体) 画像(左上) 意味 特徴
0 元.jpg 元.png 元画像と色立体の明度70の断面の円
1 色相+120.jpg 色相+120.png 色空間で時計回りに120°回転させた画像 黄はシアン, シアンはマゼンタ, マゼンタは黄に
赤は緑, 緑は青, 青は赤に変わる
2 色相+240.jpg 色相+240.png 色空間で時計回りに240°回転させた画像 黄はマゼンタ, マゼンタはシアン, シアンは黄に
赤は青, 青は緑, 緑は赤に変わる
3 彩度+30.jpg 彩度+30.png 彩度を30上げた画像 色が鮮やかになる
4 彩度-30.jpg 彩度-30.png 彩度を30下げた画像 色がせる
5 明度+30.jpg 明度+30.png 明度を30上げた画像 明るくなる
6 明度-30.jpg 明度-30.png 明度を30下げた画像 暗くなる
キーと表示される画像

1. 色立体の断面画像

概要


今回のプログラムでは色相・彩度・明度を変えて元画像がどう変わるかを見るとともに、色立体上でどのように移動するかも視覚化したい。
そのため、色立体を下から70%の水平な面でカットした断面の画像を用意し、それに元画像と同じ変換を施すことにする。
この断面は円形で、明度は円の中ならどこも同じ (70) で、色相は右側が0で時計回りに360まで増える。彩度は中心が0で外側に向かって100まで増える。

ProcessingではcolorMode関数で色指定の方法を切り替えられる。
具体的には「colorMode(HSB, 360, 100, 100);」を実行すればHSB表現になり、「color c=color(色相, 彩度, 明度);」という書式で色相・彩度・明度を指定して色が作れるようになる。

課題 1

  1. Processingを起動する。
  2. 以下のコードをコピー&ペーストする。
    // 画像用の変数
    PImage[][] img = new PImage[7][2];
    // 出力ファイル名
    String[] fName = {"元", "色相+120", "色相+240", "彩度+30", "彩度-30", "明度+30", "明度-30"};
    
    void setup() {
      size(800, 600);
      img[0][0] = loadImage("元.jpg");
      img[0][0].resize(width, height);
      textFont(createFont("MS Pゴシック", 48));
      background(0);
      image(img[0][0], 0, 0);
      createColorCircle();
      // 元画像、色円の色相を120増やした画像をimg[1][0], img[1][1]に保存
      // 元画像、色円の色相を240増やした画像をimg[2][0], img[2][1]に保存
      // 元画像、色円の彩度を30上げた画像をimg[3][0], img[3][1]に保存
      // 元画像、色円の彩度を30下げた画像をimg[4][0], img[4][1]に保存
      // 元画像、色円の明度を30上げた画像をimg[5][0], img[5][1]に保存
      // 元画像、色円の明度を30下げた画像をimg[6][0], img[6][1]に保存
      println("完了");
    }
    
    void draw() {
    }
    
    // 基本の色円を作る
    void createColorCircle() {
      img[0][1] = createImage(200, 200, ARGB); // 200x200の透明画像
      colorMode(HSB, 360, 100, 100); // 色指定モードをHSBにする
      int r = img[0][1].width/2;
      for (int j=0; j<r*2; j++) {
        for (int i=0; i<r*2; i++) {
          // 円の中心から(i, j)までの距離をfloat dに入れる
          // (i, j)が(r, r)を中心とする半径rの円の中なら
            // 円の中心と(i, j)を結ぶ線の傾き(-PI~PI)をfloat aに入れる
            // aが0以上になるように修正
            // 画像[0][1]の(i, j)の色を色相a(度に直した値), 中心からの距離に比例した彩度, 明度70の色にする
        }
      }
      img[0][1].save("data/" + fName[0] + ".png");
    }
    
    // 元画像を画像[n][0], 色円画像を画像[n][1]にコピーして色相をa度回転させ、彩度をb上げ、明度をc上げる
    void changeHSB(int n, float a, float b, float c) {
      // 元画像(k=0),色円画像(k=1)の両方について処理を行う
      for (int k=0; k<2; k++) {
        img[n][k] = img[0][k].get(); // 元画像, 色円画像をn番にコピー
        int w = img[n][k].width;
        int h = img[n][k].height;
        for (int j=0; j<h; j++) {
          for (int i=0; i<w; i++) {
            color cl = img[n][k].pixels[i+j*w]; // (i, j)のピクセルの色
            // そのピクセルが透明なら処理をスキップ
            if (alpha(cl) == 0) {
              continue;
            }
            // 色相, 彩度, 明度を取得
            float H = hue(cl);
            float S = saturation(cl);
            float B = brightness(cl);
            // 360以上にならないように色相をa増やす
            // 彩度をb上げる
            // 明度をc上げる
            img[n][k].pixels[i+j*w] = color(H, S, B);
          }
        }
      }
      img[n][0].save("data/" + fName[n] + ".jpg");
      img[n][1].save("data/" + fName[n] + ".png");
    }
    
    void keyPressed() {
      int k = key-'0';
      if (k>=0 && k<=6) {
        colorMode(RGB, 255, 255, 255); // 色指定モードをRGBにする
        background(0);
        image(img[k][0], 0, 0);
        image(img[k][1], 0, 0);
        fill(255, 0, 0);
        text(fName[k], 30, height-30);
        fill(255);
      }
    }

  3. 適当に画像検索して図の8つの色のうち少なくともどれか1つに近い色を含む元画像を用意する。
    (「繁華街」や「花壇」などで画像検索すれば良いものが見つかる)
    (第06回で使った元画像を再利用してもよい)

  4. 画像の形式に応じて以下の変更を加える。
    • JPG形式の場合→名前を元.jpgに変更
    • それ以外の場合→ペイントで開き、形式をJPGに指定して「元」という名前で保存
  5. 「元.jpg」をProcessingのウインドウにドラッグ&ドロップする。
  6. createColorCircle関数の「// 円の中心から(i, j)までの距離をfloat dに入れる」の下に必要なコードを追加する。
    • 円の中心の座標は(r, r)。
    • 2点間の距離はdist関数で取得できる。引数には点1のx座標, 点1のy座標, 点2のx座標, 点2のy座標を入れる。

  7. createColorCircle関数の「// (i, j)が(r, r)を中心とする半径rの円の中なら」の下に対応するif文を追加し、その下の3行をその例外処理に含める。
    • dがrより小さければ円の中にあることになる。
    • 「// (i, j)の色を明度a(度に直した値), 半径に比例した彩度, 明度70の色にする」の下に「}」を追加する。

  8. createColorCircle関数の「// 円の中心と(i, j)を結ぶ線の傾き(-PI~PI)をfloat aに入れる」の下に対応するコードを追加する。
    • 角度はatan2関数で取得する。
    • 第1引数には「円の中心から(i, j)のy方向のずれ」を入れる。
    • 第2引数には「円の中心から(i, j)のx方向のずれ」を入れる。

  9. createColorCircle関数の「// aが0以上になるように修正」の下に対応するコードを追加する。
    • if文か条件演算子を使う。
    • 例えばaが\(-\pi/3\)のときは、それと実質同じ向きで0以上の値、つまり\(2\pi-\pi/3=5\pi/3\)になればよい。

  10. createColorCircle関数の「// 画像[0][1]の(i, j)の色を色相a(度に直した値), 中心からの距離に比例した彩度, 明度70の色にする」の下に対応するコードを追加する。
    • ここで扱っている画像の幅はrの2倍なので、左辺はimg[0][1].pixels[i+j*r*2]。
    • 右辺の色はcolor関数で作る。引数は色相、彩度、明度の順。
    • aの単位はラジアン。これを度に直した値はdegrees(a)で得られる。
    • 中心からの距離は計算済みで、変数dに入っている。これが0のときに彩度が0、これがrのときに彩度が100になるようにする。

  11. プログラムを実行する。
    • コンソールに「完了」が表示されてから0キーを押すと、元画像の左上に色立体の下から70%の高さでの断面の画像が表示される。

2. 色相の変更

概要

色相の変更は色空間では回転に相当する。
時計回りに120°回転すると、赤は緑, 緑は青, 青は赤に、黄はシアン, シアンはマゼンタ, マゼンタは黄に変わる。
時計回りに240°、つまり反時計回りに120°回転すると、赤は青, 青は緑, 緑は赤に、黄はマゼンタ, マゼンタはシアン, シアンは黄に変わる。


課題1で作った円の画像に対して同じ変換を行っても色の変わり方は同じ。見た目上はそれぞれ反時計周りに120度、240度回転したように見える。

課題 2

  1. changeHSB関数の「// 360以上にならないように色相をa増やす」の下に必要なコードを追加する。
    • 例えばHが300, aが100の場合は普通に増やすと400になってしまうが、その場合は360を引いた値 (40) にする。
    • 普通にHをaだけ増やす処理を行ったあとでif文で例外処理してもよいし、条件演算子を使って1行で書いてもよい。

  2. setup関数の
      // 元画像、色円の色相を120増やした画像をimg[1][0], img[1][1]に保存
      // 元画像、色円の色相を240増やした画像をimg[2][0], img[2][1]に保存
    の左にそれぞれ必要なコードを追加する。
    • 呼ぶのはいずれもchangeHSB関数。
    • 第1引数が出力画像の第1インデックス。つまり1か2。
    • 第2引数が色相の変化の値。
    • 第3引数が彩度、第4引数が明度の変化の値だが、ここではどちらも変えない。つまり0にする。

  3. プログラムを実行する。
    • コンソールに「完了」が表示されてから1キーを押すと、元画像の赤い部分は緑、緑の部分は青、青の部分は赤に変わる。左上の色円は反時計周りに120度回転したように見える。
    • 2キーを押すと、元画像の赤い部分は青、青の部分は緑、緑の部分は赤に変わる。左上の色円は元の状態から反時計周りに120度回転したように見える。

3. 彩度・明度の変更

概要

彩度の値を大きくすることは、色空間では外側への移動にあたる。結果として色が鮮やかになる。
彩度の値を小さくすることはその逆で、色褪せた状態になる。


課題1で作った円の画像に対して同じ変換を行っても色の変わり方は同じ。見た目上は同じ色の部分が内側、外側に移動したように見える (変化させた方向とは逆に動いたように見える)。



明度を上げ下げすることは色空間での上下方向の移動にあたる。上げると明るく、下げると暗くなる。




課題1で作った円の画像に対して同じ変換を行っても色の変わり方は同じ。
(色立体上の上下方向の移動では断面の円の大きさが変化するはずだが、ここでは特に大きさを変える処理はしていない)

課題 3

  1. changeHSB関数の「// 彩度をb上げる」の下に必要なコードを追加する。
    • 彩度Sの有効範囲は0~100だが、color関数で色を作るときに第2引数に負の数を入れたときは0、100を超える値を入れたときは100を入れたのと同じ扱いになる。そのため、特に例外処理は行わずに「Sをbだけ増やす」処理を行えばよい。

  2. changeHSB関数の「// 明度をc上げる」の下に必要なコードを追加する。
    • こちらも彩度と同じ理由で例外処理は不要。

  3. setup関数の
      // 元画像、色円の彩度を30上げた画像をimg[3][0], img[3][1]に保存
      // 元画像、色円の彩度を30下げた画像をimg[4][0], img[4][1]に保存
      // 元画像、色円の明度を30上げた画像をimg[5][0], img[5][1]に保存
      // 元画像、色円の明度を30下げた画像をimg[6][0], img[6][1]に保存
    
    の左にそれぞれ必要なコードを追加する。
    • 呼ぶのはいずれもchangeHSB関数。
    • 第1引数が出力画像の第1インデックス。つまり3~6。
    • 第2引数が色相の変化の値。ここでは変えないので0にする。
    • 第3引数が彩度、第4引数が明度の変化の値。変える方には必要な値を入れ、そうでない方は0にする。

  4. プログラムを実行する。コンソールに「完了」が表示されてからキーを押してできた画像を確認する。
    • 3キーを押すと、元画像と色円の彩度を30上げた画像が表示される。
    • 4キーを押すと、元画像と色円の彩度を30下げた画像が表示される。
    • 5キーを押すと、元画像と色円の明度を30上げた画像が表示される。
    • 6キーを押すと、元画像と色円の明度を30下げた画像が表示される。

提出

提出先フォーム (締切:7/16(火))

※ コードはTeamsのClass Notebookに保存してください。
※ このフォームから課題を提出すれば出席がつきます。ただし、あまりにも進捗が少ない場合は提出としてカウントされません。
※ 締切後はこちらから提出してください (通常と同様に点数がつきますが、出席はつきません)。
戻る