//---------------------------------------------------------
// 概要      : 最も単純な背景差分法
// File Name : bbs1.cpp
// Library   : OpenCV for MS-Windows 1.0
//---------------------------------------------------------

#include <stdio.h>
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>

#define	THRESHOLD			20				//	差分を求める際の閾値
#define THRESHOLD_MAX_VALUE	255				//	2値化の際に使用する最大値
#define	SCALE				( 1.0 / 255.0 )	//	L*a*b*に変換するために必要なスケールファクタ

#define CAPTURE_OFF			0	//	画像のキャプチャを中止するフラグ値
#define CAPTURE_ON			1	//	画像のキャプチャを開始するフラグ値
#define COLOR_DIFFERENCE	0	//	RGB各成分における差分算出のフラグ値
#define LAB_DIFFERENCE		1	//	L*a*b*表色系における差分算出のフラグ値
#define GRAY_DIFFERENCE		2	//	グレースケールにおける差分算出のフラグ値
#define NOISE_KEEP			0	//	ノイズを除去しないフラグ値
#define	NOISE_MORPHOLOGY	1	//	モルフォロジー演算によるノイズ除去のフラグ値
#define NOISE_MEDIAN		2	//	メディアンフィルタによるノイズ除去のフラグ値

CvSize imageSize;	//	画像のサイズ

//
//	成分ごとに評価をする
//
//	引数:
//		currentImage    : 現在の画像用IplImage
//		backgroundImage : 背景画像用IplImage
//		resultImage     : 結果画像用IplImage
//
void colorDifference( IplImage *currentImage, IplImage *backgroundImage, IplImage *resultImage ){

	//	画像を生成する
	IplImage *differenceImage = cvCreateImage( imageSize, IPL_DEPTH_8U, 3 );	//	差分画像用IplImage
	IplImage *differenceRImage = cvCreateImage( imageSize, IPL_DEPTH_8U, 1 );	//	R値の差分用IplImage
	IplImage *differenceGImage = cvCreateImage( imageSize, IPL_DEPTH_8U, 1 );	//	G値の差分用IplImage
	IplImage *differenceBImage = cvCreateImage( imageSize, IPL_DEPTH_8U, 1 );	//	B値の差分用IplImage

	//	現在の背景との差の絶対値を成分ごとに取る
	cvAbsDiff( currentImage, backgroundImage, differenceImage );

	//	閾値処理を行う
	cvThreshold( differenceImage, differenceImage, THRESHOLD, THRESHOLD_MAX_VALUE, CV_THRESH_BINARY );

	//	成分ごとの画像に分割する
	cvSplit( differenceImage, differenceBImage, differenceGImage, differenceRImage, NULL );

	//	ORで合成する
	cvOr( differenceRImage, differenceGImage, resultImage );
	cvOr( differenceBImage, resultImage, resultImage );

	//	メモリを解放する
	cvReleaseImage( &differenceImage );
	cvReleaseImage( &differenceRImage );
	cvReleaseImage( &differenceGImage );
	cvReleaseImage( &differenceBImage );
}

//
//	L*a*b*で距離を評価する
//
//	引数:
//		currentImage    : 現在の画像用IplImage
//		backgroundImage : 背景画像用IplImage
//		resultImage     : 結果画像用IplImage
//
void labDifference( IplImage *currentImage, IplImage *backgroundImage, IplImage *resultImage ){

	//	画像を生成する
	IplImage *currentLabImage = cvCreateImage( imageSize,IPL_DEPTH_32F, 3 );		//	現在の画像をL*a*b*に変換した画像用IplImage
	IplImage *backgroundLabImage = cvCreateImage( imageSize, IPL_DEPTH_32F, 3 );	//	背景をL*a*b*に変換した画像用IplImage
	IplImage *differenceLabImage = cvCreateImage( imageSize, IPL_DEPTH_32F, 3 );	//	差分画像用IplImage
	IplImage *differenceLImage = cvCreateImage( imageSize, IPL_DEPTH_32F, 1 );		//	L*値の差分用IplImage
	IplImage *differenceAImage = cvCreateImage( imageSize, IPL_DEPTH_32F, 1 );		//	a*値の差分用IplImage
	IplImage *differenceBImage = cvCreateImage( imageSize, IPL_DEPTH_32F, 1 );		//	b*値の差分用IplImage
	IplImage *sqrDifferenceImage = cvCreateImage( imageSize, IPL_DEPTH_32F, 1 );	//	距離算出用IplImage

	//	現在の画像と背景を共に CIE L*a*b* に変換
	cvConvertScale( currentImage, currentLabImage, SCALE );
	cvConvertScale( backgroundImage, backgroundLabImage, SCALE );
	cvCvtColor( currentLabImage, currentLabImage, CV_BGR2Lab );
	cvCvtColor( backgroundLabImage, backgroundLabImage, CV_BGR2Lab );

	//	距離の二乗を計算する
	cvSub( currentLabImage, backgroundLabImage, differenceLabImage );
	cvPow( differenceLabImage, differenceLabImage, 2 );

	//	成分ごとの画像に分割する
	cvSplit( differenceLabImage, differenceLImage, differenceAImage, differenceBImage, NULL );

	cvCopy( differenceLImage, sqrDifferenceImage );
	cvAdd( differenceAImage, sqrDifferenceImage, sqrDifferenceImage );
	cvAdd( differenceBImage, sqrDifferenceImage, sqrDifferenceImage );

	//	閾値処理を行う
	cvThreshold( sqrDifferenceImage, resultImage, THRESHOLD * THRESHOLD, THRESHOLD_MAX_VALUE, CV_THRESH_BINARY );

	//	メモリを解放する
	cvReleaseImage( &currentLabImage );
	cvReleaseImage( &backgroundLabImage );
	cvReleaseImage( &differenceLabImage );
	cvReleaseImage( &differenceLImage );
	cvReleaseImage( &differenceAImage );
	cvReleaseImage( &differenceBImage );
	cvReleaseImage( &sqrDifferenceImage );
}

//
//	グレースケールで評価する
//
//	引数:
//		currentImage    : 現在の画像用IplImage
//		backgroundImage : 背景画像用IplImage
//		resultImage     : 結果画像用IplImage
//
void grayScaleDifference( IplImage *currentImage, IplImage *backgroundImage, IplImage *resultImage ){

	//	画像を生成する
	IplImage *differenceImage = cvCreateImage( imageSize, IPL_DEPTH_8U, 3 );	//	差分画像用IplImage

	//	現在の背景との差の絶対値を成分ごとに取る
	cvAbsDiff( currentImage, backgroundImage, differenceImage );

	//	BGRからグレースケールに変換する
	cvCvtColor( differenceImage, resultImage, CV_BGR2GRAY );

	//	グレースケールから2値に変換する
	cvThreshold( resultImage, resultImage, THRESHOLD, THRESHOLD_MAX_VALUE, CV_THRESH_BINARY );

	//	メモリを解放する
	cvReleaseImage( &differenceImage );
}

int main( void ){
	int key;					//	キー入力用の変数
	CvCapture *capture = NULL;	//	カメラキャプチャ用の構造体

	char windowNameCurrent[] = "Current";		//	現在の画像を表示するウィンドウの名前
	char windowNameResult[] = "Result";			//	背景差分結果を表示するウィンドウの名前
	char windowNameBackground[] = "Background";	//	背景画像を表示するウィンドウの名前

	IplImage *frameImage = NULL;		//	キャプチャ画像用IplImage
	IplImage *currentImage = NULL;		//	現在の画像用IplImage
	IplImage *backgroundImage = NULL;	//	背景画像用IplImage
	IplImage *resultImage = NULL;		//	結果画像用IplImage

	int	captureOn = CAPTURE_ON;				//	背景差分を行う画像を更新するかどうか
	int	differenceMode = COLOR_DIFFERENCE;	//	差分の計算モード
	int	noiseMode = NOISE_KEEP;				//	ノイズを除去するモード

	char *differenceMethod[3] = {
		"RGBの成分ごとに評価",
		"CIE L*a*b* で距離を評価",
		"グレースケールで評価"
	};

	char *noiseMethod[3] = {
		"なし",
		"オープニング",
		"メディアンフィルタ"
	};

	//	カメラを初期化する
	if ( ( capture = cvCreateCameraCapture( -1 ) ) == NULL ){
		//	カメラが見つからなかった場合
		printf( "カメラが見つかりません\n" );
		return -1;
	}
	
	//	ウィンドウを生成する
	cvNamedWindow( windowNameCurrent, CV_WINDOW_AUTOSIZE );
	cvNamedWindow( windowNameResult, CV_WINDOW_AUTOSIZE );
	cvNamedWindow( windowNameBackground, CV_WINDOW_AUTOSIZE );

	//	画像を1枚キャプチャし背景として保存する
	frameImage = cvQueryFrame( capture );
	backgroundImage = cvCloneImage( frameImage );

	//	現在の画像としても1枚確保する
	currentImage = cvCloneImage( frameImage );

	//	画像サイズを保存する
	imageSize = cvSize( frameImage->width, frameImage->height );

	//	2値化結果格納用画像を確保する
	resultImage = cvCreateImage( imageSize, IPL_DEPTH_8U, 1 );
								 
	while( 1 ){
		frameImage = cvQueryFrame( capture );

		if( captureOn != 0){
			if( currentImage != NULL){
				cvReleaseImage( &currentImage );
			}
			currentImage = cvCloneImage( frameImage );
		}

		//	差の計算方法の切り替え
		if( differenceMode == COLOR_DIFFERENCE ){	
			//	成分ごとに評価をする
			colorDifference( currentImage, backgroundImage, resultImage );
		} else if( differenceMode == LAB_DIFFERENCE ){	
			//	L*a*b*で距離を評価する
			labDifference( currentImage, backgroundImage, resultImage );
		} else if( differenceMode == GRAY_DIFFERENCE ){
			//	グレースケールで評価をする
			grayScaleDifference( currentImage, backgroundImage, resultImage );
		}

		//	ノイズ除去
		if( noiseMode == NOISE_MORPHOLOGY ){
			cvErode( resultImage, resultImage );
			cvDilate( resultImage, resultImage );
		}else if ( noiseMode == NOISE_MEDIAN ){
			cvSmooth( resultImage, resultImage, CV_MEDIAN );
		}

		if( resultImage->origin == 0 ){
			//　左上が原点の場合
			cvFlip( resultImage, resultImage, 0 );
		}

		//	画像を表示する
		cvShowImage( windowNameCurrent, currentImage );
		cvShowImage( windowNameResult, resultImage );
		cvShowImage( windowNameBackground, backgroundImage );

		//	キー入力判定
		key = cvWaitKey( 1 );
		if( key == 'q' ){
			//	'q'キーが押されたらループを抜ける
			break;
		} else if( key == 'b' ){
			//	'b'キーが押されたらその時点での画像を背景画像とする
			if( backgroundImage != NULL ){
				cvReleaseImage( &backgroundImage );
			}
			backgroundImage = cvCloneImage( frameImage );
			printf( "背景画像更新\n" );
		} else if( key == ' ' ){
			//	スペースキーが押されたら画像取得のON/OFFを変更する
			captureOn = 1 - captureOn;
			if( captureOn == CAPTURE_ON ){
                printf( "画像取得: ON\n" );
			}else if( captureOn == CAPTURE_OFF ){
				printf( "画像取得: OFF\n" );
			}
		} else if( key == 'm' ){
			//	'm'キーが押されたら差の評価手法を変更する
			differenceMode = differenceMode + 1;
			if( differenceMode > GRAY_DIFFERENCE ){
				differenceMode = COLOR_DIFFERENCE;
			}
			printf( "差の評価方法: %s\n", differenceMethod[differenceMode] );
		} else if( key == 'n' ){ 
			//	'n'キーが押されたらノイズ除去方法を変更する
			noiseMode = noiseMode + 1;
			if( noiseMode > NOISE_MEDIAN ){
				noiseMode = NOISE_KEEP;
			}
			printf( "ノイズ除去方法: %s\n", noiseMethod[noiseMode] );
		}
	}

	//	キャプチャを解放する
	cvReleaseCapture( &capture );
	//	メモリを解放する
	cvReleaseImage( &backgroundImage );
	cvReleaseImage( &currentImage );
	cvReleaseImage( &resultImage );
	//	ウィンドウを破棄する
	cvDestroyWindow( windowNameCurrent );
	cvDestroyWindow( windowNameResult );
	cvDestroyWindow( windowNameBackground );

	return 0;
}
