模擬部分可以分為 Automatic Tracking 以及 Avoidance 兩個實作部分。
Automatic Tracking 可以分為三大部分:
- 找出紅線
- 計算紅線與 jetbot 照相機中央的距離
- 條整 jetbot 的方向與速度
- 首先從
example.py
的範例中我們可以取得 jetbot 相機返回的照片 - 將照片從 BGR 轉換成 HSV
- 定義紅色的範圍
- 根據範圍從相片取得 mask
- 萃取出只有紅線的圖片 (
res
)
img = cv2.resize(change["new"], (640, 360))
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_red = np.array([37, 148, 58])
upper_red = np.array([255, 255, 236])
mask = cv2.inRange(hsv, lower_red, upper_red)
res = cv2.bitwise_and(img, img, (mask = mask))
cv2.imshow("camera", img)
cv2.imshow("res", res)
- 從剛剛的紅線可以用
findNonZero()
取出所有 pixel- 會是一個 (7000+, 1, 2) 的
ndarray
- 代表七千多個 (x, y)
- 會是一個 (7000+, 1, 2) 的
- 我們取出"最左"與"最右"兩個點,並算出紅線的中間點 (
line_mean
) - 接著就可以算出 jetbot 螢幕中央和紅線的偏差值
coord = cv2.findNonZero(mask)
left = np.min(coord, axis=0)
right = np.max(coord, axis=0)
line_mean = int(np.mean([left[0][0], right[0][0]]))
dist = 320 - line_mean
cv2.circle(res, (320, 320), 10,(255,215,0), 2)
cv2.circle(res, (left[0][0], 340), 10, (255, 255, 255), 2)
cv2.circle(res, (right[0][0], 340), 10, (255, 255, 255), 2)
cv2.circle(res, (line_mean, 340), 10, (255, 255,255), 2)
Jetbot 可以根據與紅線的偏差改變行進角度及速度
- 當偏離在 -20 至 20 之間時,讓 jetbot 保持前進
- 當偏離大於 20 時,代表紅線在左,需要向左轉動
- 當偏離小於 -20 時,代表紅線在右,需要向右轉動
if dist >= -20 and dist <= 20:
robot.forward(0.2)
elif dist > 20:
robot.left(0.002 * dist)
else:
robot.right(0.002 * -dist)
讓 jetbot 下去跑,每 10 frames 抓一張圖
if frames % 10 == 0:
cv2.imwrite('dataset/{}.jpg'.format(frames), img)
將照片分成三類
- 障礙物在左 (共 18 張)
- 障礙物在右 (共 33 張)
- 沒有障礙物 (共 91 張)
|- dataset
|- blocked_left
|- blocked_right
|- free
blocked_left | blocked_right | free |
---|---|---|
利用 jetbot 的 notebooks 的 collision_avoidance.ipynb
來進行修改,訓練一個 3 labels 的 classification model
- 圖片 input 改為 (640, 360)
- 改為 10 張圖片作為 testing,其餘用於 training
- 使用 pretrained Alexnet
- Output = 3
- 總是存取最後一次的 model
dataset = datasets.ImageFolder(
'dataset',
transforms.Compose([
transforms.ColorJitter(0.1, 0.1, 0.1, 0.1),
transforms.Resize((640, 360)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
)
train_dataset, test_dataset = torch.utils.data.random_split(
dataset, [len(dataset) - 10, 10])
model = models.alexnet(pretrained=True)
model.classifier[6] = torch.nn.Linear(model.classifier[6].in_features, 3)
device = torch.device('cuda')
model = model.to(device)
對每張進來的照片進行預測,得到 3 維的向量
- 0 代表障礙物在左
- 1 代表障礙物在右
- 2 代表都沒障礙,可以直行
model = torchvision.models.alexnet(pretrained=False)
model.classifier[6] = torch.nn.Linear(model.classifier[6].in_features, 3)
model.load_state_dict(torch.load('best_avoidance_model.pth'))
device = torch.device('cuda')
model = model.to(device)
img = cv2.resize(change["new"], (640, 360))
x = preprocess(img)
y = model(x)
y = F.softmax(y, dim=1) # tensor([[0.0013, 0.0180, 0.9807]])
當偵測到障礙物時 (在預測的 y 中最大,且機率在 75% 以上) 就暫停 30 幀,用來進行迴避
blocked_left = 0
blocked_right = 0
if i != 2 and prob > 0.75:
if i == 0:
print("block left detected")
blocked_left += 30
elif i == 1:
print("block right detected")
blocked_right += 30
在這 30 幀裡面:
- 前面 6 幀先後退
- 中間 12 幀往障礙物反方向走
- 最後 12 幀往紅線方向回去
if blocked_left > 0:
if blocked_left > 24:
robot.backward(0.15)
elif blocked_left > 12:
robot.set_motor(0.25, 0.18)
else:
robot.set_motor(0.18, 0.25)
blocked_left -= 1
elif blocked_right > 0:
if blocked_right > 24:
robot.backward(0.15)
elif blocked_right > 12:
robot.set_motor(0.18, 0.25)
else:
robot.set_motor(0.25, 0.18)
blocked_right -= 1