3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

スマホのカメラから色を検出したい

3
Posted at

はじめに

アイデアが閃いて実現できないかと思い、Clade Codeと進めたことを記事にしようと思います
試していた感じ色を判定するのが非常に難しかったので、今回はどうやって色を認識させるのかの部分だけに絞ろうと思います

作りたいもの

スマホのカメラで撮影して、色を取得したい
HTMLとJavaScriptだけで、できれば動かしたい

デジタルで色を表現する方法

RGBとHSLという方法があります

  • RGB
    • 光の三原色である Red(赤)、Green(緑)、Blue(青) を混ぜ合わせる「加法混色」に基づいたモデルです
    • 仕組み: それぞれの強さを0〜255の数値で表します
  • HSL
    • 色を色相(Hue)・彩度(Saturation)・輝度(Lightness) の3要素で表現するカラーモデル
    • 仕組み:
      • H(色相): 0〜360度の角度(赤→黄→緑→青→紫)
      • S(彩度): 0〜100%の鮮やかさ
      • L(輝度): 0〜100%の明るさ

色判定の方針:RGBではなくHSLで考える

色を判定・指定する際に、HSLは非常に適したカラーモデルです
RGB(赤・緑・青)が機械的な光の強度を数値化するのに対し、HSLは人間の視覚感覚(色相・彩度・明度)に近いため、直感的に色を理解・調整できます

例えば
カメラ映像の各ピクセルはRGBで取得できます

しかし「赤っぽいかどうか」をRGBで判定しようとすると
明るい赤 rgb(255, 80, 80) と暗い赤 rgb(120, 20, 20) は、同じ 赤 でも数値が全然違います

HSLで考える

HSL(色相・彩度・明度) に変換すると

Gemini_Generated_Image_jtcfw3jtcfw3jtcf.png

要素 意味 範囲
H (Hue) 色の種類(赤・青など) 0〜360°
S (Saturation) 鮮やかさ 0〜1
L (Lightness) 明るさ 0〜1

HSLなら 赤 = 色相が0°付近 とシンプルに表現することができます

各色の定義を考えてみる

コードに落とし込んで考えてみます

const DEFS = {
  red:    { label:'赤',       css:'#ff3333', hRanges:[[0,14],[346,360]], sMin:.45, lMin:.15, lMax:.85 },
  orange: { label:'オレンジ',  css:'#ff8800', hRanges:[[15,40]],          sMin:.55, lMin:.25, lMax:.85 },
  yellow: { label:'黄',       css:'#ddcc00', hRanges:[[41,74]],          sMin:.55, lMin:.35, lMax:.95 },
  green:  { label:'緑',       css:'#22bb33', hRanges:[[75,160]],         sMin:.30, lMin:.10, lMax:.80 },
  cyan:   { label:'水色',     css:'#00bbcc', hRanges:[[161,200]],        sMin:.45, lMin:.20, lMax:.80 },
  blue:   { label:'青',       css:'#2255ff', hRanges:[[201,255]],        sMin:.35, lMin:.10, lMax:.75 },
  purple: { label:'紫',       css:'#9922cc', hRanges:[[256,320]],        sMin:.30, lMin:.10, lMax:.75 },
  pink:   { label:'ピンク',    css:'#ff55aa', hRanges:[[321,345]],        sMin:.45, lMin:.35, lMax:.90 },
};

各色に hRanges(色相の許容範囲)と sMin(最低彩度)、lMin/lMax(明度の範囲)を定義しました

赤だけ範囲が2つある理由:色相は円環なので、赤は0°付近と360°付近をまたぎます
[[0,14],[346,360]] という2つの範囲で表現する必要があります

彩度・明度の制限が重要:白・黒・グレーは色相がどんな値でも彩度がほぼ0になります
sMin で弾かないと誤検出が大量に出ます

RGB → HSL 変換

function rgbToHsl(r,g,b) {
  r /= 255; g /= 255; b /= 255; //0〜1に正規化

  const max = Math.max(r,g,b), min = Math.min(r,g,b);
  leth=0, s=0, l=(max+min)/2; //明度

  if(max !== min) {
    const d = max-min; //彩度の分母
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min); //彩度

    switch(max) {
      case r:
        h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
        break; //赤が最大

      case g:
        h = ((b - r) / d + 2) / 6;
        break; //緑が最大

      case b:
        h = ((r - g) / d + 4) / 6;
        break; //青が最大
    }
  }
  // H:0〜360度, S:0〜1, L:0〜1
  return[ h * 360, s, l];
}

ステップ 計算 意味
正規化 r /= 255 0〜255 → 0〜1
明度L (max + min) / 2 最大と最小の中点
彩度S d / (2 - max - min) or d / (max + min) L > 0.5で式が変わる
色相H どのチャンネルが最大かで場合分け 0〜1 → 最後に ×360

戻り値の h を × 360 して度数(0〜360) にしているのがポイントです

/ 6 で割っているのは、色相の計算が本来「0〜6の範囲」で出てくるためです

赤(0) → 黄(1) → 緑(2) → 水色(3) → 青(4) → 紫(5) → 赤(6)
これを 0〜1 に圧縮しておいて、最後に × 360 で度数に戻します

0〜1 → × 360 → 0〜360°

つまり:

h = rawH / 6      // 0〜6 を 0〜1 に圧縮
return [h * 360]  // 0〜1 を 0〜360° に展開

この2ステップを合わせると rawH / 6 * 360 = rawH * 60 と同じで、色相環の1セクション = 60° という色彩論の定義と一致させることができます

ピクセルの色判定

function matches(r, g, b, name) {
  const d = DEFS[name];
  const [h, s, l] = rgbToHsl(r, g, b);

  if (s < d.sMin || l < d.lMin || l > d.lMax) return false;
  return d.hRanges.some(([lo, hi]) => h >= lo && h <= hi);
}

2段階で判定しています

  1. 彩度・明度で無彩色をまず除外する
  2. 色相が定義の範囲内かを確認する

まとめ

今回の色判定のポイントをまとめます

  • RGBのまま扱わず、HSLに変換してから色相で判定する
  • 赤のように0°をまたぐ色は 複数範囲 で定義する
  • 彩度の下限を設けて 無彩色の誤検出を防ぐ
3
3
1

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?