/* 最大値・最小値画像を用いた画像差分のサンプルコードです。 このソースコードは位置決めの済んだ画像を対象に画像差分をして、変化を検出し欠陥を見つけようとするものです。 マスタ画像と検査対象画像の差の絶対値を取る単純な画像差分では、環境変動により多くの過検出が生じる問題があります。 そこでこのサンプルコードでは、予め算出した画素ごとの最大値と最小値を用いて、過検出を抑制した画像差分を行います。 画素ごとの最大値・最小値の算出手法として、モルフォロジ演算と画像の統計情報の利用の2種類を提示します。 */ #include "fie.h" #include "oal_aloc.h" //------------------------------------------------ // 関数プロトタイプ INT fnSMP_image_diff_execute(VOID *handle, FHANDLE h_target_img, FHANDLE h_dst); VOID *fnSMP_image_diff_morphology_setup(FHANDLE h_master_img, INT filter_num, INT *errcode); VOID *fnSMP_image_diff_stat_setup(FHANDLE *master_imgs, INT master_num, DOUBLE min_thresh, DOUBLE stddev_factor, INT *errcode); VOID fnSMP_image_diff_teardown(VOID* handle); //------------------------------------------------ // 画像差分の実装 /* 最大値・最小値画像を用いた画像差分のデスクリプタ */ typedef struct { // 最小値マスタ画像 FHANDLE h_min_img; // 最大値マスタ画像 FHANDLE h_max_img; } SMP_IMAGE_DIFF_DESC; /* 最大値・最小値画像を用いた画像差分の実行。 handle に保存されたマスタ画像の画素ごとの最大値・最小値情報をもとに h_target_img の画像差分を行い、結果を h_dst に保存します。 出力画像はマスタ画像群と同じ画像型・サイズ・チャネル数である必要があります。 引数: [in] handle 画像差分のデスクリプタハンドル [in] h_target_img 検査対象画像 ( type: uc8 / ch: 1 ) [out] h_dst 出力画像 ( type: uc8 / ch: 1 ) 戻り値: F_ERR_NONE 正常終了 F_ERR_INVALID_IMAGE 画像が不正のとき F_ERR_INVALID_PARAM パラメータ不正 F_ERR_NOMEMORY メモリ不足エラー F_ERR_NO_LICENCE ライセンスエラー、または未初期化エラー */ INT fnSMP_image_diff_execute(VOID *handle, FHANDLE h_target_img, FHANDLE h_dst) { // 各種画像のパラメータ(検査対象画像、出力画像、最大値画像、最小値画像) INT tgt_width, tgt_height, tgt_channels, tgt_type; INT dst_width, dst_height, dst_channels, dst_type; INT max_width, max_height, max_channels, max_type; INT min_width, min_height, min_channels, min_type; // デスクリプタ SMP_IMAGE_DIFF_DESC *desc = (SMP_IMAGE_DIFF_DESC*)handle; // 検査対象画像が最大値・最小値を外れた大きさを表す作業用差分画像 FHANDLE h_work_brighter = NULL, h_work_darker = NULL; // エラーコード INT err = F_ERR_UNKNOWN; // 画像パラメータ取得 err = F_ERR_INVALID_PARAM; if (desc == NULL) { goto exit; } err = fnFIE_img_get_params(h_target_img, &tgt_channels, &tgt_type, NULL, &tgt_width, &tgt_height); if (err != F_ERR_NONE) { goto exit; } err = fnFIE_img_get_params(h_dst, &dst_channels, &dst_type, NULL, &dst_width, &dst_height); if (err != F_ERR_NONE) { goto exit; } err = fnFIE_img_get_params(desc->h_max_img, &max_channels, &max_type, NULL, &max_width, &max_height); if (err != F_ERR_NONE) { goto exit; } err = fnFIE_img_get_params(desc->h_min_img, &min_channels, &min_type, NULL, &min_width, &min_height); if (err != F_ERR_NONE) { goto exit; } // エラーチェック err = F_ERR_INVALID_IMAGE; // 幅が異なる if (tgt_width != dst_width || tgt_width != max_width || tgt_width != min_width) { goto exit; } // 高さが異なる if (tgt_height != dst_height || tgt_height != max_height || tgt_height != min_height) { goto exit; } // チャネル数が異なるか1でない if (tgt_channels != dst_channels || tgt_channels != max_channels || tgt_channels != min_channels || tgt_channels != 1) { goto exit; } // 型が異なるかuc8でない if (tgt_type != dst_type || tgt_type != max_type || tgt_type != min_type || tgt_type != F_IMG_UC8) { goto exit; } // 作業用画像の確保 h_work_brighter = fnFIE_img_root_alloc(tgt_type, tgt_channels, tgt_width, tgt_height); h_work_darker = fnFIE_img_root_alloc(tgt_type, tgt_channels, tgt_width, tgt_height); if (h_work_brighter == NULL || h_work_darker == NULL) { err = F_ERR_NOMEMORY; goto exit; } // 最大値側の差分画像の算出。対象画像 - 最大値画像 err = fnFIE_img_sub(h_target_img, desc->h_max_img, h_work_brighter); if (err != F_ERR_NONE) { goto exit; } // 最小値側の差分画像の算出。最小値画像 - 対象画像 err = fnFIE_img_sub(desc->h_min_img, h_target_img, h_work_darker); if (err != F_ERR_NONE) { goto exit; } // 結果画像は2つの差分画像の和 err = fnFIE_img_add(h_work_brighter, h_work_darker, h_dst); if (err != F_ERR_NONE) { goto exit; } err = F_ERR_NONE; exit: // 作業用画像の解放 fnFIE_free_object(h_work_brighter); fnFIE_free_object(h_work_darker); return err; } /* モルフォロジ演算を用いた画像差分のセットアップ。 与えられたマスタ画像に膨張と収縮を施した画像を格納したハンドルを作成します。 このハンドルを用いて、続く fnSMP_image_diff_execute() 関数により画像差分を実行します。 マスタ画像はUC8型の1チャネルである必要があります。 引数: [in] h_master_img マスタ画像 ( type: uc8 / ch: 1 ) [in] filter_num モルフォロジ演算実行回数(1以上) [out] errcode エラーコード。エラーコードを受け取る必要の無い場合は、NULLを指定します。 - F_ERR_NONE 正常終了 - F_ERR_INVALID_IMAGE 画像が不正のとき - F_ERR_INVALID_PARAM パラメータ不正 - F_ERR_NOMEMORY メモリ不足エラー - F_ERR_NO_LICENCE ライセンスエラー、または未初期化エラー 戻り値: 正常終了した場合は、画像差分のデスクリプタのハンドルを返します。 異常終了した場合はNULLを返します。 */ VOID *fnSMP_image_diff_morphology_setup(FHANDLE h_master_img, INT filter_num, INT *errcode) { // マスタ画像パラメータ INT width, height, channels, type; // デスクリプタ SMP_IMAGE_DIFF_DESC *desc = NULL; // エラーコード INT err = F_ERR_UNKNOWN; // 画像パラメータ取得とエラーチェック err = fnFIE_img_get_params(h_master_img, &channels, &type, NULL, &width, &height); if (err != F_ERR_NONE) { goto exit; } err = F_ERR_INVALID_PARAM; if (filter_num < 1) { goto exit; } err = F_ERR_INVALID_IMAGE; if (channels != 1) { goto exit; } if (type != F_IMG_UC8) { goto exit; } // デスクリプタとその画像の確保 desc = (SMP_IMAGE_DIFF_DESC*)fnOAL_malloc(sizeof(SMP_IMAGE_DIFF_DESC)); if (desc == NULL) { err = F_ERR_NOMEMORY; goto exit; } desc->h_max_img = fnFIE_img_root_alloc(type, channels, width, height); desc->h_min_img = fnFIE_img_root_alloc(type, channels, width, height); if (desc->h_max_img == NULL || desc->h_min_img == NULL) { err = F_ERR_NOMEMORY; goto exit; } // モルフォロジ演算による最大値・最小値画像の算出 err = fnFIE_dilation(h_master_img, desc->h_max_img, filter_num, 0); if (err != F_ERR_NONE) { goto exit; } err = fnFIE_erosion(h_master_img, desc->h_min_img, filter_num, 0); if (err != F_ERR_NONE) { goto exit; } err = F_ERR_NONE; exit: if (errcode) *errcode = err; if (err == F_ERR_NONE) return desc; // エラー終了の場合はデスクリプタを解放 fnSMP_image_diff_teardown(desc); return NULL; } /* 統計を用いた画像差分のセットアップ。 与えられたマスタ画像群の各画素の平均と標準偏差を用いて算出した最大値画像と最小値画像を格納したハンドルを作成します。 このハンドルを用いて、続く fnSMP_image_diff_execute() 関数により画像差分を実行します。 最大値画像と最小値画像の画素min(x,y), max(x,y)は次の手順で算出されます。 max(x,y) = ave(x,y) + max{min_thresh, stddev(x,y) * stddev_factor} min(x,y) = ave(x,y) - max{min_thresh, stddev(x,y) * stddev_factor} ここで、ave(x,y), stddev(x,y)はそれぞれマスタ画像群の画素(x,y)の平均値と標準偏差です。 すべてのマスタ画像群はUC8型の1チャネルであり、同じサイズである必要があります。 最大値画像と最小値画像の画素値は四捨五入された後サチュレーション処理が施されます。 引数: [in] master_imgs マスタ画像群へのポインタ ( type: uc8, ch: 1 ) [in] master_num マスタ画像数(2以上) [in] min_thresh 最小のしきい値(0以上)。大きいほど画像変動を強く抑制する [in] stddev_factor 標準偏差のしきい値への係数(0より大きい)。大きいほど画像変動を強く抑制する [out] errcode エラーコード。エラーコードを受け取る必要の無い場合は、NULLを指定します。 - F_ERR_NONE 正常終了 - F_ERR_INVALID_IMAGE 画像が不正のとき - F_ERR_INVALID_PARAM パラメータ不正 - F_ERR_NOMEMORY メモリ不足エラー - F_ERR_NO_LICENCE ライセンスエラー、または未初期化エラー 戻り値: 正常終了した場合は、画像差分のデスクリプタのハンドルを返します。 異常終了した場合はNULLを返します。 */ VOID *fnSMP_image_diff_stat_setup(FHANDLE *master_imgs, INT master_num, DOUBLE min_thresh, DOUBLE stddev_factor, INT *errcode) { // マスタ画像パラメータ(全画像共通) INT width, height, channels, type; // デスクリプタ SMP_IMAGE_DIFF_DESC *desc = NULL; // 作業用画像 FHANDLE h_average_img = NULL; // 平均画像 FHANDLE h_stddev_img = NULL; // 標準偏差画像 FHANDLE h_work = NULL; // エラーコード INT err = F_ERR_UNKNOWN; // 引数エラーチェック err = F_ERR_INVALID_PARAM; if (master_imgs == NULL) { goto exit; } if (master_num < 2) { goto exit; } if (min_thresh < 0) { goto exit; } if (stddev_factor <= 0) { goto exit; } // 画像パラメータの取得とエラーチェック err = fnFIE_img_get_params(master_imgs[0], &channels, &type, NULL, &width, &height); if (err != F_ERR_NONE) { goto exit; } err = F_ERR_INVALID_IMAGE; if (channels != 1) { goto exit; } if (type != F_IMG_UC8) { goto exit; } // 配列内の他の画像のチェックは関数fnFIE_stats_img_average()で行われるため省略する // デスクリプタとその画像の確保 desc = (SMP_IMAGE_DIFF_DESC*)fnOAL_malloc(sizeof(SMP_IMAGE_DIFF_DESC)); if (desc == NULL) { err = F_ERR_NOMEMORY; goto exit; } desc->h_max_img = fnFIE_img_root_alloc(type, channels, width, height); desc->h_min_img = fnFIE_img_root_alloc(type, channels, width, height); h_work = fnFIE_img_root_alloc(type, channels, width, height); if (desc->h_max_img == NULL || desc->h_min_img == NULL || h_work == NULL) { err = F_ERR_NOMEMORY; goto exit; } // マスタ画像群の画素ごとの平均と標準偏差を算出 err = fnFIE_stats_img_average(master_imgs, master_num, &h_average_img, F_IMG_UC8); if (err != F_ERR_NONE) { goto exit; } err = fnFIE_stats_img_stddev(master_imgs, master_num, &h_stddev_img, F_IMG_UC8); if (err != F_ERR_NONE) { goto exit; } // 平均画像と標準偏差画像から最大値・最小値画像を算出 err = fnFIE_img_mul_const(h_stddev_img, stddev_factor, h_work); if (err != F_ERR_NONE) { goto exit; } err = fnFIE_img_max_const(h_work, min_thresh, h_work); if (err != F_ERR_NONE) { goto exit; } err = fnFIE_img_add(h_average_img, h_work, desc->h_max_img); if (err != F_ERR_NONE) { goto exit; } err = fnFIE_img_sub(h_average_img, h_work, desc->h_min_img); if (err != F_ERR_NONE) { goto exit; } err = F_ERR_NONE; exit: if (errcode) *errcode = err; // 作業用画像を解放 fnFIE_free_object(h_average_img); fnFIE_free_object(h_stddev_img); fnFIE_free_object(h_work); if (err == F_ERR_NONE) return desc; // エラー終了の場合はデスクリプタを解放 fnSMP_image_diff_teardown(desc); return NULL; } /* 最大値・最小値画像を用いた画像差分に用いるハンドルの解放。 handle に保存されたマスタ画像情報を解放します。 引数: [in] handle 画像差分のデスクリプタハンドル。NULL可 */ VOID fnSMP_image_diff_teardown(VOID *handle) { SMP_IMAGE_DIFF_DESC *desc = (SMP_IMAGE_DIFF_DESC*)handle; if (desc) { fnFIE_free_object(desc->h_max_img); fnFIE_free_object(desc->h_min_img); fnOAL_free(desc); } } //------------------------------------------------ // 実行サンプル /* モルフォロジ演算を用いた画像差分の実行サンプル関数。 ファイルからマスタ画像と検査対象画像を読み込み、出力画像をファイルに保存します。 */ static INT run_image_diff_morphology() { // マスタ画像 FHANDLE himg_master = NULL; // 検査対象画像 FHANDLE himg_target = NULL; // 結果画像 FHANDLE himg_dst = NULL; // デスクリプタハンドル VOID* handle = NULL; INT err; // 画像の読み込み // 適当な画像を読み込んでください err = fnFIE_load_img_file("master.png", &himg_master, F_COLOR_IMG_TYPE_UC8); if (err != F_ERR_NONE) goto exit; err = fnFIE_load_img_file("target.png", &himg_target, F_COLOR_IMG_TYPE_UC8); if (err != F_ERR_NONE) goto exit; // 出力画像の確保 ( type: uc8, ch: 1 ) himg_dst = fnFIE_img_root_alloc(F_IMG_UC8, 1, fnFIE_img_get_width(himg_master), fnFIE_img_get_height(himg_master)); if (himg_dst == NULL) { err = F_ERR_NOMEMORY; goto exit; } // 画像差分の実行 handle = fnSMP_image_diff_morphology_setup(himg_master, 2, &err); if (err != F_ERR_NONE) goto exit; err = fnSMP_image_diff_execute(handle, himg_target, himg_dst); if (err != F_ERR_NONE) goto exit; // 出力画像の保存 err = fnFIE_save_png("dest_morphology.png", himg_dst, -1); if (err != F_ERR_NONE) goto exit; err = F_ERR_NONE; exit: fnFIE_free_object(himg_master); fnFIE_free_object(himg_target); fnFIE_free_object(himg_dst); fnSMP_image_diff_teardown(handle); return err; } /* 統計を用いた画像差分の実行サンプル関数。 ファイルからマスタ画像群と検査対象画像を読み込み、出力画像をファイルに保存します。 */ static INT run_image_diff_stat() { // マスタ画像群 FHANDLE himgs_master[] = { NULL, NULL, NULL, NULL, NULL, }; // マスタ画像群へのファイルパス // 適当な画像を指定してください CHAR* master_paths[] = { "master01.png", "master02.png", "master03.png", "master04.png", "master05.png", }; // マスタ画像数 INT num_masters = sizeof(master_paths) / sizeof(master_paths[0]); // 検査対象画像 FHANDLE himg_target = NULL; // 結果画像 FHANDLE himg_dst = NULL; // デスクリプタハンドル VOID* handle = NULL; INT err, i; // 画像の読み込み for (i = 0; i < num_masters; i++) { err = fnFIE_load_img_file(master_paths[i], &himgs_master[i], F_COLOR_IMG_TYPE_UC8); if (err != F_ERR_NONE) goto exit; } err = fnFIE_load_img_file("target.png", &himg_target, F_COLOR_IMG_TYPE_UC8); if (err != F_ERR_NONE) goto exit; // 出力画像の確保 ( type: uc8, ch: 1 ) himg_dst = fnFIE_img_root_alloc(F_IMG_UC8, 1, fnFIE_img_get_width(himg_target), fnFIE_img_get_height(himg_target)); if (himg_dst == NULL) { err = F_ERR_NOMEMORY; goto exit; } // 画像差分の実行 handle = fnSMP_image_diff_stat_setup(himgs_master, num_masters, 10, 3, &err); if (err != F_ERR_NONE) goto exit; err = fnSMP_image_diff_execute(handle, himg_target, himg_dst); if (err != F_ERR_NONE) goto exit; // 出力画像の保存 err = fnFIE_save_png("dest_stat.png", himg_dst, -1); if (err != F_ERR_NONE) goto exit; err = F_ERR_NONE; exit: for (i = 0; i < num_masters; i++) { fnFIE_free_object(himgs_master[i]); } fnFIE_free_object(himg_target); fnFIE_free_object(himg_dst); fnSMP_image_diff_teardown(handle); return err; } INT main() { INT err; // FIEライブラリの使用前に必ずコールする必要があります。 fnFIE_setup(); err = run_image_diff_morphology(); if (err == F_ERR_NONE) { err = run_image_diff_stat(); } // 終了処理 fnFIE_teardown(); return err; }