目標: ARToolKitで視覚マーカーを検出し、結果をGUIで表示する。
カメラを使うチュートリアルを終えていれば、後は簡単です。MarkerDetectorサービスを初期化し、マーカー(NapMarker)(と対応する物体(Entity)のペア)を突っ込むとマーカー検出が始まります。
検出結果が更新されるたびにLocationUpdateEventが発行されるため、これをEventListenerで検知して、スクリーン座標とマーカー検出の確度を標準出力に垂れ流します。なお、EventListenerは他の種類のイベントも検知するため、以下のコード中ではinstanceofでLocationUpdateEvent型か判断しています。
マーカー検出のエンジンARToolKitはカメラ画像を明るさによって二値化してマーカーの四角形を検出しています。二値化の閾値によって検出精度が著しく左右されるため、閾値をインタラクティブに変えられるGUIコンポーネントMarkerDetectorPanelが用意されています。以下のコード中ではこのコンポーネントを表示しています。
import javax.swing.JFrame; import jp.digitalmuseum.mr.Matereal; import jp.digitalmuseum.mr.gui.DisposeOnCloseFrame; import jp.digitalmuseum.mr.message.Event; import jp.digitalmuseum.mr.message.EventListener; import jp.digitalmuseum.mr.message.LocationUpdateEvent; import jp.digitalmuseum.mr.service.MarkerDetector; import jp.digitalmuseum.mr.service.Camera; import jp.digitalmuseum.napkit.NapDetectionResult; import jp.digitalmuseum.napkit.NapMarker; import jp.digitalmuseum.napkit.gui.MarkerDetectorPanel; import jp.digitalmuseum.utils.ScreenPosition; /** * Run marker detection and print its results. * * @author Jun KATO */ public class DetectMarkerPosition { public static void main(String[] args) { new DetectMarkerPosition(); } public DetectMarkerPosition() { // Run a camera. Camera camera = new Camera(); camera.start(); // Run a marker detector. final MarkerDetector detector = new MarkerDetector(); detector.setImageProvider(camera); detector.start(); // Detect a marker. NapMarker marker = new NapMarker("markers\\4x4_190.patt", 120); detector.addMarker(marker); // Print position of the marker. detector.addEventListener(new EventListener() { public void eventOccurred(Event e) { if (e instanceof LocationUpdateEvent) { NapDetectionResult result = detector.getResult(marker); if (result != null) { ScreenPosition p = result.getPosition(); System.out.println(String.format("%s (Confidence: %3g)", p, result.getConfidence())); } } } }); // Show a configuration window. JFrame configFrame = new DisposeOnCloseFrame(new MarkerDetectorPanel(detector)) { private static final long serialVersionUID = 1L; @Override public void dispose() { super.dispose(); Matereal.getInstance().dispose(); } }; configFrame.setSize(640, 480); } }
カメラを使うチュートリアルで撮影している映像を表示するImageProviderPanelを紹介しましたが、このpaintComponent(Graphics)メソッドを次のようにオーバーライドすれば、簡単に検出結果を重畳表示できます。
@Override public void paintComponent(Graphics g) { super.paintComponent(g); final Graphics2D g2 = (Graphics2D) g; if (stroke == null) { stroke = new BasicStroke(5); } g2.setStroke(stroke); g2.translate(getOffsetX(), getOffsetY()); // Get detected results. Set<NapDetectionResult> results = detector.getResults(); // Draw each detected result. for (final NapDetectionResult result : results) { // Draw corners g2.setColor(Color.orange); detector.paint(g2, result); // Draw information for the square. ScreenPosition point = result.getPosition(); g2.setColor(Color.cyan); g2.drawLine(point.getX(), point.getY(), point.getX()+55, point.getY()+43); g2.drawRect(point.getX()+55, point.getY()+23, 200, 40); drawString(g2, "Confidence: "+result.getConfidence(), point.getX()+64, point.getY()+40); drawString(g2, "Position: "+point, point.getX()+64, point.getY()+56); } // Draw detected number of squares. drawString(g2, "detected: "+(results == null ? 0 : results.size()), 10, getHeight()-10); g2.dispose(); }
全文はDetectMarker.javaを読んでください。
Materealでは、ロボットや物体を表すクラスのインスタンスはgetShape()メソッドで上から見た形状1)を取得できることになっています。これを利用してロボットや物体の形状まで重畳表示することができます。そのためのヘルパークラスがEntityPainterで、これを利用した例がDetectMarkerAndPaintEntity.javaです。元のコードから2行しか増えていない点に注目です。