목표
2대의 Wiimote를 이용하여 3D 공간에 위치한 광점(IRED)의 x,y,z 좌표값을 구한다.

본 기능은 다음의 경우에 응용할 수 있습니다.
1. 3D 스캐너 만들기
2. 헤드 트래킹(단순 위치 파악형 및 3광점 헤드 트래킹)
3. 3D 마우스

※ 3광점 헤드 트래킹:
3개의 IRED를 이용하면 단순히 위치를 파악하는것 뿐아니라 어느방향을 바라보는지도 알수 있습니다. 관련링크 참조

준비물
Wiimote 2개,  IRED(적외선LED) (참고링크2 참조) 
기타 Wiimote 사용을 위해 필요한 게이트웨이 SW 및 BT 장치 등.

Wiimote 설치 및 연동
Wiimote와 Flash를 연동하는 방법은  참고링크1.을 참고하시기 바랍니다. 

Wiimote로 측정 가능한 정보
wiimote에는 3축 가속센서, 여러개의 버튼이 포함되어있고, 내장된 적외선 카메라로 적외선 광점의 좌표값을 4개까지 인식할 수 있습니다. 물론 wiimote에서 출력해주는 좌표값은 카메라 스크린을 기준으로한 x,y 좌표(2D)좌료 값이므로 이것만으로는 3D좌표값을 구할수 없습니다. 하지만 wiimote를 2개 이상 사용한다면, 이 값들의 조합으로 3D 좌표값을 구해 낼 수 있습니다.
참고로 일반적인 WebCam으로도 가능합니다. 다만 wiimote를 사용하므로써 광점을 인식처리해주는 이미지 처리과정이 필요없고, wiimote의 기타 센서 기능을 함께 사용할 수 있으므로 목적에따라 선택하여 사용하면 됩니다.
아래의 이미지는 WiiFlash Server에서 기본 제공되는 데모 실행화면입니다. 버튼의 눌림상태와 각종 센서들의 수치값이 실시간으로 보여지는 것을 볼 수있습니다.




예제 소스
아래에 첨부된 소스는 Wiimote 2개( WA와 WB)를 이용하여  3차원공간상에 위치한 광점(ir Point)의 좌표값을 계산하는 예제 소스입니다. 저는 이를 기초로하여 3D 스캐너와 헤드트래킹을 구현했습니다. 좀더 고급 수학을 이용한 복잡한 방법이 있을듯 합니다만  저는 잘 모르고요, 제가 아는 범위의 단순한 삼각함수를 이용했습니다. 중.고등학교 과정의 수학이면 이해 가능할 듯 합니다.

package{
	import org.wiiflash.Wiimote;
	import org.wiiflash.events.ButtonEvent;
	import org.wiiflash.events.WiimoteEvent;
	import flash.events.*;
	import flash.display.*;
	import flash.text.TextField;
	
	public class PositionDetector extends Sprite{

		public const D2R:Number = Math.PI / 180;
		public const R2D:Number = 180 / Math.PI;
		// Calibration 수치
		public const IRX2DEG:Number = 41;
		public const IRY2DEG:Number = 32;
		private var L:Number = 500;				//500mm
		private var WA_LRCenterDeg:Number = 60;
		private var WA_UDCenterDeg:Number = 0;
		private var WB_LRCenterDeg:Number = 120;
		private var WB_UDCenterDeg:Number = 0;

		// p(x,y,z)산출 기본자료
		private var WAirx:Number;
		private var WAiry:Number;
		private var WBirx:Number;
		private var WBiry:Number;

		private var p1x:Number;
		private var p1y:Number;
		private var p1z:Number;

		// create a new Wiimote
		private var WA:Wiimote = new Wiimote();
		private var WB:Wiimote = new Wiimote();
				
		public function PositionDetector(){
			//ir value update EventListener 설정
			WA.addEventListener( Event.CONNECT, onWiimoteConnect );
			WA.addEventListener( IOErrorEvent.IO_ERROR, onWiimoteConnectError );
			WA.addEventListener( Event.CLOSE, onCloseConnection );
			WA.addEventListener( WiimoteEvent.UPDATE, WiimoteAUpdateHandler );
			WB.addEventListener( Event.CONNECT, onWiimoteConnect );
			WB.addEventListener( IOErrorEvent.IO_ERROR, onWiimoteConnectError );
			WB.addEventListener( Event.CLOSE, onCloseConnection );
			WB.addEventListener( WiimoteEvent.UPDATE, WiimoteBUpdateHandler );								
			WA.connect ();
			WB.connect ();
			Canvas2D.point.visible = false;
		}

		function onCloseConnection ( pEvent:Event ):void{
			trace("onClose: " + pEvent.target );
		}
		
		function onWiimoteConnectError ( pEvent:IOErrorEvent ):void{
			trace("onError: " + pEvent.target );
		}
		
		function onWiimoteConnect ( pEvent:Event ):void{
			trace( "Wiimote successfully connected: " + pEvent.target );
		}		

		public function WiimoteAUpdateHandler(pEvent:WiimoteEvent){
			if(pEvent.target.ir.p1){	
				WAirx = WA.ir.x1;
				WAiry = WA.ir.y1;
			}			
		}
		public function WiimoteBUpdateHandler(pEvent:WiimoteEvent){
			if(pEvent.target.ir.p1){	
				WBirx = WB.ir.x1;
				WBiry = WB.ir.y1;
				Canvas2D.point.visible = true;				
				CalcProcess();
			}else{
				Canvas2D.point.visible = false;
			}
		}
		public function CalcProcess(){
			//irx => WADeg => WAm(기울기)  , WBm도함께
			var WADeg:Number = WA_LRCenterDeg - ( WAirx - 0.5 ) * IRX2DEG;
			var WAm:Number = Math.tan( WADeg * D2R);
			trace( WADeg, WAm);
			var WBDeg:Number = WB_LRCenterDeg - ( WBirx - 0.5 ) * IRX2DEG;
			var WBm:Number = Math.tan( WBDeg * D2R);

			// 기울기를 알고있는 두 선분의 교점P(x,z) 구하기  
			// WA는 (0,0)  WB는 (L,0)을 지나는경우이며,  WA/WB 수평평면이 x/z 평면이다.
			p1x = ( -WBm * L ) / ( WAm - WBm ) ;
			p1z = WAm * p1x;

			// py구하기
			var Lxz:Number = Math.sqrt( p1x * p1x + p1z * p1z ) ;
			var WA_UDDeg:Number = WA_UDCenterDeg - ( WAiry - 0.5 ) * IRY2DEG;
			p1y = - Math.tan( WA_UDDeg *D2R ) * Lxz;

			// p(x,y,z)표시
			PointXYZ.text = "px = " + String(p1x);
			PointXYZ.text += "\npy = " + String(p1y);
			PointXYZ.text += "\npz = " + String(p1z);			
			
			var Lcan = Canvas2D.WB.x - Canvas2D.WA.x;
			Canvas2D.point.x = Canvas2D.WA.x + p1x  * Lcan / L;
			Canvas2D.point.y = Canvas2D.WA.y - p1z * Lcan / L;
		}
	}
}


예제 소스 설명

1. 장비 설치에 따른 수치 정의
측정하기 전에 WA와 WB의 world좌표상의 거리 L과  x축을 기준으로 한 회전각을 정의합니다.
좀더 노력하면 자동으로 Calibration하여 이값들을 구해낼 수 도 있으나, 본 예제는 기초적인 수학으로 3차원 자표값을 구해내는것을 목표로 하므로, 단순하게 직접 측정하여 기입하는 방식을 사용합니다.
 정의되야할 정보는 아래와 같습니다.

private var L:Number = 500;                           // WA와 WB 사이의 거리, 이경우 500mm
private var WA_LRCenterDeg:Number = 60;      // WA의 좌우 회전각 (Deg)
private var WA_UDCenterDeg:Number = 0;        // WA의 상하 회전각
private var WB_LRCenterDeg:Number = 120;     // WB의 좌우 회전각
private var WB_UDCenterDeg:Number = 0;        // WB의 좌우 회전각
public const IRX2DEG:Number = 41;                // Wiimote의 수평 화각이다.  (측정 값이며 오차있음)
public const IRY2DEG:Number = 32;                // Wiimote의 수직 화각이다.  (측정 값이며 오차있음)


그림1. Wiimote 설정치 참조도 (xz평면도)

2. 작동프로세스
Wiimote의 update 이벤트 발생시마다,  WA,WB모두에서 irPoint 가 발견될 경우에만 WA,WB의 irx,iry를 구한 뒤, p(x,y,z) 값  px,py,pz을 구합니다.


3. 실제 좌표값을 구하는 수학은 간단한 삼각함수를 이용합니다.  소스를 보시고 이해해 보시거나 직접 삼각함수로 연산을 해보시면 됩니다. 단, 한가지 알아둘 사항은 Wiimote에서 출력해주는 좌표값은 0~1 사이의 값이기 때문에 이값을 각도에 해당하는 값으로 변환해줘야 삼각함수 연산이 가능합니다.  이때문에 Wiimote의 화각정보를 미리 측정해 둬야하는데요, 가령 Wiimote의 좌우방향 화각이 30도라고 합시다.   이경우 광점이 가장 왼쪽경계에서 발견된다면  wiimote의 irx값은 0으로 출력됩니다.  그리고 만약 우측 경계부분에서 광점이 발견되면 irx의 값은 1이 됩니다.  또한 Wiimote 화각의 중심점에서 발견될 경우 이값은 정확이 0.5로 출력됩니다.

즉, 아래와 같이 변환될 수 있습니다.
 irx  0  0.25  0.5 0.75
1
중심기준 각도(deg)
 -15  -7.5 0
 7.5  15

실제 코드상에서는 아래와 같이 계산합니다.
var WADeg:Number = WA_LRCenterDeg - ( WAirx - 0.5 ) * IRX2DEG;


추가로, D2R 과 R2D 는 Deg각도 값과 Radian값을 변환해주는 상수 값입니다. 
Degree값을 Radian값으로 바꾸려면 D2R을 곱해주면 됩니다.  마찬가지로 Rad를 Deg로 변환하려면 R2D값을 곱해주면 됩니다.




동영상1. 3차원 좌표값 실시간 구하기


사진1. 설치예



+ Recent posts