【C言語】差分画像を作成する

差分画像の作成方法の解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、2つの画像の差分画像を作成する方法について解説していきます。

早速ですが、↓ 画像と

差分画像を生成する元画像の1つ

↓ の画像との違いが分かりますか?

差分画像を生成する元画像の2つ目

うーん…

一緒にしか見えない!

単純に見比べるだけだと違いを見つけるのは大変だよね!

でも差分部分だけを抽出して画像として表示してやれば違いを見つけるのは簡単だよ!

今回は2つの画像の差分部分だけを抽出した差分画像の作り方を解説していくよ!

例えば、画像の拡大縮小や、圧縮(非可逆圧縮)を行うと画像は劣化します。

ですが、わずかな劣化のため、単に画像を見比べてもどんな違いがあるかは確認しにくいです。

こういった画像のわずかな変化を確認するのに便利なのが今回紹介する差分画像です。

差分画像では2つの画像の差分だけを確認することができるので、どのような劣化が発生しているかが簡単に確認することができます!

MEMO

差分画像作成を行う画像のサンプルとしてリズム727さんによる写真ACからの写真を使用させていただいています

差分画像とは

では差分画像とはどのような画像であるかについて解説しておきます。

差分画像は、2つの画像の差分を画像として表したものになります。

差分画像作成のイメージ

また、下のページで解説しているように、画像は画素が集まったデータです。

画像データの解説ページアイキャッチ 画像データの構造・画素・ビットマップデータについて解説

差分画像では、各座標の画素が「2つの画像の同じ座標の画素の差分」となります。

また、これも上のページで解説しているように、カラー画像においては画素は RGB (赤緑青) の輝度値から構成されます。

ですので、差分画像の画素の R と G と B の輝度値は、2つの画像の同じ座標の画素の R と G と B それぞれの差の値となります。

輝度値は単なる数値ですので、演算子 - を用いて普通に引き算を実行することで差を求めることができます。

要は、差分画像とは「2つの画像の各画素における RGB の輝度値の差から構成される画像」です。

で、この差の絶対値が大きい輝度値が、2つの画像で差が大きい輝度値となります。

単純に輝度値の差を求めればいいだけだから結構簡単そう!

そうだね!

だけど単純に差を求めれば良いのかというとそういうわけでもないんだ…

その辺りのことも踏まえて次は差分画像の作り方について確認していこう!

差分画像の作り方

では続いて差分画像の作り方について解説していきます。

解説は 24 bit カラー画像同士の差分画像を作成することを前提に行なっていきます(24 bit カラー画像は最も一般的なカラー画像な形式になります)。

24 bit カラー画像では、色は RGB の 3 色で、各色の輝度値は 8 bit の符号なしの数値として扱われます。

ですので、1つの輝度値としては符号なし 8 bit のデータが表現可能な 0255 の範囲の数値を設定可能です。

スポンサーリンク

各画素の輝度値の差を求める

前述の通り、差分画像とは「2つの画像の各画素における RGB の輝度値の差から構成される画像」です。

これは、下記を全座標の画素の全色(RGB)に対して繰り返すことで実現することができます。

  • 2つの画像の輝度値の差を求め、その差を差分画像の輝度値に設定する

要は下記のようなループの中で上記を行う感じです。

各画素の輝度値の差を求める
for (j = 0; j < 画像の高さ; j++) {
    for (i = 0; i < 画像の幅; i++) {
        for (c = 0; c < 画像の色数; c++) {
            差分画像の (i, j) 座標の色 c の輝度値 =
                2つの画像の (i, j) 座標の色 c の輝度値の差;
        }
    }
}

ただし、単純に輝度値の差を求めるだけだと下記のような問題があります。

  • 輝度値が負の値になる
  • 輝度値が 8 bit で表せない

1つ1つ何が問題で、どう対処すれば良いかについて解説していきたいと思います。

負の値を正の値で表現できるようにする

まずは1つ目の問題である「輝度値が負の値になる」について解説していきます。

輝度値が負の値になるのが問題である理由

2つの輝度値の差を引き算で求めているのですから、2つ目の輝度値の方が大きい場合は結果が負の値になります。

最悪1つ目の輝度値が 0、2つ目の輝度値が 255 の時は輝度値が -255 になります。

一方、画像の輝度値は正の値(24 bit カラー画像の場合は1つの輝度値が 0255 の値)を前提として通常扱われます。

実際に負の値が画像の輝度値として設定されたとしても、それは正の値に丸められて扱われます。

で、ここで問題になるのがこの丸められ方ですね。

例えば輝度値を unsigned char 型の変数で扱うとしましょう(unsigned char 型は符号なしのサイズ 8 bit の型なので輝度値を格納する変数の型としてよく使用される)。

この変数に -3 が格納されようとした場合は 253-255 が格納された場合は 1 として扱われます。

つまり、-3 のように小さな差分しかないのに大きな差分があるように、-255 のように大きな差分があるのに小さな差分しかないように輝度値が設定されてしまうことになります。

なので、負の値の対処を行わずに画像として保存しても、どこにどれくらい差分が出ているのかが全く分からなくなってしまいます。

輝度値が負の値になる問題の対処法

ではどうやって輝度値が負の値になる問題を対処することができるでしょうか?

色んな方法により対処することができますが、今回は差分画像の輝度値を、2つの画像の輝度値の差 +256 として設定することで対処したいと思います。

輝度値が 0255 の範囲の値しか取らないのであれば、2つの輝度値の差の最小値の -255 場合でも、+256 することで 1 になるので輝度値が負の値になるのを防ぐことができます。

輝度値を 8 bit で表現できるようにする

続いて2つ目の問題である「輝度値が 8 bit で表せない」について解説していきます。

輝度値が 8 bit で表せないのが問題である理由

前述の通り、2つの画像の輝度値の最小値は -255 になります。さらに、2つの画像の輝度値の最大値は 255 になります。

つまり、256 通りの値しか表現できない 8 bit のデータでは2つの画像の輝度値を表現しきれないことになります。

また、負の値を正の値で表現できるようにするで輝度値の差分を +256 することで対処すると述べましたが、これでも結局最大値は 511、最小値は 1 になるだけでこの問題の対処には至りません。

例えば輝度値を unsigned char 型の変数に格納することを考えると、256 以上の値の場合は桁溢れが発生して -256 された値に丸められてしまいます。

つまり、差分が大きな輝度値も小さな輝度値に丸めて表示されてしまう可能性があります。

ですので、輝度値が 0255 の値に収まらないような場合も、差分画像として表示してもどの程度差分が発生しているかが確認できません。

輝度値が 8 bit で表せない問題の対処法

このような場合は、表現できる値の範囲に値を丸めてしまうのが良いです。

例えば2つの画像の輝度値の差が 0511 の間の範囲を取るのであれば、単純に各画素の輝度値を 2 で割ることで輝度値の差を 0255 に収めることができます。

で、この方法だと輝度値の差の大小関係を保ったままになるので、差分画像から2つの画像の差を確認しやすいです。

この方法で作成した差分画像においては、R=128, G=128, B=128 の画素が差分なしの画素、それ以外の画素は差分ありの画素となります。

128 よりも大きい輝度値は1つ目の画像の方が輝度値が大きい、128 よりも小さい輝度値は2つ目の画像の方が輝度が大きいことを表すことになります。

スポンサーリンク

差分画像を作るプログラム

ではここまで解説してきた内容に基づいて、差分画像を作るプログラムのソースコードの紹介やプログラムの説明などをしていきたいと思います。

ソースコード

差分画像を作るプログラムのソースコードの例は下記のようになります。

main.c
#include "myJpeg.h"

int main(int argc, char *argv[]){

    BITMAPDATA_t bitmap1, bitmap2, diff;
    int i, j, c;
    int index;
    int diffNum;
    char outname[256];

    if(argc != 3){
        printf("ファイル名が指定されていません\n");
        return -1;
    }

    /* 1つ目のJPEGファイルの読み込みとデコード */
    if(jpegFileReadDecode(&bitmap1, argv[1]) == -1){
        printf("read or decode error[%s]\n", argv[1]);
        return -1;
    }

    /* 2つ目のJPEGファイルの読み込みとデコード */
    if(jpegFileReadDecode(&bitmap2, argv[2]) == -1){
        printf("read or decode error[%s]\n", argv[2]);
        return -1;
    }

    /* アルファチャンネルは非サポート */
    if (bitmap1.ch == 4 || bitmap2.ch == 4) {
        printf("Not support alpha channel\n");
        return -1;
    }

    /* サイズが違ったら差分は作れないのでエラー */
    if (bitmap1.width != bitmap2.width ||
        bitmap1.height != bitmap2.height ||
        bitmap1.ch != bitmap2.ch) {
        printf("Not same size\n");
        return -1;
    }

    /* 差分画像の情報セット */
    diff.width = bitmap1.width;
    diff.height = bitmap1.height;
    diff.ch = bitmap1.ch;

    /* 差分画像データ格納用のメモリ確保 */
    diff.data = (unsigned char*)malloc(sizeof(unsigned char) * diff.width * diff.height * diff.ch);
    if (diff.data == NULL) {
        printf("malloc error\n");
        freeBitmapData(&bitmap1);
        freeBitmapData(&bitmap2);
        return -1;
    }

    /* 差分画像を作成 */
    for(j = 0; j < diff.height; j++) {
        for(i = 0; i < diff.width; i++) {
            for(c = 0; c < diff.ch; c++){
                /* 座標(i,j)の色cにアクセスする添字を決定 */
                index = c + diff.ch * i + diff.ch * diff.width * j;

                /* 2つの画像の輝度値の差を算出 */
                diffNum = (int)(bitmap1.data[index]) - (int)(bitmap2.data[index]);

                /* 差分画像の輝度値を計算して差分画像の輝度値にセット */
                diff.data[index] = (unsigned char)((diffNum + 256) / 2);

            }
        }
    }

    /* 画像データを解放 */
    freeBitmapData(&bitmap1);
    freeBitmapData(&bitmap2);

    /* 差分画像をJPEGファイルとして保存 */
    sprintf(outname, "%s", "diff.jpg");
    if(jpegFileEncodeWrite(&diff,outname) == -1){
        printf("encode or write error[%s]\n", outname);
        freeBitmapData(&diff);
        return -1;
    }

    /* 画像データを解放 */
    freeBitmapData(&diff);

    return 0;
}

コンパイル

コンパイルを行う上で必要なソースコードファイルは下記の3つです。

  • main.c:このページで紹介しているソースコード
  • myJpeg.c:JPEG 読み込み・書き込み用のソースコード
  • myJpeg.h:JPEG 読み込み・書き込みようのヘッダーファイル

myJpeg.cmyJpeg.h は下記ページで公開していますので、コピペして同じファイル名で保存して使用していただければと思います。

libjpegの使い方解説ページアイキャッチ 【C言語】libjpegのインストールと使用方法・使用例

またJPEG の読み込みと書き込みを行うため、libjpeg をインストールしておく必要があります。

libjpeg をインストールしておくとC言語で JPEG ファイルを使ったプログラムが簡単に作れるようになりますので興味があればインストールしておくことをオススメします。

この libjpeg のインストール方法についても上記のページで紹介していますので、こちらを参考にしてインストールしていただければと思います。

gcc を用いたコマンドラインからのコンパイルは下記で行うことができます。

> gcc myJpeg.c -c
> gcc main.c -c
> gcc myJpeg.o main.o -ljpeg -o main.exe

-ljpeg を付けることで libjpeg ライブラリをリンクしています。

スポンサーリンク

実行方法

プログラムの実行は、コンパイルで生成した実行可能ファイル(main.exe)を下記のように1つの引数を指定して実行します。

./main.exe cat1.jpg cat2.jpg

指定する引数は下記になります。

  • 第1引数:1つ目の JPEG 画像ファイルへのパス
  • 第2引数:2つ目の JPEG 画像ファイルへのパス

実行すると、第1引数と第2引数との差分画像が diff.jpg という名前の JPEG ファイルとして保存されます。

プログラムの解説

ではプログラムのポイントになる部分を解説していきたいと思います。

JPEG 画像の読み込み

下記の jpegFileReadDecode 関数の実行により、第1引数と第2引数で指定されたパスの JPEG 画像の読み込みとデコードを行なっています。

JPEG 画像の読み込み
    /* 1つ目のJPEGファイルの読み込みとデコード */
    if(jpegFileReadDecode(&bitmap1, argv[1]) == -1){
        printf("read or decode error[%s]\n", argv[1]);
        return -1;
    }

    /* 2つ目のJPEGファイルの読み込みとデコード */
    if(jpegFileReadDecode(&bitmap2, argv[2]) == -1){
        printf("read or decode error[%s]\n", argv[2]);
        return -1;
    }

デコード後の BITMAP データは BITMAPDATA_t 型の変数 bitmap1 及び bitmap2 のメンバである data ポインタが指すことになります。

この bitmap1bitmap2 それぞれの data ポインタが指すアドレスのデータの差分を取っていくことで差分画像を作成していくことになります。

また、変数 bitmap1bitmap2 の data 以外の各メンバには jpegFileReadDecode 関数内で読み込んだ JPEG 画像(元画像)の情報が格納されます。

具体的には下記の情報が格納されます。

  • width:読み込んだ JPEG 画像の幅
  • height:読み込んだ JPEG 画像の高さ
  • ch:読み込んだ JPEG 画像の色数

この jpegFileReadDecode 関数や BITMAPDATA_t 構造体については下記ページで説明していますので必要に応じて参照してください。

libjpegの使い方解説ページアイキャッチ 【C言語】libjpegのインストールと使用方法・使用例

また、下記ページで紹介しているライブラリやソースコードを利用すれば、JPEG 以外のファイルの読み込みやデコードも行うことができます。

libtiffの使い方解説ページアイキャッチ 【C言語】LibTIFFのインストールと使用方法・使用例 libpngの使い方解説ページアイキャッチ 【C言語】libpngのインストールと使用方法・使用例 libwebpの使い方解説ページアイキャッチ 【C言語】libwebpのインストールと使用方法・使用例

エラーチェック

また、下記ではエラーチェックを行なっています。

エラーチェック
    /* アルファチャンネルは非サポート */
    if (bitmap1.ch == 4 || bitmap2.ch == 4) {
        printf("Not support alpha channel\n");
        return -1;
    }

    /* サイズが違ったら差分は作れないのでエラー */
    if (bitmap1.width != bitmap2.width ||
        bitmap1.height != bitmap2.height ||
        bitmap1.ch != bitmap2.ch) {
        printf("Not same size\n");
        return -1;
    }

今回紹介するプログラムでは、画像サイズや色数が異なる画像同士の差分画像作成には対応していないため、引数で指定された2つの画像のサイズや色数を比較し、異なる場合はエラーにしています。

差分画像データのセット

下記では差分画像用の情報を格納する BITMAPDATA_t 構造体の変数 diff の各メンバに情報をセットしています。

差分画像データのセット
    /* 差分画像の情報セット */
    diff.width = bitmap1.width;
    diff.height = bitmap1.height;
    diff.ch = bitmap1.ch;

    /* 差分画像データ格納用のメモリ確保 */
    diff.data = (unsigned char*)malloc(sizeof(unsigned char) * diff.width * diff.height * diff.ch);
    if (diff.data == NULL) {
        printf("malloc error\n");
        freeBitmapData(&bitmap1);
        freeBitmapData(&bitmap2);
        return -1;
    }

引数で指定された画像の情報がセットされている変数 bitmap1widthheightch と同じ値を diff にセットしています(bitmap2widthheightch をセットするのでも良いです)。

さらに、差分画像の BITMAP データを格納するために malloc 関数で 輝度値の個数 * 1 バイト分のメモリを確保しています。

輝度値の個数は、画像の幅 * 画像の高さ * 画像の色数 で計算することができます。

差分画像の作成

で、次に行なっているのが本題である差分画像の作成です。

差分画像の作成
    /* 差分画像を作成 */
    for(j = 0; j < diff.height; j++) {
        for(i = 0; i < diff.width; i++) {
            for(c = 0; c < diff.ch; c++){
                /* 座標(i,j)の色cにアクセスする添字を決定 */
                index = c + diff.ch * i + diff.ch * diff.width * j;

                /* 2つの画像の輝度値の差を算出 */
                diffNum = (int)(bitmap1.data[index]) - (int)(bitmap2.data[index]);

                /* 差分画像の輝度値を計算して差分画像の輝度値にセット */
                diff.data[index] = (unsigned char)((diffNum + 256) / 2);

            }
        }
    }

実際に差分画像の輝度値を求めているのは下記になります。

差分画像の輝度値の算出
/* 2つの画像の輝度値の差を算出 */
diffNum = (int)(bitmap1.data[index]) - (int)(bitmap2.data[index]);

/* 差分画像の輝度値を計算して差分画像の輝度値にセット */
diff.data[index] = (unsigned char)((diffNum + 256) / 2);

ポイントの1つ目は diffNum の型です。

前述の通り、2つの画像の輝度値の差は負の値になる &  8 bit では表現できないので、diffNum の型は負の値も扱える &  8 bit よりも大きな値が扱える int 型にしています。

さらに、この diffNum8 bit  & 正の値のみで表すために、diffNum を +256 をし、さらにその足し算結果を 2 で割ってから *diffPtr に格納しています。

また、index は画像の先頭アドレスから画像の座標 (i, j) の画素の色 c の値が格納されたアドレスまでどれだけ離れているかを計算した結果が格納されています。

輝度値へアクセスするためのインデックス計算
/* 座標(i,j)の色cにアクセスする添字を決定 */
 index = c + diff.ch * i + diff.ch * diff.width * j;

この index を画像の先頭アドレス(を格納したポインタ)に足し算したり、上記のプログラムのように配列の添字として指定することで、画像の座標 (i, j) の画素の色 c の値にアクセスすることができます。

で、これらを下記のループで画像の全輝度値に対して実行することで、差分画像を作成しています。

画像の全輝度値に対するループ
for(j = 0; j < diff.height; j++) {
    for(i = 0; i < diff.width; i++) {
        for(c = 0; c < diff.ch; c++){

        }
    }
}

JPEG 画像の保存

最後に下記で jpegFileEncodeWrite 関数を実行し、差分画像を JPEG ファイルとして保存しています。

JPEG保存
    /* 差分画像をJPEGファイルとして保存 */
    sprintf(outname, "%s", "diff.jpg");
    if(jpegFileEncodeWrite(&diff,outname) == -1){
        printf("encode or write error[%s]\n", outname);
        freeBitmapData(&diff);
        return -1;
    }

この jpegFileEncodeWrite 関数についても下記ページで説明していますので必要に応じて参照してください。

libjpegの使い方解説ページアイキャッチ 【C言語】libjpegのインストールと使用方法・使用例

作成できる差分画像

引数で指定して入力する画像をこのページの最初にお見せした画像、つまり、下の画像と

差分画像を生成する元画像の1つ目

下の画像とした場合、

差分画像を生成する元画像の2つ目

プログラムを実行すると下の画像のような差分が生成されます。

作成した差分画像

ほとんどの画素は R=128、G=128、B=128 の灰色です。が、黒色になっている画素もありますね!ここが1つ目と2つ目の画像とで差がある画素になります。

ヒゲだ!

2つ目の画像には余分にヒゲが書いてあったんだね

そう!

差分画像で確認すると2つの画像の違いが一瞬でわかるよね!

スポンサーリンク

差分をよりわかりやすくする

差分画像を作るプログラムで紹介したプログラムにより差分画像を作成できるようになりました。

ただ、このプログラムは2つの画像の差分画像どれくらいあるかを確認するのには適しているのですが、小さな差分がどこにあるかを確認する時には不便な場合があります、

例えば下の画像と、

差分画像を生成する元画像の1つ目

下の画像の差分画像を作ってみましょう!

差分画像を生成する元画像の3つ目

結果は下の画像のようになりました。

作成した差分画像2

うーん、ぼんやりしててよく分からないね…

そうだよね

差分が少ないのは分かるんだけど、どこに差分があるかはちょっと分かりにくい…

なので、差分が分かりやすいようにちょっと工夫をしてみよう!

単なる差分画像だと差分を確認しにくい場合もあります。ですが、ちょっと工夫すれば差分より分かりやすく表現することもできます。

次は差分がもっと分かりやすくなるようにする工夫例を紹介していきたいと思います。

差分のある輝度値を 255 にする

1つの方法は差分のある輝度値を全て 255 に、差分のない輝度値を全て 0 にするという方法です。

この方法で作成した画像では、どれくらい差分が発生しているかは分からないですが、どこに差分が発生しているかがはっきりと分かります。

これを行うためには、差分画像を作るプログラムのソースコードの下記部分を、

変更前
diff.data[index] = (unsigned char)((diffNum + 256) / 2)

下記のように変更すれば良いです。

変更後
if (diffNum != 0) {
    diff.data[index] = 255;
} else {
    diff.data[index] = 0;
}

上記のように変更を行うことで、この章の最初に見せた2つの画像の差分画像は下の画像のようになります。

差分のある輝度値を最大にした差分画像

黒色の画素以外は差分のあった画素になります。

うわっ、こんなに差分あったんだね…

気付かなかった…

こんな感じで差分のある画素を強調するだけで、どの画素に差があるかが一目で分かるようになるよ

差分の大きな輝度値を 255 にする

先ほどは差分のある輝度値を全て 255 にしました。つまり、差分が 0 以外の場合は全て輝度値を 255 にしていました。

ここでは、ある閾値を設け、この閾値を超えた場合に 255 にする方法で差分画像を作成しようと思います。

つまり、差分の大きな輝度値のみを 255 にし、それ以外は輝度値を 0 にします。

これにより、どの画素で2つの画像間の差分が大きいかが分かりやすくなります。

これを行うためには、差分画像を作るプログラムのソースコードの下記部分を、

変更前
diff.data[index] = (unsigned char)((diffNum + 256) / 2)

下記のように変更すれば良いです。

変更後
if (abs(diffNum) > 30) {
    diff.data[index] = 255;
} else {
    diff.data[index] = 0;
}

↑ では差分の絶対値が 30 を超えた場合に輝度値を 255 にするようにしています。

上記のように変更を行うことで、この章の最初に見せた2つの画像の差分画像は下の画像のようになります。

差分の大きい輝度値を強調した差分画像

黒色の画素は輝度値の差分が小さい画素で、それ以外は輝度値の差分が大きい画素になります。

例えばヒゲのあたりにいっぱい差分あるってことかな?
そうそう!

こんな感じで差分の大きいところだけ強調することで、どこに大きな違いがあるかが分かりやすくなるよ

スポンサーリンク

元画像に対して差分のある画素に色をつける

差分画像だけ見ても、それが元画像のどの位置の差分かが分かりにくいことがあります。

なので、2つの画像の差分のある画素に対して、その画素のみを特定の色にすることで、元々の画像のどこに差分が発生しているかをより分かりやすくします。

これを行うためには、差分画像を作るプログラムのソースコードの差分画像を作成するループ処理を下記のように変更すれば良いです。

変更後
/* 差分画像を作成 */
for(j = 0; j < diff.height; j++) {
    for(i = 0; i < diff.width; i++) {
        int diffR, diffG, diffB;

        /* 座標(i,j)にアクセスする添字を決定 */
       index = diff.ch * i + diff.ch * diff.width * j;

        /* 2つの画像の輝度値の差を算出 */
        diffR = (int)(bitmap1.data[index + 0]) - (int)(bitmap2.data[index + 0]);
        diffG = (int)(bitmap1.data[index + 1]) - (int)(bitmap2.data[index + 1]);
        diffB = (int)(bitmap1.data[index + 2]) - (int)(bitmap2.data[index + 2]);

        if (diffR == 0 && diffG == 0 && diffB == 0) {
            /* 差分のない画素はそのまま設定 */
            diff.data[index + 0] = bitmap1.data[index + 0];
            diff.data[index + 1] = bitmap1.data[index + 1];
            diff.data[index + 2] = bitmap1.data[index + 2];
        } else {
            /* 差分のある画素は緑色に設定 */
            diff.data[index + 0] = 0;
            diff.data[index + 1] = 255;
            diff.data[index + 2] = 0;
        }
    }
}

このページの最初に載せた2つの画像に対して実行すると、結果の画像下のようになります。

差のある部分を緑色にして差分を表示した画像

緑色の部分が差分のある画素になります。色は周りにあまり無い色に設定するとより差分が確認しやすくなります。

あ、これ一番好きかも!

差分が分かりやすい!

元画像と一緒に確認できるからいいよね!

でも差分のある画素が多い場合、画像が緑一色になって余計に分かりにくくなったりするから注意も必要だよ

元画像に対して差分のある画素に色を混ぜる

元画像に対して差分のある画素に色をつけるで紹介したプログラムでは元画像の差分のある画素をそのまま特定の色に置き換えるため(先程の例では緑色)、特に差分のある画素が少ないと差分が確認しやすいです。

ですが、差分のある画素が多いと、その色一色の画像になってしまってむしろ差分が分かりにくくなってしまいます。

例えばこの章の最初にお見せした画像2つに対して元画像に対して差分のある画素に色をつけるで紹介したプログラムを実行して作成される画像は下の図のようになります…。

差のある部分を緑色にして差分を表示した画像2

なので、今度はもうちょっと工夫を施し、そのまま画素を置き換えてしまうのではなく、元々の画素に特定の色を混ぜて差分画像を作成するようにしたいと思います。

これにより、元々の画像の見た目を保持しつつ、差分もその画像上に表現することができるようになります。

画素に特定の色を混ぜる処理は、下記により行うことができます。

画素に色を混ぜる
R = 元々の画素のR * (1 - α) + 混ぜる色のR * α;
G = 元々の画素のG * (1 - α) + 混ぜる色のG * α;
B = 元々の画素のB * (1 - α) + 混ぜる色のB * α;

ですので、差分の無い画素に対しては元々の画素を、差分のある画素に対しては上記により特定の色を混ぜた画素を設定するようにすることで、差分のある画素のみ特定の色を混ぜた画像にすることができます。

α は元々の画素に対してどれくらいの度合いで色を混ぜるかを設定するパラメータになります。α には 01 の値を設定でき、α が大きいほど混ぜる色の方が元々の画素の色よりも濃くなります。

ここでは「混ぜる色」は “緑色 (R=0, G=255, B=0)”、α=0.25 としたいと思います。

で、これを行うには元画像に対して差分のある画素に色をつけるで紹介したプログラムの下記部分を

変更前
/* 差分のある画素は緑色に設定 */
diff.data[index + 0] = 0;
diff.data[index + 1] = 255;
diff.data[index + 2] = 0;
変更後
/* 差分のある画素は緑色を混ぜて輝度値を設定 */
diff.data[index + 0] = bitmap1.data[index + 0] * 0.75;
diff.data[index + 1] = bitmap1.data[index + 1] * 0.75 + 255 * 0.25;
diff.data[index + 2] = bitmap1.data[index + 2] * 0.75;

この章の最初に載せた2つの画像に対して実行すると、結果の画像下のようになります。

差のある部分に緑を混ぜて差分を表示した画像

緑色が混ざった画素の部分が差分のある箇所になります(上の例だとほとんどがそうですが…)。色は画像内にあまり無い色に設定するとより差分が確認しやすくなります。

ちなみに元画像に対して差分のある画素に色をつけるで紹介したプログラムで同じ画像に対して差分画像を作ると下の図のようになります。

なるほど!

いろんな差分画像を作ることで2つの画像の違いより分かりやすくなるね!

そうそう!

差分をとる2つの画像の特徴や、確認したい差分に応じて作る差分画像を選択することが重要だよ!

こんな感じで差分の特徴や確認したいことに応じて工夫を施せばより分かりやすい差分画像を作成することができます。

まとめ

このページでは2つの画像に対する差分画像を作成する方法について解説しました!

差分画像を作成するときの基本的な考え方は「2つの画像の各画素の輝度値の差を求める」です。

ただし、差を求めると下記のような問題があるので、それぞれに対して対策を行う必要があるので注意が必要です。

  • 輝度値が負の値になる
  • 輝度値が 8 bit で表せない

さらに工夫を施すことで差分画像をより分かりやすいものにすることも可能です!

画像処理などを行う場合は処理前後の差を確認したくなることも多いですので、是非そんな時はこのページで解説した内容で差分画像を作成してみてください!

同じカテゴリのページ一覧を表示