色覚異常をシミュレートする方法について

はじめに

こんにちは。技術部の中山といいます。普段はぼくらの甲子園!ポケットチームでUnityとかGAS(Google Apps Script)を触ったりしています。

この記事では、色覚異常1のシミュレート方法について紹介します。

シミュレート結果

ここで紹介する方法で1型2色覚の人の見え方をシミュレートをすると次のようになります。左が元画像、右がシミュレートしたものです。

左が元画像、右が1型2色覚のシミュレート結果
左が元画像、右が1型2色覚のシミュレート結果

シミュレート方法

どの方法を選ぶか

シミュレートの方法は調べるといくつか出てくるのですが、自分が1型2色覚なので、自分の目で元画像と変換後の画像を見て一番差がないと思ったものを紹介します。「Digital Video Colourmaps for Checking the Legibility of Displays by Dichromats」という論文に載っている方法で、ここにアクセスすると読めます。

手順

論文に従うと、シミュレートの手順は以下のようになります。

  1. RGB値から錐体細胞が受ける刺激の量(LMS色空間の値)を求める
  2. 特定の錐体細胞が受ける刺激の量が0になるような変換を行う
  3. LMS色空間からRGBに戻す

錐体細胞というのは目の網膜にある細胞で、人の網膜にはL錐体、M錐体、S錐体の3種類の錐体細胞があります。錐体細胞は

  • L錐体は赤い光によく反応する
  • M錐体は緑の光によく反応する
  • S錐体は青い光によく反応する

というように種類によってどの波長の光によく反応するかが異なっています。これらのうち1種類以上の機能が弱かったり特定の種類の錐体細胞がなかったりする状態が色覚異常なので、上の手順のように特定の錐体細胞の刺激量を0にすることでシミュレートができます。

具体的にどのように変換を行うのかは論文の3ページ目と6ページ目をご覧ください。ベクトルを作って行列を掛けるだけです2

1型3色覚とか2型3色覚のシミュレート

論文の(4)式、(5)式、(6)式を組み合わせると、RGBを並べて作ったベクトルに

\mathit{RGB\_to\_LMS}^{-1}\times(\text{(5)式の行列})\times\mathit{RGB\_to\_LMS}

という行列を掛ければ色覚異常のシミュレートができるということがわかりますが、この計算で得られるのはL錐体とかM錐体がない場合(1型2色覚とか2型2色覚とか言われる状態)のシミュレート結果です。

では錐体細胞の機能が弱い場合(1型3色覚とか2型3色覚)のシミュレートはどうすれば良いのかというと、上の式の (\text{(5)式の行列}) の部分を単位行列に置き換えると何も変換しなくなる(色覚異常ではない人のシミュレート結果が得られるとも言える?)ので、

\mathit{RGB\_to\_LMS}^{-1}\times( (\text{(5)式の行列})t+I(1-t) )\times\mathit{RGB\_to\_LMS}

のように (\text{(5)式の行列}) と単位行列を線形補間してやればそれっぽくなります。 I は単位行列、 t は0~1の適当な値です。

Unityでシミュレートする

Unityだと OnRenderImage を使うのが一番手軽だと思います。

上の式で色の変換をするシェーダを用意しておいて、そのシェーダをマテリアルに設定しておいて、次のようなコンポーネントをカメラにつけるだけです。

using UnityEngine;

public class ColorblindEffect : MonoBehaviour
{
    public Material material;

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit(source, destination, material);
    }
}

参考文献

Françoise Viénot, Hans Brettel, and John D. Mollon, Digital Video Colourmaps for Checking the Legibility of Displays by Dichromats, 1999.


  1. 「色弱」「色盲」という呼び方をしたり、「色覚特性」とか「色覚多様性」と言ったりすることもあるみたいですが、ここでは「色覚異常」で統一します。

  2. 本当は使ってるディスプレイの赤緑青の波長に合わせて行列を用意しないと厳密な結果にならない気がするけど、ここではそこまで細かいことは気にしないことにしてます。