Udacity机器人软件工程师课程笔记(三)-样本搜索和找回-基于漫游者号模拟器-使用moviepy输出测试视频
6.方法測試
在這個部分我們要整體的測試我們的程序,對前面的知識和內容有一個整體的應用和概括。
這是Udacity提供的相應資料,在code文件夾中有一個Rover_Project_Test_Notebook.ipynb文件,提供了輸出視頻的筆記本文件。
由于個人喜好的原因,我選擇了pycharm2019來運行此程序。
(1)方法測試程序概述
在這個部分我們需要完成的項目如下
- 首先運行筆記本中的每個單元格,檢查代碼和每個單元格的結果。
- 在“訓練模式”中運行模擬器并記錄一些數據。注意:只需要使用一些示例圖像,幾百張即可。
- 將數據目錄路徑(下面的2個單元格)更改為保存數據的目錄
- 測試數據上提供的功能
- 編寫新功能(或修改現有功能)以報告和繪制障礙物和巖石樣本(黃色巖石)的檢測結果
- 使用適當的步驟/函數填充process_image()函數,以從原圖像轉換為worldmap。
- 使用moviepy函數運行調用process_image()的單元格以創建視頻輸出
- 如果繪制工作可以進行,則繼續修改perception.py和decision.py,讓流浪者號自主導航并以自動模式進行繪制。
首先我們需要獲取ffmpeg工具
import imageioimageio.plugins.ffmpeg.download()
運行此程序以下載ffmpeg工具。
但我在運行這個程序時遇到了一個問題。
報錯
imageio.ffmpeg.download() has been deprecated. Use 'pip install imageio-ffmpeg' instead.'‘
經查閱,該錯誤是由版本更新引起的錯誤,解決辦法(在Windows上)為
pip install imageio==2.4.1
現在再一次運行我們的程序,如果imageio正確安裝且電腦中沒有安裝ffmpeg,程序會自動下載安裝
import imageioimageio.plugins.ffmpeg.download()
python會自動下載ffmpeg,如果下載不成功或者速度太慢可以選擇到相應網站下載,單擊這里進行下載。
ffmpeg應當放到AppData目錄下。
(2)讀入以保存的數據和世界地圖
我們定義一個名為 Databucket() 類來存儲測量數據、圖像路徑和圖像。
當我們實例化這個類時,我們將得到一個名為 data 的全局變量,我們將在之后的 process_image() 函數中來引用其中的數據。
其程序如下:
import pandas as pd
df = pd.read_csv('.../Roversim/Roverdata/robot_log.csv', delimiter=';', decimal='.')
csv_img_list = df["Path"].tolist()
# 讀入世界地圖并使用它創建一個3通道的圖像
ground_truth = mpimg.imread('.../RoboND-Rover-Project-master/calibration_images/map_bw.png')
ground_truth_3d = np.dstack((ground_truth*0, ground_truth*255, ground_truth*0)).astype(np.float)# 創建一個類作為Databucket,讀取csv文件中保存的數據并填充此對象。
# worldmap被實例化為200*200個網格,其對應200m*200m的空間。
class Databucket():def __init__(self):self.images = csv_img_list self.xpos = df["X_Position"].valuesself.ypos = df["Y_Position"].valuesself.yaw = df["Yaw"].valuesself.count = 0 # 運行索引self.worldmap = np.zeros((200, 200, 3)).astype(np.float)self.ground_truth = ground_truth_3d # Ground truth worldmap# 實例化一個databucket,這將是一個全局變量,
# 我們可以在接下來的process_image()函數中使用
data = Databucket()
(3)圖像處理函數
1)process_image()函數
編寫process_image()函數,以執行圖像分析和映射。我們只需要把對應漫游者號的位置等映射到worldmap上去就可以。
在之前的程序中,我們小車的位置和偏轉角是隨機生成的,但是這里就需要我們來讀取csv文件中的數據,將相應的透視變換之后的圖像映射到世界地圖上去。
換句話說, 我們將各個圖像傳遞到 process_image() 函數并以此來構建一個名為 output_image 的圖像,該圖像將存儲為視頻的一幀。
下面是經過更改后的 process_image() 函數:
# 定義圖片處理函數
def process_image(img):output_image = np.zeros((img.shape[0] + data.worldmap.shape[0],img.shape[1] * 2, 3))# 定義第一張圖為imgoutput_image[0:img.shape[0], 0:img.shape[1]] = img# 定義第二張圖為img經過透視變換后的圖像warped = perspect_transform(img, src, dst)output_image[0:img.shape[0], img.shape[1]:] = warpedgray_warped = cv2.cvtColor(warped, cv2.COLOR_RGB2GRAY)ret, img_thresh = cv2.threshold(gray_warped, 160, 255, cv2.THRESH_BINARY)test_image = np.expand_dims(img_thresh, axis=2)output_image[180:340, img.shape[1]:, 0] = img_threshxpix, ypix = rover_coords(img_thresh)worldmap = data.worldmapx_world, y_world = pix_to_world(xpix, ypix, data.xpos[data.count], data.ypos[data.count], data.yaw[data.count],data.worldmap.shape[0], 20 )worldmap[y_world, x_world] += 1map_add = cv2.addWeighted(ground_truth_3d, 1, worldmap, 0.2, 0)output_image[img.shape[0]:, 0:data.worldmap.shape[1]] = map_addcv2.putText(output_image, "Test!", (20, 20),cv2.FONT_HERSHEY_COMPLEX, 0.4, (255, 255, 255), 1)# 跟蹤Databucket中的索引if data.count < len(data.images) - 1:data.count += 1return output_image
2)cv2.addWeighted
此函數是為了使兩個圖像疊加使用的,用在我們將小車的透視變換后的圖像映射到世界地圖上。
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])
參數說明:
- src1 - 第一個輸入數組。
- alpha - 第一個數組元素的權重。
- src2 - 與src1具有相同大小和通道編號的第二個輸入數組。
- beta - 第二個數組元素的權重。
- dst - 與輸入數組具有相同大小和通道數的輸出數組。
- gamma - 標量加到每個總和。
- dtype - 輸出數組的可選深度; 當兩個輸入數組具有相同的深度時,dtype可以設置為-1,這相當于src1.depth()
此函數可以用一下矩陣表達式來代替:
dst = src1 * alpha + src2 * beta + gamma;
(4)輸出測試視頻
因為我們要對特定的顏色進行圖像顏色閾值處理,比如之后選擇特定的黃色的巖石樣本,所以我圖像二值化選擇使用numpy來完成而不是cv2。其程序如下:
def color_thresh(img, rgb_thresh=(160, 160, 160)):color_select = np.zeros_like(img[:,:,0])above_thresh = (img[:,:,0] > rgb_thresh[0]) \& (img[:,:,1] > rgb_thresh[1]) \& (img[:,:,2] > rgb_thresh[2])color_select[above_thresh] = 1return color_selectthreshed = color_thresh(warped)
plt.imshow(threshed, cmap='gray')
以下是最終程序
import numpy as np
import cv2
import pandas as pd
import matplotlib.image as mpimg
from moviepy.editor import VideoFileClip
from moviepy.editor import ImageSequenceClip##顏色閾值
# 使用Numpy定義二值化圖像函數
def color_thresh(img, rgb_thresh=(160, 160, 160)):img_thresh = np.zeros_like(img[:, :, 0])above_thresh = (img[:, :, 0] > rgb_thresh[0]) \& (img[:, :, 1] > rgb_thresh[1]) \& (img[:, :, 2] > rgb_thresh[2])img_thresh[above_thresh] = 1return img_thresh##透視變換
# 定義圖像映射函數,將攝像頭的圖像映射到平面坐標中去
def perspect_transform(img, src, dst):M = cv2.getPerspectiveTransform(src, dst) # 定義變換矩陣img_perspect = cv2.warpPerspective(img, M, (img.shape[1], img.shape[0]))return img_perspect##坐標變換
# 定義從圖像坐標轉換函數
def rover_coords(binary_img):ypos, xpos = binary_img.nonzero()x_pixel = -(ypos - binary_img.shape[0]).astype(np.float)y_pixel = -(xpos - binary_img.shape[1]/2 ).astype(np.float)return x_pixel, y_pixel# 定義旋轉操作函數
def rotate_pix(xpix, ypix, yaw):yaw_rad = yaw * np.pi / 180xpix_rotated = (xpix * np.cos(yaw_rad)) - (ypix * np.sin(yaw_rad))ypix_rotated = (xpix * np.sin(yaw_rad)) + (ypix * np.cos(yaw_rad))return xpix_rotated, ypix_rotated# 定義平移操作函數
def translate_pix(xpix_rot, ypix_rot, xpos, ypos, scale):xpix_translated = (xpix_rot / scale) + xposypix_translated = (ypix_rot / scale) + yposreturn xpix_translated, ypix_translated# 定義綜合函數,將旋轉和平移函數進行結合,并限制了圖像范圍
def pix_to_world(xpix, ypix, xpos, ypos, yaw, world_size, scale):xpix_rot, ypix_rot = rotate_pix(xpix, ypix, yaw)xpix_tran, ypix_tran = translate_pix(xpix_rot, ypix_rot, xpos, ypos, scale)x_pix_world = np.clip(np.int_(xpix_tran), 0, world_size - 1)y_pix_world = np.clip(np.int_(ypix_tran), 0, world_size - 1)return x_pix_world, y_pix_world# 定義轉換為極坐標函數
def to_polar_coords(xpix, ypix):dist = np.sqrt(xpix**2 + ypix ** 2)angles = np.arctan2(ypix, xpix)return dist, angles# 參考圖像,用作透視變換
filename = '.../RoboND-Rover-Project-master/calibration_images/example_grid1.jpg'
image = cv2.imread(filename)dst_size = 5
bottom_offset = 0src = np.float32([[14, 140], [301, 140], [200, 96], [118, 96]])
dst = np.float32([[image.shape[1]/2 - dst_size, image.shape[0] - bottom_offset],[image.shape[1]/2 + dst_size, image.shape[0] - bottom_offset],[image.shape[1]/2 + dst_size, image.shape[0] - 2*dst_size - bottom_offset],[image.shape[1]/2 - dst_size, image.shape[0] - 2*dst_size - bottom_offset],])# 透視變換
warped = perspect_transform(image, src, dst)df = pd.read_csv('.../Roverdata/robot_log.csv', delimiter=';', decimal='.')
csv_img_list = df["Path"].tolist() # 創建一個關于圖像的路徑列表
# 讀取worldmap,并創建一個三維圖像
ground_truth = mpimg.imread('.../RoboND-Rover-Project-master/calibration_images/map_bw.png')
ground_truth_3d = np.dstack((ground_truth*0, ground_truth*255, ground_truth*0)).astype(np.float)# 定義一個類來存儲諸如漫游者號位置,方向角等數據
class Databucket():def __init__(self):self.images = csv_img_listself.xpos = df["X_Position"].valuesself.ypos = df["Y_Position"].valuesself.yaw = df["Yaw"].valuesself.count = 0self.worldmap = np.zeros((200, 200, 3)).astype(np.float)self.ground_truth = ground_truth_3d# 實例化Databucket類
data = Databucket()# 定義圖片處理函數
def process_image(img):output_image = np.zeros((img.shape[0] + data.worldmap.shape[0],img.shape[1] * 2, 3))# 定義第一張圖為攝像頭原圖imgoutput_image[0:img.shape[0], 0:img.shape[1]] = img# 定義第二張圖為img經過透視變換后的圖像warped = perspect_transform(img, src, dst)output_image[0:img.shape[0], img.shape[1]:] = warpedgray_warped = cv2.cvtColor(warped, cv2.COLOR_RGB2GRAY)ret, img_thresh = cv2.threshold(gray_warped, 160, 255, cv2.THRESH_BINARY)test_image = np.expand_dims(img_thresh, axis=2)output_image[180:340, img.shape[1]:, 0] = img_thresh# 漫游者號的坐標系,在每一個圖像中都不同xpix, ypix = rover_coords(img_thresh)worldmap = data.worldmapx_world, y_world = pix_to_world(xpix, ypix, data.xpos[data.count], data.ypos[data.count], data.yaw[data.count], data.worldmap.shape[0], 20 )# 相應位置顏色+1worldmap[y_world, x_world] += 1map_add = cv2.addWeighted(ground_truth_3d, 1, worldmap, 0.2, 0)output_image[img.shape[0]:, 0:data.worldmap.shape[1]] = map_addcv2.putText(output_image, "Test_Video", (20, 20),cv2.FONT_HERSHEY_COMPLEX, 0.4, (255, 255, 255), 1)# 跟蹤Databucket中的索引if data.count < len(data.images) - 1:data.count += 1return output_image# 將處理過的圖像制作成視頻,使用moviepy
output = '.../test_mapping.mp4'clip = ImageSequenceClip(data.images, fps=60)
new_clip = clip.fl_image(process_image)# 只接受彩色圖片
new_clip.write_videofile(output, audio=False)
輸出的視頻結果如下:
總結
以上是生活随笔為你收集整理的Udacity机器人软件工程师课程笔记(三)-样本搜索和找回-基于漫游者号模拟器-使用moviepy输出测试视频的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Udacity机器人软件工程师课程笔记(
- 下一篇: Udacity机器人软件工程师课程笔记(