檢測坑窪,水坑,不同型別的地形等
本期是關於路面語義分割方法的。因此,這裡的重點是路面模式,例如:車輛行駛在哪種路面上或道路上是否有損壞,還有道路標記和減速帶等等。
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: s = fnames[i] toSave = open_image(img_s) split = f'{img_s}' split = 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 cvcolored_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 = 0filenames = [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. 總結
在很多情況下,識別路面狀況都很重要,基於此車輛或駕駛員可以做出調整,使駕駛變的更加安全,舒適和高效。這在可能存在更多道路維護問題或相當數量的未鋪設道路的發展中國家中尤其重要。對於處理路面變化的環境,對於高速公路分析和養護部門也很有用,以便使他們在評估道路質量和確定需要維護的地方的工作自動化。