画像フィルターの実例として平滑化を具体的に説明
画像フィルターの実例として平滑化を具体的に説明
2024年10月28初稿
解説エッセイ『画像フィルターの概念説明用ソースコード』
で具体的な処理として「平滑化」≪撮影した顔の肌映像を
ファンデーションを塗った様に滑らかに出来る様な処理で
主に撮像素子(ビデオカメラ等)のノイズ除去用として多く
使用される画像処理≫を簡単に紹介したが、具体的な画像処
理ライブラリでのソースコードでドウ成って居るかをクラス
「Filter」の解説前に事前紹介します!
の係数「K」が全て「1」の場合が、平滑化なのと特に使用
頻度が高い処理なので専用の関数として
を使用する関数の方が処理時間短縮に成る高速化ソース
コードを記述しています!
1.関連関数
code:平滑化に関するFilter::Smoothing(){}
/************************************************************************/
/***** 平滑化フィルター *****/
/***** SMOOTHING,s,d,ft,fh,fv,clamp,mode *****/
/************************************************************************/
int Filter::Smoothing(
TypeArray *ps, // S画像情報
TypeArray *pd, // D画像情報
const int ft[], // 重み配列
int fh, // 重み水平サイズ
int fv, // 重み垂直サイズ
int mode // 周辺指定モード
// 0:周辺処理無
// 1:周辺0クリア
// 2:周辺コピー
){
TypeArray imgS; // S画像情報:最小サイズ
TypeArray imgD; // D画像情報:最小サイズ
void *ptrs; // S画像Ptr
void *ptrd; // D画像Ptr
int h; // 水平幅
int v; // 垂直幅
int incs; // S画像増加幅
int incd; // D画像増加幅
int sum; // 重み合計
int sti; // ステータス情報
sti = CheckFilterImage( ps, pd, fh, fv ); // SD画像検査
if( sti != END_STI ){ // エラー在りなら
return( sti ); // ステータスを返す
} //
sum = sumSmoothing( ft, fh * fv ); // 重みを合計
if( sum == 0 ){ // 合計が0なら
return( STI_RUN ); // 左記を返す
} //
h = ps->h; // 最小の水平幅を
if( h > pd->h ){ // SDから
h = pd->h; // 取り出す
} //
v = ps->v; // 最小の垂直幅を
if( v > pd->v ){ // SDから
v = pd->v; // 取り出す
} //
imgS.subset( ps, 0, 0, h, v ); // 部分画像を
imgD.subset( pd, 0, 0, h, v ); // 作成し
ps = &imgS; // 画像情報ポインタを
pd = &imgD; // 付け替える
ptrs = (void*)ps->adr; // SD画像の画像Ptr
ptrd = (void*)pd->adr; // 始点を取り出す
incs = ps->inc; // SD画像の増加幅
incd = pd->inc; // を取り出す
if( ps->w == 1 ){ // BYTE単位画素なら
if( pd->w == 1 ){ // D=BYTE単位なら
smg_byte_filter( // 左記で処理
(BYTE*)ptrs, (BYTE*)ptrd, ft, sum, //
h, v, incs, incd, fh, fv ); //
}else{ // D=short単位なら
smg_byte_short_filter( // 左記で処理
(BYTE*)ptrs, (short*)ptrd, ft, //
sum, h, v, incs, incd, fh, fv ); //
} //
}else if( ps->w == 2 ){ // short単位画素なら
smg_short_filter( // 左記で処理
(short*)ptrs, (short*)ptrd, ft, //
sum, h, v, incs, incd, fh, fv ); //
}else{ // long単位画素なら
smg_long_filter( // 左記で処理
(long*)ptrs, (long*)ptrd, ft, //
sum, h, v, incs, incd, fh, fv ); //
} //
if( mode == 1 ){ // 1:0クリア時は
ClearRoundImageBase( pd, 0, fv / 2, // 外周クリア
fh / 2 ); //
}else if( mode == 2 ){ // 2:コピー時は
CopyRoundImageBase( ps, pd, fv / 2, // 外周コピー
fh / 2 ); //
} //
return( END_STI ); // 正常終了
}
注目して欲しいのは、
code:if2段分岐
if( ps->w == 1 ){ // BYTE単位画素なら
if( pd->w == 1 ){ // D=BYTE単位なら
smg_byte_filter( // 左記で処理
(BYTE*)ptrs, (BYTE*)ptrd, ft, sum, //
h, v, incs, incd, fh, fv ); //
}else{ // D=short単位なら
smg_byte_short_filter( // 左記で処理
(BYTE*)ptrs, (short*)ptrd, ft, //
sum, h, v, incs, incd, fh, fv ); //
} //
}else if( ps->w == 2 ){ // short単位画素なら
smg_short_filter( // 左記で処理
(short*)ptrs, (short*)ptrd, ft, //
sum, h, v, incs, incd, fh, fv ); //
}else{ // long単位画素なら
smg_long_filter( // 左記で処理
(long*)ptrs, (long*)ptrd, ft, //
sum, h, v, incs, incd, fh, fv ); //
} //
で元画像を示す「ps->w」と結果画像を示す「pd->w」が
1・2バイト整数型の組み合わせとそれ以外とに専用関数を
分ける事で本当に使用頻度の高い「smg_byte_filter()」≒
元画像・結果画像共1バイト整数型の処理等に分岐します!
この解説では、smg_byte_filter()に関して説明します!
2.1バイト整数画素専用関数
code:smg_byte_filter(){}
/************************************************************************/
/***** 平滑化フィルター:実行部:バイト単位 *****/
/***** SMOOTHING,s,d,ft,fh,fv *****/
/************************************************************************/
void Filter::smg_byte_filter(
BYTE *ps, // S画像Ptr
BYTE *pd, // D画像Ptr
const int *ft, // 重み配列
int sum, // 重み合計
int h, // 水平幅
int v, // 垂直幅
int incs, // S画像増加幅
int incd, // D画像増加幅
int wh, // 水平Win幅
int wv // 垂直Win幅
){
pd += wh / 2 + ( wv / 2 ) * incd; // Dptr格納位置補正
h -= wh / 2 * 2; // 水平処理幅を補正
v -= wv / 2 * 2; // 垂直処理幅を補正
if( wh == 3 && wv == 3 ){ // 3×3なら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_3_3( ps, pd, ft, sum, h, // 水平の処理を行い
incs ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wv == 1 ){ // H×1なら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_hh( ps, pd, ft, sum, h, wh ); // 水平の処理を行い
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wh == 1 ){ // 1×Vなら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_1( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wh == 3 ){ // 3×Vなら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_3( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wh == 5 ){ // 5×Vなら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_5( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wh == 7 ){ // 7×Vなら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_7( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else{ // 上記以外なら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wh, wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
} //
}
2-1.関数名「smg_byte_filter」
「smg」は、平滑化「Smoothing」の省略形、
「byte」は、1バイト整数型を意味!
「filter」は、フィルター演算を意味!
2-2.仮引数
void Filter::smg_byte_filter(
BYTE *ps, // S画像Ptr
BYTE *pd, // D画像Ptr
const int *ft, // 重み配列
int sum, // 重み合計
int h, // 水平幅
int v, // 垂直幅
int incs, // S画像増加幅
int incd, // D画像増加幅
int wh, // 水平Win幅
int wv // 垂直Win幅
){
「BYTE* ps,」は、元画像実画素へのポインタ
「BYTE* pd,」は、結果画像実画素へのポインタ
「const int* ft,」は、重み配列、数式を図示した式での
係数K配列と考えて下さい!※備考※私のうろ覚えで係数K
が全て1の場合の関数と誤解したが、個別に係数を設定出来
る関数に成っていた⇒恐らく、この方式にしても著しい速度
低下が起こらないとベンチマークテストで実証実験を行った
結果と思える!今回、この説明を始めたのでコノママ解説を
行う事に方針変換しました※
「int sum,」は、重み配列の合計値≪平均値を出すので除算
値として使用≫
「int h,」は、画像の水平(X座標)方向処理幅
「int v,」は、画像の垂直(Y座標)方向処理幅
「int incs,」は、元画像の増加幅
「int incd,」は、結果画像の増加幅
「int wh,」は、水平ウィンドウ幅≪3×3とn×mフィル
ター演算ウィンドウの水平方向n≫※注目:ライブラリの
ソースコードは「h」を水平(X座標)方向の意味に使用
「int wv,」は、垂直ウィンドウ幅≪3×3とn×mフィル
ター演算ウィンドウの垂直方向m≫※注目:ライブラリの
ソースコードは「v」を垂直(Y座標)方向の意味に使用
2-3.アルゴリズム
){
pd += wh / 2 + ( wv / 2 ) * incd; // Dptr格納位置補正
h -= wh / 2 * 2; // 水平処理幅を補正
v -= wv / 2 * 2; // 垂直処理幅を補正
if( wh == 3 && wv == 3 ){ // 3×3なら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_3_3( ps, pd, ft, sum, h, // 水平の処理を行い
incs ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wv == 1 ){ // H×1なら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_hh( ps, pd, ft, sum, h, wh ); // 水平の処理を行い
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wh == 1 ){ // 1×Vなら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_1( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wh == 3 ){ // 3×Vなら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_3( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wh == 5 ){ // 5×Vなら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_5( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else if( wh == 7 ){ // 7×Vなら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h_7( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
}else{ // 上記以外なら
while( --v >= 0 ){ // 垂直方向処理繰返
smg_byte_h( ps, pd, ft, sum, h, incs, // 水平の処理を行い
wh, wv ); //
ps += incs; // 垂直方向増加:S
pd += incd; // 垂直方向増加:D
} //
} //
}
「pd+=wh/2+(wv/2)incd;」は、結果画像の書き込み始点を
補正します!元画像は全体を使用するので左上そのママで
すが、水平垂直のウィンドウ幅の半分の位置にズレる事で
画像全体としてズレる事が無く成ります!
「h-=wh/22;v-=wv/2*2;」は、結果画像の水平垂直の処理幅
が、ウィンドウ幅の半分の位置にズレる事に連動して幅を
補正≪減る≫が≪「/2」と半分にスルのは中心への位置ずれ
で「*2」は上下・左右と両端あるからです≫
「if(wh==3&&wv==3){・・分岐中身・・}」は、ウィンドウ
幅が「3×3」の場合の処理を記述します※備考:この解説
では、「3×3」の場合の処理だけ解説します!他のif
分岐ソースコードの解説は、クラス「Filter」のソース
コード解説で行います※
「while(--v>=0){・・ループ中身・・}」は、垂直方向に
垂直幅「int v,」を補正した値で繰り返す処理で実際に
平滑化を行う部分です!そのループ中身は、
「smg_byte_h_3_3(ps,pd,ft,sum,h,incs);」とサブルーチン
関数「smg_byte_h_3_3()」で水平方向の処理「関数名_h_は
それを意味」します!
「ps+=incs;pd+=incd;」で元画像・結果画像の画素ポインタ
を垂直方向に進める!
3.1バイト整数画素専用関数サブルーチン関数の水平方向処理関数
code:smg_byte_h_3_3(){}
/************************************************************************/
/***** 平滑化フィルター:バイト単位:3×3専用:水平方向処理 *****/
/***** SMOOTHING,s,d,ft,3,3 *****/
/************************************************************************/
void Filter::smg_byte_h_3_3(
BYTE *ps, // S画像Ptr:上
BYTE *pd, // D画像Ptr
const int *ft, // 重み配列
int sum, // 重み合計
int h, // 水平幅
int incs // S画像増加幅
){
BYTE *p1; // S画像Ptr:中
BYTE *p2; // S画像Ptr:下
const int *pft; // 重み配列Ptr
int data; // 合計データ
p1 = ps + incs; // 中Ptrをセット
p2 = ps + incs * 2; // 下Ptrをセット
while( --h >= 0 ){ // 水平方向に繰返し
pft = ft; // 重み配列Ptrセット
data = *ps++ * *pft++; // 左上を計算
data += *ps++ * *pft++; // 中上を計算
data += *ps++ * *pft++; // 右上を計算
data += *p1++ * *pft++; // 左中を計算
data += *p1++ * *pft++; // 中央を計算
data += *p1++ * *pft++; // 右中を計算
data += *p2++ * *pft++; // 左下を計算
data += *p2++ * *pft++; // 中下を計算
data += *p2++ * *pft++; // 右下を計算
ps -= 2; // 上Ptr次位置を補正
p1 -= 2; // 中Ptr次位置を補正
p2 -= 2; // 下Ptr次位置を補正
*pd++ = data / sum; // 平滑化
} //
}
3-1.関数名「smg_byte_h_3_3」
「smg」は、平滑化「Smoothing」の省略形、
「byte」は、1バイト整数型を意味!
「h」は、水平方向処理を意味!
「_3_3」は、フィルター演算ウィンドウ幅が3×3を意味!
3-2.仮引数
code:仮引数
void Filter::smg_byte_h_3_3(
BYTE *ps, // S画像Ptr:上
BYTE *pd, // D画像Ptr
const int *ft, // 重み配列
int sum, // 重み合計
int h, // 水平幅
int incs // S画像増加幅
){
「BYTE* ps,」は、元画像実画素へのポインタ※注意:
ソースコードのコメントで「S画像のPtr:上」と有るのは
3×3で演算すると言う事は上中下と3本有る事を意味し、
一番上の位置を示します!
「BYTE* pd,」は、結果画像実画素へのポインタ
「const int* ft,」は、重み配列、数式を図示した式での
係数K配列と考えて下さい!※備考※私のうろ覚えで係数K
が全て1の場合の関数と誤解したが、個別に係数を設定出来
る関数に成っていた⇒恐らく、この方式にしても著しい速度
低下が起こらないとベンチマークテストで実証実験を行った
結果と思える!今回、この説明を始めたのでコノママ解説を
行う事に方針変換しました※
「int sum,」は、重み配列の合計値≪平均値を出すので除算
値として使用≫
「int h,」は、画像の水平(X座標)方向処理幅
「int v,」は、画像の垂直(Y座標)方向処理幅
「int incs」は、元画像の増加幅
3-2.ローカル変数
code:ローカル変数
){
BYTE *p1; // S画像Ptr:中
BYTE *p2; // S画像Ptr:下
const int *pft; // 重み配列Ptr
int data; // 合計データ
「BYTE* p1;」は、元画像実画素へのポインタ※注意:
ソースコードのコメントで「S画像のPtr:中」と有るのは
3×3で演算すると言う事は上中下と3本有る事を意味し、
ここでは中の位置です!
「BYTE* p2;」は、元画像実画素へのポインタ※注意:
ソースコードのコメントで「S画像のPtr:下」と有るのは
3×3で演算すると言う事は上中下と3本有る事を意味し、
ここでは下の位置です!
「const int* pft;」は、重み配列での位置をローカルに
3×3で演算する為にループ内先頭で常に先頭をセットする
為のポインタ変数です!
「int data;」は、3×3で演算で計算する合計値!
3-3.アルゴリズム
code:アルゴリズム
p1 = ps + incs; // 中Ptrをセット
p2 = ps + incs * 2; // 下Ptrをセット
while( --h >= 0 ){ // 水平方向に繰返し
pft = ft; // 重み配列Ptrセット
data = *ps++ * *pft++; // 左上を計算
data += *ps++ * *pft++; // 中上を計算
data += *ps++ * *pft++; // 右上を計算
data += *p1++ * *pft++; // 左中を計算
data += *p1++ * *pft++; // 中央を計算
data += *p1++ * *pft++; // 右中を計算
data += *p2++ * *pft++; // 左下を計算
data += *p2++ * *pft++; // 中下を計算
data += *p2++ * *pft++; // 右下を計算
ps -= 2; // 上Ptr次位置を補正
p1 -= 2; // 中Ptr次位置を補正
p2 -= 2; // 下Ptr次位置を補正
*pd++ = data / sum; // 平滑化
} //
}
「p1=ps+incs;」は、※注意:ソースコードのコメントで
「S画像のPtr:中」と有るのは3×3で演算すると言う事
は上中下と3本有る事を意味し、ここでは中の位置をセット
する事です!
「p2=ps+incs*2;」は、※注意:ソースコードのコメントで
「S画像のPtr:下」と有るのは3×3で演算すると言う事
は上中下と3本有る事を意味し、ここでは下の位置をセット
する事です!
「while(--h>=0){・・ループ本体・・}」は、水平方向処理
を水平幅「int h,」繰り返す処理で中身は、
「pft=ft;」で重み配列での位置を先頭にセット
「data=*ps++**pft++;」で「左上」の位置を乗算、
「data+=*ps++**pft++;data+=*ps++**pft++;」で「中上」と
「右上」を連続して乗算し合計値算出!
「data+=*p1++**pft++;」でコメントに有る様に「左中」を
連続して乗算し合計値算出!
「data+=*p1++**pft++;」でコメントに有る様に「中央」を
連続して乗算し合計値算出!
「data+=*p1++**pft++;」でコメントに有る様に「右中」を
連続して乗算し合計値算出!
「data+=*p2++**pft++;」でコメントに有る様に「左下」を
連続して乗算し合計値算出!
「data+=*p2++**pft++;」でコメントに有る様に「中下」を
連続して乗算し合計値算出!
「data+=*p2++**pft++;」でコメントに有る様に「右下」を
連続して乗算し合計値算出!※注意:「左上」から「右下」
まで連続して並べて9画素分の乗算&合計値算出して居る事
で固定数の繰り返しなのでループを使わ無い事で高速化
「ps-=2;p1-=2;p2-=2;」で「++」演算でポインタ変数の移動
を1画素分に補正する!
「*pd++=data/sum;」で結果画像画素に「data/sum」と
平均値を計算して格納!