kinect OpenNIを使って手の指先の認識 Processing ライブラリ
Post date: Mar 10, 2013 12:24:34 PM
processingのsimple-openNIを使うにあたって、手の甲の認識はNiteを使えばできる。
しかし、指先の認識までは出来ないのが玉にキズである。ここで、FingerTrackerというライブラリがprocessing向けに出ている。
これとキネクトを使えば、指の認識ができるはずなのだが、残念ながらサンプルプログラムは誤認識がひどかったり、
閾値(このライブラリは閾値を「色」ではなく「距離」で指定する)が固定なのでキネクトと手の距離が変わったり、手がキネクトと並行でなくなった時に認識してくれなくなったりと大変である。
そこで、1.閾値を常に手の位置に合わせる 2.手の周辺のみに指(手の形)の認識を行う の2つをプログラムに追加したいと思う。
まず、1.閾値を常に手の位置に合わせる であるが、これは「手の位置を得る」→「その位置の深度を得てそれを閾値とする」を行えば良い。
Simple-OpenNIのサンプルであるNite->Handsを使えばよい。このソースは、「C:\Program Files (x86)\PrimeSense\NITE\Hands\Nite.ini」の中身を
//AllowMultipleHands=1
//TrackAdditionalHands=1
の2行のコメントを外す必要がある。ちなみに私の環境だとHandsなんてフォルダはなく、Hands_1_4_2とかいうフォルダが5つくらいあったので一応全部変えた。
さて、このプログラムのPVector screenPosに手の甲のxy座標が代入されることになる。そのxy座標の深度を得るには、
とすれば良い。(ちなみにここでwidthを使うのはホントはまずい。 context.depthWidth()の方が安全。)
次に2.手の周辺のみに指(手の形)の認識を行う であるが、これは、注目座標(screenPos)から大きく外れた部分の深度をすべて0にするという方法を取る。
本当はOpenCVなどにあるROI機能が使えればそれでいいのだけれども、そんな機能はやはりint型の1次配列にはなかった。
多分、閾値が0付近になることはほぼ無いと思われるので(現時点では)それでOKとしている。
今回は手の甲を重心とした一辺200ピクセルに注目することにした。
以下がソースである。サンプルのNite->Handsを元に少し足した感じになる。
context.depthMap()[(int)screenPos.x+(int)screenPos.y*width]
import SimpleOpenNI.*;
import fingertracker.*;
FingerTracker fingers;
SimpleOpenNI context;
int threshold = 625;
int error=0;//手の甲の深度が必ずぴったり手の認識の閾値として最適とは限らない。そこで手動で操作できるようにした。
// NITE
XnVSessionManager sessionManager;
XnVFlowRouter flowRouter;
PointDrawer pointDrawer;
PVector index=new PVector();
void setup()
{
context = new SimpleOpenNI(this);
// mirror is by default enabled
context.setMirror(true);
// enable depthMap generation
context.enableRGB();
if (context.enableDepth() == false)
{
println("Can't open the depthMap, maybe the camera is not connected!");
exit();
return;
}
context.alternativeViewPointDepthToImage();
// enable the hands + gesture
context.enableGesture();
context.enableHands();
// setup NITE
sessionManager = context.createSessionManager("Click,Wave", "RaiseHand");
pointDrawer = new PointDrawer();
flowRouter = new XnVFlowRouter();
flowRouter.SetActive(pointDrawer);
sessionManager.AddListener(flowRouter);
size(context.depthWidth(), context.depthHeight());
fingers = new FingerTracker(this, width, height);
fingers.setMeltFactor(100);
smooth();
}
void draw()
{
background(200, 0, 0);
// update the cam
context.update();
// update nite
context.update(sessionManager);
// draw depthImageMap
PImage depthImage = context.depthImage();
image(context.rgbImage(), 0, 0);
int[] depthMap = context.depthMap();
if(depthMap[(int)index.x+(int)index.y*width]!=0){//認識ミスで深度が0になったときは閾値を変更しない。
threshold=depthMap[(int)index.x+(int)index.y*width]+error;
}
//手の甲を中心として1辺200ピクセル外の部分の深度を0にする。
for(int x=0;x<width;x++){
for(int y=0;y<height;y++){
if(x<index.x-100)depthMap[x+y*width]=0;
if(y<index.y-100)depthMap[x+y*width]=0;
if(x>index.x+100)depthMap[x+y*width]=0;
if(y>index.y+100)depthMap[x+y*width]=0;
}
}
fingers.setThreshold(threshold);
fingers.update(depthMap);
stroke(0,255,0);
for (int k = 0; k < fingers.getNumContours(); k++) {
fingers.drawContour(k);
}
// iterate over all the fingers found
// and draw them as a red circle
noStroke();
fill(255,0,0);
for (int i = 0; i < fingers.getNumFingers(); i++) {
PVector position = fingers.getFinger(i);
ellipse(position.x - 5, position.y -5, 10, 10);
}
// show the threshold on the screen
fill(255,0,0);
text(threshold, 10, 20);
// draw the list
pointDrawer.draw();
}
void keyPressed()
{
switch(key)
{
case 'e':
// end sessions
sessionManager.EndSession();
println("end session");
break;
}
if(keyCode==UP){
error++;
println("error:"+error);
}
if(keyCode==DOWN){
error--;
println("error:"+error);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// session callbacks
void onStartSession(PVector pos)
{
println("onStartSession: " + pos);
}
void onEndSession()
{
println("onEndSession: ");
}
void onFocusSession(String strFocus, PVector pos, float progress)
{
println("onFocusSession: focus=" + strFocus + ",pos=" + pos + ",progress=" + progress);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// PointDrawer keeps track of the handpoints
class PointDrawer extends XnVPointControl
{
HashMap _pointLists;
int _maxPoints;
color[] _colorList = {
color(255, 0, 0), color(0, 255, 0), color(0, 0, 255), color(255, 255, 0)
};
public PointDrawer()
{
_maxPoints = 30;
_pointLists = new HashMap();
}
public void OnPointCreate(XnVHandPointContext cxt)
{
// create a new list
addPoint(cxt.getNID(), new PVector(cxt.getPtPosition().getX(), cxt.getPtPosition().getY(), cxt.getPtPosition().getZ()));
println("OnPointCreate, handId: " + cxt.getNID());
}
public void OnPointUpdate(XnVHandPointContext cxt)
{
//println("OnPointUpdate " + cxt.getPtPosition());
addPoint(cxt.getNID(), new PVector(cxt.getPtPosition().getX(), cxt.getPtPosition().getY(), cxt.getPtPosition().getZ()));
}
public void OnPointDestroy(long nID)
{
println("OnPointDestroy, handId: " + nID);
// remove list
if (_pointLists.containsKey(nID))
_pointLists.remove(nID);
}
public ArrayList getPointList(long handId)
{
ArrayList curList;
if (_pointLists.containsKey(handId))
curList = (ArrayList)_pointLists.get(handId);
else
{
curList = new ArrayList(_maxPoints);
_pointLists.put(handId, curList);
}
return curList;
}
public void addPoint(long handId, PVector handPoint)
{
ArrayList curList = getPointList(handId);
curList.add(0, handPoint);
if (curList.size() > _maxPoints)
curList.remove(curList.size() - 1);
}
public void draw()
{
if (_pointLists.size() <= 0)
return;
pushStyle();
noFill();
PVector vec;
PVector firstVec;
PVector screenPos = new PVector();
int colorIndex=0;
// draw the hand lists
Iterator<Map.Entry> itrList = _pointLists.entrySet().iterator();
while (itrList.hasNext ())
{
strokeWeight(2);
stroke(_colorList[colorIndex % (_colorList.length - 1)]);
ArrayList curList = (ArrayList)itrList.next().getValue();
// draw line
firstVec = null;
Iterator<PVector> itr = curList.iterator();
beginShape();
while (itr.hasNext ())
{
vec = itr.next();
if (firstVec == null)
firstVec = vec;
// calc the screen pos
context.convertRealWorldToProjective(vec, screenPos);
vertex(screenPos.x, screenPos.y);
}
endShape();
// draw current pos of the hand
if (firstVec != null)
{
strokeWeight(8);
context.convertRealWorldToProjective(firstVec, screenPos);
point(screenPos.x, screenPos.y);
index.x=screenPos.x;//ココらへんで(深度とか対象範囲指定とかに使う)手の位置を更新する
index.y=screenPos.y;
}
colorIndex++;
}
popStyle();
}
}
「↑」「↓」を押すことで、閾値が手の甲の深度からずらしていくことが可能である。
大体error:25らへんにした時にうまく認識することが出来た。
手をバイバイすることで、手の甲の座標を得始める。