반응형

핸드폰에서는 자이로센서를 사용하면 되겠지만...

고정되어있는 디바이스에서 화면에 공간적인 느낌을 낼 수 있는 방법중 하나가 카메라같은 센서를 사용하여 사람을 인식 한 후 위치를 가져와 화면을 움직을 수 있을 것이다. 대표적인 센서로는 키넥트나 인텔에서 나온 리얼센스카메라를 사용하면 되겠으나 그렇게 되면 배보다 배꼽이 더 커지는게 아닐까 싶다. 소프트웨어로 처리할 수 있는 기술도 있는데 대표적인게 Open CV라는 녀석인듯 하다. 근데 이놈도 유니티에서 사용하려니 95달러나... 다른 경로를 통해 검색해 보니 좀 낮은 버전을 얻을 수 있었다.

 

하지만 테스트 해본 결과 얼굴인식이 그다지 잘 되지는 않는것 같다. 간혹 얼굴을 측면으로 돌린다거나 멀어지면 인식이 되지 않는 경우가 많다. 그래서 고민하다 화면 전체의 이전 프레임과 현재프레임의 픽셀을 비교하고 달라지는 픽셀을 검출하는 방식으로 달라지는 픽셀들의 위치값의 평균을 가져오면 얼굴인식처럼 사람위치를 트래킹할 수 있겠다는 생각을 해보았다.

 

픽셀의 위치값을 가져오기 위해 해당 픽섹의 위치에 GameObject를 만들어야 하는데 해상도 만큼 만들게 되면 기기가 뻗을 수도 있다. 웹캠 해상도가 1280x720이면 921,600개를 만들어야 하고 매프레임마다 변화값을 체크해야하니 무리가 될 수 밖에 없겠다.

그래서 적당히 픽셀을 건너뛰고 오브젝트를 만들었다. 아래 코드는 20픽셀씩 스킵하였다. 기기가 조금 뜨거워 지는듯 하지만 적당히 잘 돌아간다.

 

아래코드는  웹캠을 통해 화면을 받아오고 픽셀을 검출해서 비교하기 위한 코드이다.

using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;

public class WebCamTracker : MonoBehaviour {
	WebCamTexture webCamTexture;
	WebCamDevice webCamDevice;
	public RawImage rawImage;
	int width = 1280, height = 720;

	[SerializeField]
	bool isCamOn;
	[SerializeField]
	GameObject Pref;

	static List<Color> NowColor;
	List<PixelHandler> ListGameObj, ListOn;
	public RectTransform RT_Pos;
	public Transform T_Text, T_Head;
	public TextMesh[] ArrText;
	public Slider Slider;
	public float Value;
	public Text Sensitivity;

	int skip = 20;
	Vector2 LastPos;
	float CutOffMaxPixelCount = 1000;

	void Start () {
		if (Application.platform == RuntimePlatform.Android)
			webCamDevice = WebCamTexture.devices[1];
		else
			webCamDevice = WebCamTexture.devices[0];
		webCamTexture = new WebCamTexture (webCamDevice.name, width, height);
		rawImage.texture = webCamTexture;
		webCamTexture.Play ();
		rawImage.GetComponent<RectTransform> ().SetSizeWithCurrentAnchors (RectTransform.Axis.Horizontal, width);
		rawImage.GetComponent<RectTransform> ().SetSizeWithCurrentAnchors (RectTransform.Axis.Vertical, height);
		NowColor = new List<Color> ();
		makePixels ();
		Slider.onValueChanged.AddListener (ValueChange);
		ValueChange (Slider.value);
	}
	void ValueChange (float value) {
		Value = (value * CutOffMaxPixelCount);
		Value = Mathf.Round (Value);
		Sensitivity.text = "Cut Off Pixel : " + Value.ToString ();
	}
	void makePixels () {
		ListGameObj = new List<PixelHandler> ();
		ListOn = new List<PixelHandler> ();
		for (int y = 0; y < height / skip; y++) {
			for (int x = 0; x < width / skip; x++) {
				GameObject go = Instantiate (Pref);
				go.SetActive (true);
				go.transform.SetParent (Pref.transform.parent);
				go.transform.localScale = new Vector3 (1f, 1f, 1f);
				go.transform.localPosition = new Vector2 (x * skip - width / 2 + skip - 10, y * skip - height / 2 + 30);
				go.GetComponent<PixelHandler> ().CG.alpha = 0.5f;
				ListGameObj.Add (go.GetComponent<PixelHandler> ());
			}
		}
		Debug.Log ("make ListGameObj");
	}

	static float posX, posY;

	IEnumerator ComparePixel () {
		yield return new WaitForSeconds (0.1f);

		NowColor.Clear ();
		for (int y = 0; y < webCamTexture.height / skip; y++) {
			for (int x = 0; x < webCamTexture.width / skip; x++) {
				NowColor.Add (webCamTexture.GetPixel (x * skip, y * skip));
			}
		}

		ListOn.Clear ();
		for (int i = 0; i < ListGameObj.Count; i++) {
			ListGameObj[i].Img.color = NowColor[i];

			if (ListGameObj[i].isOn)
				ListOn.Add (ListGameObj[i]);

			if (ListGameObj[i].isOn) {
				ListGameObj[i].RT.DOScale (1f, 0.2f).SetEase (Ease.Linear);
				ListGameObj[i].RT.DOLocalRotate (new Vector3 (0, 0, 45), 0.2f).SetEase (Ease.Linear);
			} else {
				ListGameObj[i].RT.DOScale (0f, 0.2f).SetEase (Ease.Linear);
				ListGameObj[i].RT.DOLocalRotate (new Vector3 (0, 0, 0), 0.2f).SetEase (Ease.Linear);
			}
		}
		for (int i = 0; i < ArrText.Length; i++) {
			ArrText[i].text = "True Pixel Count : " + ListOn.Count;
		}
		if (ListOn.Count > Value) {
			for (int i = 0; i < ListOn.Count; i++) {
				posX = posX + ListOn[i].RT.anchoredPosition.x;
				posY = posY + ListOn[i].RT.anchoredPosition.y;
			}
			posX = Mathf.Clamp (posX / ListOn.Count, -width / 2, width / 2);
			posY = Mathf.Clamp (posY / ListOn.Count, -width / 2, width / 2);
			if (!float.IsNaN (posX) && !float.IsNaN (posY))
				try {
					RT_Pos.DOLocalMove (new Vector3 (-posX, posY * 0.4f, 24), 0.4f).SetEase (Ease.Linear);
				} catch { }
			LastPos = new Vector2 (-posX, posY);
		} else {
			RT_Pos.DOLocalMove (LastPos, 0.4f).SetEase (Ease.Linear);
		}
		posX = posY = 0;

		StartCoroutine ("ComparePixel");

	}
	void Update () {

		if (webCamTexture.width > 16) {
			if (!isCamOn) {
				width = webCamTexture.width;
				height = webCamTexture.height;
				isCamOn = true;
				StartCoroutine ("ComparePixel");
				// for (int i = 0; i < ArrText.Length; i++) {
				// 	ArrText[i].text = width + "X" + height;
				// }
			}
			T_Head.DOLocalRotate (new Vector3 (RT_Pos.anchoredPosition.x * 0.08f, 0, 30 + RT_Pos.anchoredPosition.y * 0.2f), 0.3f).SetEase (Ease.Linear);
			T_Text.DOLocalRotate (new Vector3 (7 + RT_Pos.anchoredPosition.y * 0.1f, -RT_Pos.anchoredPosition.x * 0.1f, 0), 0.3f).SetEase (Ease.Linear);
		}
	}
}

 

아래코드는 해당 픽셀에 붙는 코드이다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PixelHandler : MonoBehaviour {
	public Image Img;
	public RectTransform RT;
	public CanvasGroup CG;
	public bool isOn;
	Color MyTempColor1;
	// public Color FistFrame;
	float min = 0.6f, max = 1.4f;

	void Start () {
		StartCoroutine (OnCheck ());
	}
	IEnumerator OnCheck () {

		MyTempColor1 = Img.color;
		yield return new WaitForSeconds (0.1f);
		if (
			MyTempColor1.r * max > Img.color.r && MyTempColor1.r * min < Img.color.r ||
			MyTempColor1.g * max > Img.color.g && MyTempColor1.r * min < Img.color.g ||
			MyTempColor1.b * max > Img.color.b && MyTempColor1.r * min < Img.color.b
		)
			isOn = false;
		else
			isOn = true;

		StartCoroutine (OnCheck ());
	}
}

 

 

반응형

+ Recent posts