/* * 作成日: 2008/06/23 */ package jp.ac.fit.asura.nao.localization.self; import static jp.ac.fit.asura.nao.misc.MathUtils.clipping; import static jp.ac.fit.asura.nao.misc.MathUtils.gaussian; import static jp.ac.fit.asura.nao.misc.MathUtils.normalizeAnglePI; import static jp.ac.fit.asura.nao.misc.MathUtils.rand; import static jp.ac.fit.asura.nao.misc.MathUtils.square; import java.awt.Point; import javax.vecmath.Point2f; import jp.ac.fit.asura.nao.RobotContext; import jp.ac.fit.asura.nao.VisualFrameContext; import jp.ac.fit.asura.nao.misc.MathUtils; import jp.ac.fit.asura.nao.physical.Field; import jp.ac.fit.asura.nao.physical.Goal; import jp.ac.fit.asura.nao.vision.VisualContext; import jp.ac.fit.asura.nao.vision.VisualObjects; import jp.ac.fit.asura.nao.vision.perception.GoalVisualObject; import jp.ac.fit.asura.nao.vision.perception.VisualObject; import org.apache.log4j.Logger; /** * モンテカルロ法を使った自己位置同定. * * いじるパラメータ * * -standardWeight: 候補点の重みがどれくらいでリサンプルの対象にするかとか * * -HisteresisSensorResettingsのなかとbeta: 重みの変動がどれくらいでリセットするかとか * * -dDistの分母: 候補点の距離の違いへの感度 * * -dHeadの分母: 候補点の角度の違いへの感度 * * -gaussian関連: リサンプルした候補点へのノイズ量 * * @author sey * * @version $Id: MonteCarloLocalization.java 717 2008-12-31 18:16:20Z sey $ * */ public class MonteCarloLocalization extends SelfLocalization { private Logger log = Logger.getLogger(MonteCarloLocalization.class); class HisteresisSensorResettings { static final float longEta = 0.125f; static final float shortEta = 0.9f; float beta; float longAlpha; float shortAlpha; float threshold; HisteresisSensorResettings() { beta = 0.0f; longAlpha = 1.0f; shortAlpha = 1.0f; threshold = 0.001f; } float getBeta() { return beta; } void update(double alpha) { longAlpha += longEta * (alpha - longAlpha); shortAlpha += shortEta * (alpha - shortAlpha); assert longAlpha * threshold != 0; beta = 1.0f - shortAlpha / (longAlpha * threshold); } void reset() { longAlpha = 1.0f; shortAlpha = 1.0f; } float getLongAlpha() { return longAlpha; } } public static class Position { public int x; public int y; public float h; public void clear() { x = y = 0; h = 0.0f; } } public static class Candidate extends Position { public float w; } private HisteresisSensorResettings resettings; private float standardWeight; private Candidate[] candidates; private Position position; private Position variance; private int confidence; public MonteCarloLocalization() { resettings = new HisteresisSensorResettings(); candidates = new Candidate[128]; for (int i = 0; i < candidates.length; i++) candidates[i] = new Candidate(); position = new Position(); variance = new Position(); standardWeight = (1e-5f / candidates.length); } @Override public void init(RobotContext rctx) { } @Override public void start() { reset(); } @Override public void step(VisualFrameContext context) { } @Override public void stop() { } public synchronized void reset() { resettings.reset(); randomSampling(); } public synchronized void updateVision(VisualContext context) { int resampled = 0; VisualObject bg = context.get(VisualObjects.BlueGoal); VisualObject yg = context.get(VisualObjects.YellowGoal); if (localizeByGoal((GoalVisualObject) yg) || localizeByGoal((GoalVisualObject) bg)) { fieldClipping(); resampled = resampleCandidates(); } estimateCurrentPosition(); if (log.isDebugEnabled()) { if (context.getFrameContext().getFrame() % 25 == 0) { log.debug(String.format( "MCL: current position x:%d y:%d h:%f, cf:%d", position.x, position.y, MathUtils.toDegrees(position.h), confidence)); } } if (resampled > 0) log.debug("MCL: resample " + resampled); } @Override public synchronized void updateOdometry(float forward, float left, float turnCCW) { assert Math.abs(forward) < 1e4 : forward; assert Math.abs(left) < 1e4 : left; assert Math.abs(turnCCW) < 1e4 : turnCCW; for (Candidate c : candidates) { c.x += MathUtils.sin(c.h) * forward + MathUtils.cos(c.h) * left; c.y += MathUtils.cos(c.h) * forward - MathUtils.sin(c.h) * left; c.h = normalizeAnglePI(c.h + turnCCW); } position.x += MathUtils.sin(position.h) * forward + MathUtils.cos(position.h) * left; position.y += MathUtils.cos(position.h) * forward - MathUtils.sin(position.h) * left; position.h = normalizeAnglePI(position.h + turnCCW); } public int getConfidence() { return confidence; } public float getHeading() { return position.h; } public int getX() { return position.x; } public int getY() { return position.y; } public Candidate[] getCandidates() { return candidates; } /** * ビーコンベースで候補点の確からしさを求め、 Weightを更新する。 */ private boolean localizeByGoal(GoalVisualObject vo) { // 候補点の位置がどの程度正しいのかをチェックする。 // 正しければ、weightを上げる。 // 正しくなければ、weightを下げる。 // beaconとの位置関係によって正しさを求める。 // 前判定 if (vo.confidence < 200) return false; boolean useDist = vo.distanceUsable; int voDist = vo.distance; Point2f robotAngle = vo.robotAngle; int goalX = vo.getType() == VisualObjects.YellowGoal ? Goal.YellowGoalX : Goal.BlueGoalX; int goalY = vo.getType() == VisualObjects.YellowGoal ? Goal.YellowGoalY : Goal.BlueGoalY; float alpha = 0.0f; Point goal = new Point(goalX, goalY); Point leftPost = new Point(goalX - Goal.HalfWidth + Goal.PoleRadius, goalY); Point rightPost = new Point(goalX + Goal.HalfWidth - Goal.PoleRadius, goalY); Point[] beacons; if (vo.isLeftPost) { if (vo.isRightPost) beacons = new Point[] { leftPost, rightPost }; else beacons = new Point[] { leftPost }; } else if (vo.isRightPost) beacons = new Point[] { rightPost }; else beacons = new Point[] { goal }; for (Candidate c : candidates) { for (Point beacon : beacons) { int dx = beacon.x - c.x; // ビーコンとの距離 int dy = beacon.y - c.y; int dDist = 0; if (useDist) dDist = square((int) Math.sqrt(square(dx) + square(dy)) - voDist); float theta = (float) Math.atan2(dx, dy); float dHead = normalizeAnglePI(theta - c.h - robotAngle.x); float a = dDist / square(2048f); float b = square(dHead) / 3; c.w *= (float) Math.exp(-(a + b)); } alpha += c.w; assert !Float.isNaN(c.w) && !Float.isInfinite(c.w); } resettings.update(alpha); if (alpha == 0.0f) { randomSampling(); log.error(" warning alpha is zero"); assert false; return false; } assert alpha != 0.0f && !Float.isInfinite(alpha) && !Float.isNaN(alpha); // Σc.w = 1になるよう正規化 for (Candidate c : candidates) { c.w /= alpha; } // standardWeight = (1 / candidates.length * 1e-6); return true; } private int fieldClipping() { int count = 0; for (Candidate c : candidates) { int dx = Math.max(c.x - Field.MaxX, 0) - Math.min(c.x - Field.MinX, 0); int dy = Math.max(c.y - Field.MaxY, 0) - Math.min(c.y - Field.MinY, 0); if (dx + dy > 0) { count++; c.w *= Math.max(Math.exp(-(dx + dy) / square(2048f)), 1e-4); } } return count; } private int resampleCandidates() { // この識別境界面の決定は見直す必要がある // 平均のwよりもとっても低いやつはリサンプル // このへんのメモリの使い方は最適化できそう float beta = resettings.getBeta(); // Q: 汚染されてる状況ならリセットすべきか // ASSERT(finite(beta)); if (beta > 0.5f) { log.debug("beta " + beta); // なんかおかしいのでリセットする // Log::info(LOG_GPS, "MCLocalization: I've been kidnapped! // beta:%f", beta // ); // resetしたらもう一度beacon評価やり直したほうがいいんじゃね? if (beta > 0.9f) { // SR randomSampling(); } else { randomSampling(); // ER // expandSampling(beta * 4); } resettings.reset(); return (int) (candidates.length * beta); } Candidate[] new_c = new Candidate[candidates.length]; float score[] = new float[candidates.length]; int resamples = 0; // リサンプルする候補数 for (int i = 0; i < candidates.length; i++) { if (candidates[i].w > standardWeight) continue; resamples++; for (int j = 0; j < Integer.MAX_VALUE; j++) { if (log.isTraceEnabled() && j % 100 == 0) log.trace("" + j); int r = rand(0, candidates.length); score[r] += candidates[r].w; assert !Double.isNaN(score[r]); assert !Double.isInfinite(score[r]); if (score[r] >= standardWeight) { score[r] -= standardWeight; float dh = clipping(gaussian(0.0f, 0.25f), -MathUtils.PIf, MathUtils.PIf); Point2f p = new Point2f(); gaussian(p, 0, 500); int dx = clipping((int) p.x, Field.MinX, Field.MaxX); int dy = clipping((int) p.y, Field.MinY, Field.MaxY); new_c[i] = new Candidate(); new_c[i].x = candidates[r].x + dx; new_c[i].y = candidates[r].y + dy; new_c[i].h = normalizeAnglePI(candidates[r].h + dh); assert Math.abs(new_c[i].x) < 1e4 : new_c[i].x; assert Math.abs(new_c[i].y) < 1e4 : new_c[i].y; assert Math.abs(new_c[i].h) < 1e1 : new_c[i].h; new_c[i].w = (float) (candidates[r].w * Math .exp(-(square(dx / 10) + square(dy / 10) + square(dh)) / (2 * square(32.0f)))); break; } } } // 重みが低いのはリサンプル float cfSum = 0.0f, wsum = 0.0f; for (int i = 0; i < candidates.length; i++) { if (candidates[i].w < standardWeight) { candidates[i] = new_c[i]; } cfSum += candidates[i].w; } if (cfSum < 1.0e-6f) { // cfSumがあまりにも低い>とりあえずリセット // Log::error("MCLocalization: cfsum eq %.20f", cfSum); randomSampling(); return 0; } for (int i = 0; i < candidates.length; i++) { candidates[i].w /= cfSum; wsum += candidates[i].w; } // Log::debug(LOG_GPS, "MCLocalization: candidate %d/%d:resampled.", // resamples, // numberOfCandidates); return resamples; } private void randomSampling() { log.info("randomSampling();"); for (Candidate c : candidates) { // maxExclusiveだけどいいか. c.x = rand(Field.MinX, Field.MaxX); c.y = rand(Field.MinY, Field.MaxY); c.h = rand(-MathUtils.PIf, MathUtils.PIf); assert Math.abs(c.x) < 1e4 : c.x; assert Math.abs(c.y) < 1e4 : c.y; assert Math.abs(c.h) < 1e2 : c.h; c.w = 1.0f / candidates.length; } } private void estimateCurrentPosition() { calculatePosition(); calculateVariance(); float d = (float) Math.sqrt(variance.x + variance.y + 50 * 180 / MathUtils.PIf * variance.h); // log.debug("MCL: var:" + d); float f = clipping(1000 - d / 10, 0f, 1000f); confidence = (int) (confidence * 0.6f + f * 0.4f); } private void calculatePosition() { Position s = new Position(); Candidate base = candidates[0]; for (Candidate c : candidates) { s.x += c.x; s.y += c.y; if (base.w < c.w) { base = c; } } // wのもっとも高いものを基準に角度の平均をとる for (Candidate c : candidates) { s.h += normalizeAnglePI(c.h - base.h); } position.x = s.x / candidates.length; position.y = s.y / candidates.length; position.h = normalizeAnglePI(s.h / candidates.length + base.h); assert Math.abs(position.x) < 1e5; assert Math.abs(position.y) < 1e5; } private void calculateVariance() { Position s = new Position(); for (Candidate c : candidates) { assert s.x < Integer.MAX_VALUE : s.x; assert s.y < Integer.MAX_VALUE : s.y; assert s.h < Integer.MAX_VALUE : s.h; assert c.x < 1e4 : c.x; assert c.y < 1e4 : c.y; assert c.h < 1e2 : c.h; s.x += square(c.x - position.x); s.y += square(c.y - position.y); s.h += square(c.h - position.h); } // 角度の分散ってどうしよう? variance.x = s.x / candidates.length; variance.y = s.y / candidates.length; variance.h = s.h / candidates.length; assert variance.x >= 0 : variance.x; assert variance.y >= 0 : variance.y; assert variance.h >= 0 : variance.h; variance.y = Math.max(variance.y, 0); variance.x = Math.max(variance.x, 0); variance.h = Math.max(variance.h, 0); } }