AI Dev Lab
ChromaLens

ChromaLens ができるまで — 色覚シミュレーションとダルトナイズ補正

色覚シミュレーターの開発ログ。 1型・2型・3型の見え方を 3×3 の色変換行列で再現し、 失われる色差を別のチャンネルへ逃がすダルトナイズで区別しやすく補正するまでの実装を記録しています。

ChromaLens は、 画像 を 1型 (P型・赤) / 2型 (D型・緑) / 3型 (T型・青) の 色覚特性 で の 見え方 に 変換 して 並べ、 さらに 「区別 しやすく」 補正 する ブラウザ ツール。 変換 は 3×3 の 行列 演算 だけ で 済む ので、 機械学習 モデル も サーバー も 使わ ず、 画像 は 端末 の 中 から 出ません。

#🎯 「見えない」 ではなく 「区別 が つかない」

作り 始める 前 に 一番 誤解 して いた の が ここ でした。 色覚特性 は 「色 が 見えない」 のでは なく、 特定 の 組み合わせ の 区別 が つき にくい。 日本人男性 の 約 5% (20 人 に 1 人) が 該当 する と 言われ、 赤緑 型 が 大半 を 占め ます。

つまり 検証 したい の は 「この 画像 は 何色 に 見える か」 では なく、 「グラフ の 赤い線 と 緑の線 が 別物 だ と 分かる か」。 それ が 分かる 形 に 出力 を 設計 しました。

  • 元画像 と 1型 / 2型 / 3型 を 横 に 並べて 同時 に 見せる (切り替え 式 に する と 差分 が 分から ない)
  • 「区別 しやすく 補正 した 画像」 も 同じ 並び に 置く
  • サンプル に 原色 ブロック を 入れて、 赤 と 緑 が どこ まで 寄る か を 一目 で 分かる ように する

#🏗️ 3×3 行列 を かける だけ

色覚 シミュレーション は、 RGB の 各 ピクセル に 行列 を かける 線形変換 で 近似 でき ます。 広く 使われて いる 係数 (HCIRN / Wickline 系) を sRGB に 直接 当てる 方式 を 採り ました。

ts
const SIM: Record<Vision, number[]> = {
  protan: [0.567, 0.433, 0.0,  0.558, 0.442, 0.0,  0.0, 0.242, 0.758],
  deutan: [0.625, 0.375, 0.0,  0.700, 0.300, 0.0,  0.0, 0.300, 0.700],
  tritan: [0.950, 0.050, 0.0,  0.0,   0.433, 0.567, 0.0, 0.475, 0.525],
}

1型 の 行 を 見る と、 R と G の 係数 が どちら の 行 でも ほぼ 同じ 比率 に なって います。 これ が 「赤 と 緑 が 同じ 値 に 潰れる」 の 正体 で、 コード に する と 現象 が そのまま 数字 で 見え ます。

ImageData を 1 ピクセル ずつ 舐めて 掛ける だけ な ので、 依存 ライブラリ は ゼロ。 1200×800 程度 なら 体感 で 一瞬 です。

#🔑 ダルトナイズ ── 失われる 差 を 別 の 色 に 逃がす

見え方 を 再現 する だけ なら 上 の 行列 で 終わり です が、 実用 上 欲しい の は 「じゃあ どう 直せば いい か」。 そこ で ダルトナイズ (daltonize) を 実装 しました。

考え方 は 素直 で、

  1. 元 の 色 と、 シミュレート した 色 の 差分 を 取る (= その 特性 で 失われる 情報)
  2. その 差分 を、 まだ 感じ 取れる チャンネル へ 足し 込む
ts
const [sr, sg, sb] = applyMatrix(r, g, b, m)
const er = r - sr   // 失われた 赤成分
const eg = g - sg
const eb = b - sb

// 赤 の 損失 を 緑 と 青 に 7 割 ずつ 逃がす
d[i + 1] = clamp(g + 0.7 * er + eg)
d[i + 2] = clamp(b + 0.7 * er + eb)

赤 の 損失 er を 緑 と 青 に 配分 する の が 肝 です。 補正後 の 画像 は 一般 色覚 の 人 に は 「少し 色 が ずれた 絵」 に 見え ます が、 赤緑 の 区別 が つき にくい 人 に は 元 は 潰れて いた 差 が 明度 と 青み の 差 として 立ち上がり ます

#😓 苦労した ところ

補正 の 強さ を どこ に 置く か。 係数 を 上げる ほど 区別 は つき やすく なり ます が、 写真 が 一気 に 不自然 に なり ます。 0.7 は 「資料 の 配色 チェック に 使える 程度 に 区別 が 出て、 写真 が まだ 写真 に 見える」 境目 として 選び ました。 正解 が ある 数字 では ない ので、 ここ は 断言 せず 「一般的 な モデル に よる 近似」 と 画面 にも 明記 して います。

サンプル 画像 を 何 に する か。 風景写真 だ と 差 が 分かり にくく、 最初 は 「変換 されて いる の か どうか」 すら 伝わり ません でした。 最終的 に 原色 ブロック (純赤 / 緑 / 青 / 黄 / 橙 / 茶 / マゼンタ / シアン) を 敷き詰めた サンプル を 用意 し、 1型 で 赤 と 茶 が 同じ 色 に なる の が 一目 で 分かる ように しました。

個人差 を 言い切ら ない こと。 色覚特性 は 型 の 中 でも 程度 が 大きく 違い ます。 「あなた に は こう 見えます」 と 言い切る の は 誠実 では ない ので、 表記 は 一貫 して 「この 特性 で の 見え方 の 近似」 に 統一 しました。

#🔭 今後 の 拡張

  • 弱度 (protanomaly / deuteranomaly) の 段階 指定 — 現状 は 二色型 の 全欠損 近似 のみ
  • 画像 内 の 「区別 が つか ない 色 ペア」 を 自動 検出 して 印 を 付ける
  • 配色 コード (#rrggbb) を 直接 入力 して、 その 組み合わせ が 安全 か 判定 する モード
  • WebGL 化 (現状 は CPU ループ、 4000px 級 だ と 待ち が 出る)

#💡 この サービス から 言える こと

画像処理 は 端末 の 中 で 完結 する なら、 それ 自体 が 価値 に なり ます。 資料 や 社内 の グラフ を 検証 したい 場面 で、 画像 を どこか に アップ ロード しなくて いい こと は 「便利」 では なく 「使える か どうか」 の 分かれ目 です。

そして、 3×3 の 行列 を かける だけ の 処理 でも、 出力 の 見せ方 を 設計 すれば 実用 ツール に なり ます。 重い モデル を 動かす こと と、 役 に 立つ こと は 別 の 話 でした。

[ ./next_action ]

読んだら、 ChromaLens を実際に動かす。

この開発ログは ChromaLens をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ToonCast

ToonCast ができるまで — AnimeGANv2 をブラウザで動かす

AnimeGANv2 の小さな ONNX (約9MB) を onnxruntime-web (単一スレッド WASM=COOP/COEP不要、 color-revive で承認済みライブラリの再利用) で実行。 512x512・[-1,1] 正規化で推論し、 結果を元解像度に戻して表示する設計記録。 写真は端末内処理。

read log →
ColorRevive

ColorRevive ができるまで — onnxruntime-web で白黒写真をカラー化

DeOldify の量子化 ONNX を onnxruntime-web (CDN side-load・単一スレッド WASM=COOP/COEP不要) で実行。 256x256 でモデル推論し、 輝度は元写真・色だけ AI を YCbCr で再合成して輪郭を保つ設計記録。 写真は端末内処理。

read log →
PhotoTwin

PhotoTwin ができるまで — CLIP画像埋め込みで似た写真を見つける

CLIP (Xenova/clip-vit-base-patch32) の image-feature-extraction を transformers.js の CDN ESM で side-load し、 各写真を正規化ベクトル化。 cosine 類似度で重複・似た写真をブラウザ内で検出する設計記録 (新ライブラリ追加なし=what-cam と同じ CLIP の再利用)。

read log →
AkinFind

AkinFind ができるまで — ブラウザ内で完結する embeddings 意味検索

多言語の文章埋め込みモデル (Xenova/multilingual-e5-small) を transformers.js の CDN ESM で side-load し、 各文を正規化ベクトル化。 cosine 類似度で意味検索と似ている文ペア検出を全て端末内で行う設計記録。

read log →
WhatCam

WhatCam ができるまで — CLIP のゼロショット画像分類をブラウザで動かす

CLIP (Xenova/clip-vit-base-patch32) を transformers.js の CDN ESM で side-load し、 写真と候補ラベルの近さをブラウザ内で計算。 日本語ラベルを英語プロンプトに変換し、 図鑑と自由入力の両モードで「これ何?」を判定する設計記録。

read log →
DepthCast

DepthCast ができるまで — 1枚の写真をAIの深度推定で立体にする

Depth Anything (transformers.js) を CDN ESM で side-load し、 1枚の写真から深度マップを推定。 WebGL2 フラグメントシェーダで深度に比例した視差 (iterative backward parallax) を作り、 赤青アナグリフ / WebM 書き出しまで端末内で完結させた設計記録。

read log →