見出し画像

画像フィルターの実例として平滑化を具体的に説明

画像フィルターの実例として平滑化を具体的に説明


2024年10月28初稿

解説エッセイ『画像フィルターの概念説明用ソースコード』
で具体的な処理として「平滑化」≪撮影した顔の肌映像を
ファンデーションを塗った様に滑らかに出来る様な処理で
主に撮像素子(ビデオカメラ等)のノイズ除去用として多く
使用される画像処理≫を簡単に紹介したが、具体的な画像処
理ライブラリでのソースコード
でドウ成って居るかをクラス
「Filter」の解説前に事前紹介します!

3×3フィルター演算

の係数「K」が全て「1」の場合が、平滑化なのと特に使用
頻度が高い処理なので専用の関数として

3×3平滑化フィルター演算

を使用する関数の方が処理時間短縮に成る高速化ソース
コードを記述しています!

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」と
平均値を計算して格納!

文末

いいなと思ったら応援しよう!