コンテンツにスキップ
日本語

迷路を進む

このチュートリアルでは、完全なプログラムを一歩ずつ作っていきます。ロボットは壁や穴を避けながら迷路を走り回り、壁のトークンらしきものを見つけたら報告します。これは初めての実行で動かしたプログラムです。

スターターロボットと、移動とセンサーで学んだことを使います。

完全なプログラム: maze.py · maze.cpp

world1.wbt で実行しましょう。このワールドは次のようになっています。

Webots で開いたワールド world1

まず、プログラムはロボットを作成し、車輪のモーターを取得して、制限なく回るようにします。speeds は各車輪の速度を保持し、プログラムの残りの部分がこれを変更します。reported は、ロボットがすでにトークンを報告した場所を記憶します。

from controller import Robot
import struct
import numpy as np
import cv2 as cv
robot = Robot()
timeStep = 32 # Milliseconds between steps
maxVelocity = 6.28 # The maximum speed of the wheels, in radians per second
leftWheel = robot.getDevice("left wheel motor")
rightWheel = robot.getDevice("right wheel motor")
leftWheel.setPosition(float("inf")) # Let the wheels turn without limit
rightWheel.setPosition(float("inf"))
speeds = [maxVelocity, maxVelocity] # [left wheel speed, right wheel speed]
reported = [] # Positions where a token has already been reported

次に、スターターロボットのセンサーを取得して有効にします。

センサーに名前を付けたサンプルロボット

leftDist = robot.getDevice("leftDist")
frontDist = robot.getDevice("frontDist")
rightDist = robot.getDevice("rightDist")
camera = robot.getDevice("camera")
colorSensor = robot.getDevice("color")
gps = robot.getDevice("gps")
emitter = robot.getDevice("emitter") # The emitter does not need to be enabled
for sensor in [leftDist, frontDist, rightDist, camera, colorSensor, gps]:
sensor.enable(timeStep)

これらの関数は、速度を変えて曲がったりその場で回転したりします。delay は、指定したミリ秒のあいだロボットに同じ動作を続けさせ、getBrightness はカラーセンサーを 0(黒)から 255(白)までの 1 つの値として読み取ります。

def turnRight():
speeds[0] = 0.6 * maxVelocity
speeds[1] = -0.2 * maxVelocity
def turnLeft():
speeds[0] = -0.2 * maxVelocity
speeds[1] = 0.6 * maxVelocity
def spin():
speeds[0] = 0.6 * maxVelocity
speeds[1] = -0.6 * maxVelocity
def delay(ms):
start = robot.getTime()
while robot.step(timeStep) != -1:
if (robot.getTime() - start) * 1000 > ms:
break
def getBrightness():
image = colorSensor.getImage()
return colorSensor.imageGetGray(image, colorSensor.getWidth(), 0, 0) # 0 (black) to 255 (white)

この関数は、カメラ画像の中から壁のトークンを探します。トークンは暗い壁の上の明るい四角形なので、ほぼ正方形で、画像の端(ふつうは床が写る部分)に接していない明るい形を探します。

def seesToken(image):
height, width = camera.getHeight(), camera.getWidth()
image = np.frombuffer(image, np.uint8).reshape((height, width, 4)) # BGRA image for OpenCV
gray = cv.cvtColor(image, cv.COLOR_BGRA2GRAY)
_, bright = cv.threshold(gray, 200, 255, cv.THRESH_BINARY) # Bright parts in white
contours, _ = cv.findContours(bright, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
for contour in contours:
x, y, w, h = cv.boundingRect(contour)
if x == 0 or y == 0 or x + w == width or y + h == height:
continue # Shapes touching the edge of the image are usually the floor
if cv.contourArea(contour) > 40 and 0.65 < w / h < 1.5:
return True # A bright, roughly square shape on a wall
return False

これは簡単な出発点です。記号を見分けることはできず、コグニティブターゲットもうまく見えません。壁のトークンのチュートリアルで、よりよい方法を紹介しています。

トークンで得点するには、ロボットは 1 秒より長く停止してから、位置とトークンの種類を送る必要があります。各トークンで得点できるのは 1 回だけで、もう一度報告すると 5 点の減点になるので、プログラムは報告した場所を記憶します。これらのメッセージは通信で説明しています。

def report(tokenType):
leftWheel.setVelocity(0) # Stop for more than 1 second
rightWheel.setVelocity(0)
delay(1300)
x = int(gps.getValues()[0] * 100) # Meters to centimeters
z = int(gps.getValues()[2] * 100)
emitter.send(struct.pack("i i c", x, z, tokenType.encode()))
robot.step(timeStep)
reported.append((x, z))
def alreadyReported():
# Reporting the same token again is a misidentification
x = gps.getValues()[0] * 100
z = gps.getValues()[2] * 100
return any(abs(x - rx) < 12 and abs(z - rz) < 12 for rx, rz in reported) # Closer than 1 tile

最後に、メインループですべてを組み合わせます。各ステップで、全速力で走り始め、壁から離れるように曲がり、穴からは引き返し、正面の壁に近づいたらカメラに見えたものを報告します。

while robot.step(timeStep) != -1:
speeds[0] = maxVelocity
speeds[1] = maxVelocity
if leftDist.getValue() < 0.05: # Wall on the left: turn right
turnRight()
if rightDist.getValue() < 0.05: # Wall on the right: turn left
turnLeft()
if frontDist.getValue() < 0.05: # Wall in front: spin
spin()
if getBrightness() < 80: # Black floor: turn away from the hole
spin()
leftWheel.setVelocity(speeds[0])
rightWheel.setVelocity(speeds[1])
delay(600)
# Close to a wall, and something on it: report it
if frontDist.getValue() < 0.07 and not alreadyReported() and seesToken(camera.getImage()):
report("H") # The type cannot be told yet, so guess "H"
leftWheel.setVelocity(speeds[0])
rightWheel.setVelocity(speeds[1])

どのくらいうまく動きますか?

Section titled “どのくらいうまく動きますか?”

このロボットはあてもなく走り回ります。どこを通ったかを覚えておらず、すべての被災者の種類を推測で決めています。ときどき動けなくなり、20 秒後にゲームによってチェックポイントに戻されます。最初のプログラムとしてはこれで十分です。

  • 自分のロボットに最適な速度と距離を見つけましょう。
  • 片側の壁に沿って進んでみましょう。横のセンサーの距離を一定に保ち、前のセンサーが壁を見つけたら曲がります。
  • 壁までの近さに応じて速度を変えてみましょう。

次は、通信で得点のしかたを学びましょう。