檢測坑窪,水坑,不同類型的地形等
本期是關于路面語義分割方法的。是以,這裡的重點是路面模式,例如:車輛行駛在哪種路面上或道路上是否有損壞,還有道路标記和減速帶等等。
0.1 簡介有時我們需要确定路面是青瀝路面、鵝卵石路面亦或是未鋪砌的路面?出于對駕駛員的安全以及車内人員的舒适性的考慮我們需要提前知道路面情況。為了實作這些目标,将使用卷積神經網絡(CNN)進行路面的語義分割。CNN體系結構是U-NET [4],該體系結構旨在執行醫學圖像中的語義分割任務,但已成功應用于許多問題當中。另外,使用resnet34和resnet50完成此方法的實驗。對于資料增強步驟,使用來自fastai庫的标準選項,并進行了水準旋轉和透視變形。為了訓練神經網絡并測試和驗證結果,使用來自RTK資料集中的701張圖像建立了以下路況(GT):

02. 實作步驟
第一步-初始設定
from fastai.vision import *from fastai.vision.interpret import *from fastai.callbacks.hooks import *from pathlib import Pathfrom fastai.utils.mem import *torch.backends.cudnn.benchmark=True
由于我們将使用Google驅動器中的資料集,是以需要對其進行挂載:
from google.colab import drivedrive.mount('/content/gdrive')
大家将看到類似下圖的内容,單擊連結,我們就獲得授權碼,是以隻需将授權碼複制并粘貼到期望的字段中即可。
現在,隻需将我們的Google雲端硬碟作為檔案系統通路即可。接下來加載我們的資料。
第二步-準備資料
path = Path('gdrive/My Drive/Colab Notebooks/data/')path.ls()
其中“ image ”是包含原始圖像的檔案夾。“ labels ”是一個檔案夾,其中包含我們将用于訓練和驗證的圖像,這些圖像是8位灰階圖。在“ colorLabels ”中,有原始的彩色圖像,可以将其用于視覺比較。“ valid.txt ”檔案包含随機選擇用于驗證的圖像名稱清單。最後,“ codes.txt ”檔案包含帶有類名稱的清單。
codes = np.loadtxt(path/'codes.txt', dtype=str); code
現在,我們定義原始圖像和GT圖像的路徑,進而可以通路檔案夾中的所有圖像。
path_lbl = path/'labels'path_img = path/'images'
fnames = get_image_files(path_img)fnames[:3]len(fnames)
lbl_names = get_image_files(path_lbl)lbl_names[:3]len(lbl_names)
img_f = fnames[139]img = open_image(img_f)img.show(figsize=(5,5))
我們可以看到一個示例,資料集中的圖像139。
接下來,我們使用一個函數來從原始圖像中推斷檔案名,該檔案名負責每個像素的顔色編碼。
get_y_fn = lambda x: path_lbl/f'{x.stem}{x.suffix}'
mask = open_mask(get_y_fn(img_f))mask.show(figsize=(5,5), alpha=1)
src_size = np.array(mask.shape[1:])src_size,mask.data
第三步 —無權重檢測
現在我們進入第3步。讓我們建立一個DataBunch,使用資料塊API訓練我們的第一個模型。定義圖像來源,将用于驗證的圖像與原始圖像建立對應關系。對于資料擴充,fastai庫提供了很多選項,但是在這裡,我們将僅使用帶有的預設選項get_transforms(),該選項由随機的水準旋轉和透視變形組成。在transform調用時我們要令tfm_y=True,以確定每個蒙版及其原始圖像的資料集中資料擴充的轉換都相同。想象一下,如果我們旋轉原始圖像,但是與該圖像相對應的蒙版沒有旋轉,那将是多麼混亂!
size = src_sizefree = gpu_mem_get_free_no_cache()# the max size of bs depends on the available GPU RAMif free > 8200: bs=8else: bs=4print(f"using bs={bs}, have {free}MB of GPU RAM free")src = (SegmentationItemList.from_folder(path_img) .split_by_fname_file('../valid.txt') .label_from_func(get_y_fn, classes=codes)) data = (src.transform(get_transforms(), size=size, tfm_y=True) .databunch(bs=bs) .normalize(imagenet_stats))
使用lesson3-camvid定義準确度度量和權衰減。我們使用resnet34模型,定義學習率lr_find(learn)為1e-4。
name2id = {v:k for k,v in enumerate(codes)}def acc_rtk(input, target): target = target.squeeze(1) mask = target != 0 return (input.argmax(dim=1)[mask]==target[mask]).float().mean() metrics=acc_rtkwd=1e-2learn = unet_learner(data, models.resnet34, metrics=metrics, wd=wd)lr_find(learn)learn.recorder.plot()
接下來,我們運作fit_one_cycle()10次以檢查模型的運作情況。
lr=1e-4learn.fit_one_cycle(10, slice(lr), pct_start=0.9)
interp = SegmentationInterpretation.from_learner(learn)top_losses, top_idxs = interp.top_losses((288,352))mean_cm, single_img_cm = interp._generate_confusion()df = interp._plot_intersect_cm(mean_cm, "Mean of Ratio of Intersection given True Label")
别忘了儲存我們到目前為止訓練的模型。
learn.save('stage-1')
slice關鍵字用于擷取起始值和終止值,在第一層以起始值開始訓練,并且在到達終止值時結束。
learn.unfreeze()lrs = slice(lr/400,lr/4)learn.fit_one_cycle(100, lrs, pct_start=0.9)learn.save('stage-2')
這是我們的第一個沒有權重的模型,該模型在路面上可以正常使用,但并不普适。
第四步-帶有權重的模型
我們還要繼續使用第一個模型。這部分與第3步幾乎完全相同,因為資料綁定,我們隻需要記住加載先前的模型即可。
learn.load('stage-2')
在我們開始教育訓練過程之前,我們需要權重重。我定義了這些權重,以便嘗試與每個類在資料集中出現的數量(像素數)成正比。
balanced_loss = CrossEntropyFlat(axis=1, weight=torch.tensor([1.0,5.0,6.0,7.0,75.0,1000.0,3100.0,3300.0,0.0,270.0,2200.0,1000.0,180.0]).cuda())learn = unet_learner(data, models.resnet34, metrics=metrics, loss_func=balanced_loss, wd=wd)
其餘部分與前面介紹的第三步完全一樣。得到的結果有什麼變化。
現在,對于所有類來說,我們似乎都有一個更合理的結果。記住要儲存!
learn.save('stage-2-weights')
結果
最後,讓我們看看我們的圖像。首先,最好儲存我們的結果或測試圖像。
img_f = fnames[655]img = open_image(img_f)img.show(figsize=(5,5))
prediction = learn.predict(img)prediction[0].show(figsize=(5,5))
results_save = 'results'path_rst = path/results_savepath_rst.mkdir(exist_ok=True)
def save_preds(names): i=0 #names = dl.dataset.items for b in names: img_s = fnames[i] img_toSave = open_image(img_s) img_split = f'{img_s}' img_split = img_split[44:] predictionSave = learn.predict(img_toSave) predictionSave[0].save(path_rst/img_split) #Save Image i += 1 print(i) save_preds(fnames)
可是等等!圖像全部看起來都是黑色的,我們的結果在哪裡???冷靜一下,這些就是結果,隻是沒有顔色圖,如果在整個螢幕上以高亮度打開這些圖像之一,則可以看到小的變化,即“十一色灰色”。是以,讓我們對結果進行上色以使其更具表現力嗎?現在,我們将使用OpenCV并建立一個新檔案夾來儲存彩色結果。
import osimport globimport base64import cv2 as cv
colored_results = 'results_color'path_crst = path/colored_resultspath_crst.mkdir(exist_ok=True)
是以,我們建立了一個函數來識别每個變化并為每個像素着色。
def colorfull(image): # grab the image dimensions #height = image.shape[0] #width = image.shape[1] width = 288 height = 352 # loop over the image, pixel by pixel for x in range(width): for y in range(height): b, g, r = frame[x, y] if (b, g, r) == (0,0,0): #background frame[x, y] = (0,0,0) elif (b, g, r) == (1,1,1): #roadAsphalt frame[x, y] = (85,85,255) elif (b, g, r) == (2,2,2): #roadPaved frame[x, y] = (85,170,127) elif (b, g, r) == (3,3,3): #roadUnpaved frame[x, y] = (255,170,127) elif (b, g, r) == (4,4,4): #roadMarking frame[x, y] = (255,255,255) elif (b, g, r) == (5,5,5): #speedBump frame[x, y] = (255,85,255) elif (b, g, r) == (6,6,6): #catsEye frame[x, y] = (255,255,127) elif (b, g, r) == (7,7,7): #stormDrain frame[x, y] = (170,0,127) elif (b, g, r) == (8,8,8): #manholeCover frame[x, y] = (0,255,255) elif (b, g, r) == (9,9,9): #patchs frame[x, y] = (0,0,127) elif (b, g, r) == (10,10,10): #waterPuddle frame[x, y] = (170,0,0) elif (b, g, r) == (11,11,11): #pothole frame[x, y] = (255,0,0) elif (b, g, r) == (12,12,12): #cracks frame[x, y] = (255,85,0) # return the colored image return image
接下來,我們讀取每個圖像,調用函數并儲存最終結果。
fqtd = 0
filenames = [img for img in glob.glob(str(path_rst/"*.png"))]
filenames.sort()
for img in filenames: frame = cv.imread(img) frame = colorfull(frame) frame = cv.cvtColor(frame,cv.COLOR_BGR2RGB) name = "%09d.png"%fqtd cv.imwrite(os.path.join(path_crst, name), frame)
fqtd += 1 print(fqtd)
print("Done!")
使用以下過程,%timeit我們可以達到以下目的,是以此過程可能會花費不必要的時間:
03. 總結