//---------------------------------------------------------
// 概要      : ジャンケンの判定
// File Name : rockpaperscissors.cpp
// Library   : OpenCV for MS-Windows 1.0
//---------------------------------------------------------

#include "rockpaperscissors.h"

char windowNameCapture[] = "Capture"; 		//	キャプチャした画像を表示するウィンドウの名前
char windowNameSkin[] = "Skin";				//	肌色抽出画像を表示するウィンドウの名前
char windowNameConvexHull[] = "ConvexHull";	//	ConnvexHull画像を表示するウィンドウの名前

//
//　ウィンドウを作成する
//
void createWindows() {
	cvNamedWindow( windowNameCapture );
	cvNamedWindow( windowNameSkin );
	cvNamedWindow( windowNameConvexHull );
}

//
//	肌色を抽出する
//
//	引数:
//		frameImage : キャプチャした画像用IplImage
//		hsvImage   : HSV画像用IplImage
//		skinImage  : 肌色抽出画像用IplImage
//
void extractSkinColor( IplImage *frameImage, IplImage *hsvImage, IplImage *skinImage ) {
	CvScalar color;		//	HSV表色系で表した色
	unsigned char h;	//	H成分
	unsigned char s;	//	S成分
	unsigned char v;	//	V成分
	
	//	BGRからHSVに変換する
	cvCvtColor( frameImage, hsvImage, CV_BGR2HSV );

	//肌色抽出
	for( int x = 0; x < skinImage->width; x++ ) {
		for( int y = 0 ; y < skinImage->height; y++ ) {

			color = cvGet2D( hsvImage, y, x );
			h = color.val[0];
			s = color.val[1];
			v = color.val[2];

			if( h <= HMAX && h >= HMIN &&
				s <= SMAX && s >= SMIN &&
					v <= VMAX && v >= VMIN ) {
				//	肌色の場合
				cvSetReal2D( skinImage, y, x, 255 );
			} else {
				cvSetReal2D( skinImage, y, x, 0 );
			}
		}
	}
}

//
//	欠損領域を補間する
//
//	引数:
//		skinImage : 肌色抽出画像用IplImage
//		temp      : 一時保存用IplImage
//
void interpolate( IplImage *skinImage, IplImage *temp ) {
	//膨張をITERATIONS回行う
	cvDilate( skinImage, temp, NULL, ITERATIONS );

	//収縮をITERATIONS回行う
	cvErode( temp, skinImage, NULL, ITERATIONS );
}

//
//	最大領域(手領域)の抽出を行う
//
//	引数:
//		skinImage       : 肌色抽出画像用IplImage
//		label           : ラベリングした結果
//		convexHullImage : ConvexHull画像用IplImage
//
//	戻り値:
//		手領域の面積
//
int pickupMaxArea(IplImage *skinImage, IplImage *label, IplImage *convexHullImage ) {

	int handarea = 0;	//	手領域の面積

	for(int x = 0; x < skinImage->width; x++ ) {
		for( int y=0; y < skinImage->height; y++ ) {
			if( cvGetReal2D( label, y, x ) == 1 ) {
				//	最大領域だった場合
				handarea++;
				cvSet2D( convexHullImage, y, x, CV_RGB( 255, 255, 255 ) );
			} else {
				cvSetReal2D( skinImage, y, x, 0 );
				cvSet2D( convexHullImage, y, x, CV_RGB( 0, 0, 0 ) );
			}
		}
	}
	return handarea;
}

//
//	ConvexHullを生成する
//
//	引数:
//		skinImage   : 肌色抽出画像用IplImage
//		handarea    : 手領域の面積(点の数)
//		handpoint   : 手領域内の点の座標配列へのポインタ
//		hull        : ConvexHullの頂点のhandpointにおけるindex番号へのポインタ
//		pointMatrix : 手領域用行列へのポインタ
//		hullMatrix  : ConvexHull用行列へのポインタ
//
void createConvexHull(IplImage *skinImage, int handarea, CvPoint **handpoint, int **hull,
					  CvMat *pointMatrix, CvMat *hullMatrix ) {
	int i=0;

	//	ConvexHullを計算するために必要な行列を生成する
	*handpoint=( CvPoint * )malloc( sizeof( CvPoint ) * handarea );
	*hull = ( int * )malloc( sizeof( int ) * handarea );
	*pointMatrix = cvMat( 1, handarea, CV_32SC2, *handpoint );
	*hullMatrix = cvMat( 1, handarea, CV_32SC1, *hull );

	for( int x = 0; x < skinImage->width; x++ ) {
		for(  int y = 0; y < skinImage->height; y++ ) {
			if( cvGetReal2D( skinImage, y, x ) == 255 ) {
				( *handpoint )[i].x = x;
				( *handpoint )[i].y = y;
				i++;
			}
		}
	}

	//	ConvexHullを生成する
	cvConvexHull2( pointMatrix, hullMatrix, CV_CLOCKWISE, 0 );
}

//
//	ConvexHullを描画する
//
//	引数:
//		convexHullImage : ConvexHull画像用IplImage
//		handpoint       : 手領域内の点の座標配列
//		hull            : ConvexHullの頂点のhandpointにおけるindex番号
//		hullcount       : ConvexHullの頂点の数
//
void drawConvexHull(IplImage *convexHullImage, CvPoint *handpoint, int *hull, int hullcount ) {
	CvPoint pt0 = handpoint[hull[hullcount-1]];
	for( int i = 0; i < hullcount; i++ ) {
		CvPoint pt = handpoint[hull[i]];
		cvLine( convexHullImage, pt0, pt, CV_RGB( 0, 255, 0 ) );
		pt0 = pt;
	}
}

//
//	ConvexHull内の面積を求める
//
//	引数:
//		convexHullImage : ConvexHull画像用IplImage
//		handpoint       : 手領域内の点の座標配列
//		hull            : ConvexHullの頂点のhandpointにおけるindex番号
//		hullcount       : ConvexHullの頂点の数　　
//
//	戻り値:
//		ConvexHull内の面積
//
int calcConvexHullArea( IplImage *convexHullImage, CvPoint *handpoint, int *hull, int hullcount ) {

	//	ConvexHullの頂点からなる行列を生成
	CvPoint *hullpoint = ( CvPoint * )malloc( sizeof( CvPoint ) * hullcount );
	CvMat hMatrix = cvMat( 1, hullcount, CV_32SC2, hullpoint );
	for( int i = 0; i < hullcount; i++ ) {
		hullpoint[i]=handpoint[hull[i]];
	}

	//	ConvexHull内の点の数を数える
	int hullarea = 0;
	for( int x = 0; x < convexHullImage->width; x++ ) {
		for( int y = 0;y < convexHullImage->height; y++ ) {
			if( cvPointPolygonTest( &hMatrix, cvPoint2D32f( x, y ), 0 ) > 0) {
				hullarea++;
			}
		}
	}

	free( hullpoint );
	return hullarea;
}

//
//	ジャンケンの判定を行う
//
//	引数:
//		handarea : 手領域の面積
//		hullarea : ConvexHull内の面積
//
void decide( int handarea, int hullarea ) {
	double ratio;	//	ConvexHull内の面積に対する手領域の面積の割合
	
	ratio=handarea / ( double )hullarea;	
	printf( "Ratio = %lf\n", ratio );

	if( ratio >= ROCKMIN && ratio <= ROCKMAX ) {
		printf( "グー\n" );
	} else if( ratio >= SCISSORMIN && ratio <= SCISSORMAX ) {
		printf( "チョキ\n" );
	} else if( ratio >= PAPERMIN && ratio <= PAPERMAX ) {
		printf( "パー\n" );
	}
}

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

	//	カメラを初期化する
	if( ( capture = cvCreateCameraCapture( -1 ) ) == NULL ) {
		//	カメラが見つからなかった場合
		printf( "カメラが見つかりません\n" );
		return -1;
	}
	
	IplImage *frameImage = cvQueryFrame( capture );	//	キャプチャ画像用IplImage

	//	画像を生成する
	IplImage *hsvImage = cvCreateImage( cvGetSize( frameImage ),IPL_DEPTH_8U,3);		//	HSV画像用IplImage
	IplImage *convexHullImage = cvCreateImage( cvGetSize( frameImage ),IPL_DEPTH_8U,3 );//	ConvexHull画像用IplImage
	IplImage *skinImage = cvCreateImage( cvGetSize( frameImage ),IPL_DEPTH_8U,1);		//	肌色抽出画像用IplImage
	IplImage *temp = cvCreateImage( cvGetSize( frameImage ),IPL_DEPTH_8U,1);			//	一時保存用IplImage
	IplImage *label = cvCreateImage( cvGetSize( frameImage ),IPL_DEPTH_16S,1);			//	ラベル結果保存用IplImage

	//	ウィンドウを生成する
	createWindows();

	while( 1 ){

		frameImage = cvQueryFrame( capture );

		//	肌色を抽出する
		extractSkinColor( frameImage, hsvImage, skinImage );

		//	欠損領域を補間する
		interpolate( skinImage, temp );
		
		//	ラベリングを行う
		Label *labeling = createLabeling();
		exec( labeling, skinImage, label, true, IGNORE_SIZE );

		if( getNumOfResultRegions( labeling ) > 0 ) {
			//	IGNORE_SIZEよりも大きな領域があった場合

			int handarea;		//	手領域の面積
			int hullarea;		//	ConvexHull内の面積
			int hullcount;		//	ConvexHullの頂点の数
			CvPoint *handpoint;	//	手領域内の点の座標配列
			int *hull;			//	ConvexHullの頂点のhandpointにおけるindex番号
			CvMat pointMatrix;	//	手領域用行列
			CvMat hullMatrix;	//	ConvexHull用行列

			//	最大領域(手領域)の抽出を行う
			handarea = pickupMaxArea( skinImage, label, convexHullImage );

			//	ConvexHullを生成する
			createConvexHull( skinImage, handarea, &handpoint, &hull, &pointMatrix, &hullMatrix );
			
			hullcount = hullMatrix.cols;

			//	ConvexHullを描画する
			drawConvexHull( convexHullImage, handpoint, hull, hullcount );

			//	ConvexHull内の面積を求める
			hullarea = calcConvexHullArea( convexHullImage, handpoint,hull, hullcount );

			//	ジャンケンの判定を行う
			decide( handarea, hullarea );

			//	メモリを解放する
			free( handpoint );
			free( hull );
		} else {
			//	画像を初期化する
			cvSetZero( convexHullImage );
		}

		releaseLabeling( labeling );

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

		//	画像を表示する
		cvShowImage( windowNameCapture, frameImage );
		cvShowImage( windowNameSkin, skinImage );
		cvShowImage( windowNameConvexHull, convexHullImage );

		//	キー入力判定
		key = cvWaitKey( 1 );
		if( key == 'q' ) {
			//	'q'キーが押されたらループを抜ける
			break;
		}
	}
	
	//	キャプチャを解放する
	cvReleaseCapture( &capture );
	//	メモリを解放する
	cvReleaseImage( &hsvImage );
	cvReleaseImage( &skinImage );
	cvReleaseImage( &temp );
	cvReleaseImage( &label );
	cvReleaseImage( &convexHullImage );
	//	ウィンドウを破棄する
	cvDestroyWindow( windowNameCapture );
	cvDestroyWindow( windowNameSkin );
	cvDestroyWindow( windowNameConvexHull );

	return 0;
}