路面語義分割
點(diǎn)擊上方“小白學(xué)視覺”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時(shí)間送達(dá)
檢測(cè)坑洼,水坑,不同類型的地形等
本期是關(guān)于路面語義分割方法的。因此,這里的重點(diǎn)是路面模式,例如:車輛行駛在哪種路面上或道路上是否有損壞,還有道路標(biāo)記和減速帶等等。


第一步-初始設(shè)置
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驅(qū)動(dòng)器中的數(shù)據(jù)集,因此需要對(duì)其進(jìn)行掛載:
from?google.colab?import?drivedrive.mount('/content/gdrive')
大家將看到類似下圖的內(nèi)容,單擊鏈接,我們就獲得授權(quán)碼,因此只需將授權(quán)碼復(fù)制并粘貼到期望的字段中即可。

現(xiàn)在,只需將我們的Google云端硬盤作為文件系統(tǒng)訪問即可。接下來加載我們的數(shù)據(jù)。
第二步-準(zhǔn)備數(shù)據(jù)
path = Path('gdrive/My Drive/Colab Notebooks/data/')path.ls()
其中“?image?”是包含原始圖像的文件夾?!?labels?”是一個(gè)文件夾,其中包含我們將用于訓(xùn)練和驗(yàn)證的圖像,這些圖像是8位灰度圖。在“?colorLabels?”中,有原始的彩色圖像,可以將其用于視覺比較?!?valid.txt?”文件包含隨機(jī)選擇用于驗(yàn)證的圖像名稱列表。最后,“?codes.txt?”文件包含帶有類名稱的列表。

codes?=?np.loadtxt(path/'codes.txt',?dtype=str);?code
現(xiàn)在,我們定義原始圖像和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))
我們可以看到一個(gè)示例,數(shù)據(jù)集中的圖像139。

接下來,我們使用一個(gè)函數(shù)來從原始圖像中推斷文件名,該文件名負(fù)責(zé)每個(gè)像素的顏色編碼。
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
第三步?—無權(quán)重檢測(cè)
現(xiàn)在我們進(jìn)入第3步。讓我們創(chuàng)建一個(gè)DataBunch,使用數(shù)據(jù)塊API訓(xùn)練我們的第一個(gè)模型。定義圖像來源,將用于驗(yàn)證的圖像與原始圖像建立對(duì)應(yīng)關(guān)系。對(duì)于數(shù)據(jù)擴(kuò)充,fastai庫提供了很多選項(xiàng),但是在這里,我們將僅使用帶有的默認(rèn)選項(xiàng)get_transforms(),該選項(xiàng)由隨機(jī)的水平旋轉(zhuǎn)和透視變形組成。在transform調(diào)用時(shí)我們要令tfm_y=True,以確保每個(gè)蒙版及其原始圖像的數(shù)據(jù)集中數(shù)據(jù)擴(kuò)充的轉(zhuǎn)換都相同。想象一下,如果我們旋轉(zhuǎn)原始圖像,但是與該圖像相對(duì)應(yīng)的蒙版沒有旋轉(zhuǎn),那將是多么混亂!
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定義準(zhǔn)確度度量和權(quán)衰減。我們使用resnet34模型,定義學(xué)習(xí)率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()

接下來,我們運(yùn)行fit_one_cycle()10次以檢查模型的運(yùn)行情況。
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")

別忘了保存我們到目前為止訓(xùn)練的模型。
learn.save('stage-1')slice關(guān)鍵字用于獲取起始值和終止值,在第一層以起始值開始訓(xùn)練,并且在到達(dá)終止值時(shí)結(jié)束。
learn.unfreeze()lrs = slice(lr/400,lr/4)learn.fit_one_cycle(100, lrs, pct_start=0.9)learn.save('stage-2')

這是我們的第一個(gè)沒有權(quán)重的模型,該模型在路面上可以正常使用,但并不普適。

第四步-帶有權(quán)重的模型
我們還要繼續(xù)使用第一個(gè)模型。這部分與第3步幾乎完全相同,因?yàn)閿?shù)據(jù)綁定,我們只需要記住加載先前的模型即可。
learn.load('stage-2')在我們開始培訓(xùn)過程之前,我們需要加權(quán)重。我定義了這些權(quán)重,以便嘗試與每個(gè)類在數(shù)據(jù)集中出現(xiàn)的數(shù)量(像素?cái)?shù))成正比。
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)
其余部分與前面介紹的第三步完全一樣。得到的結(jié)果有什么變化。

現(xiàn)在,對(duì)于所有類來說,我們似乎都有一個(gè)更合理的結(jié)果。記住要保存!
learn.save('stage-2-weights')結(jié)果
最后,讓我們看看我們的圖像。首先,最好保存我們的結(jié)果或測(cè)試圖像。
img_f = fnames[655]img = open_image(img_f)=(5,5))prediction = learn.predict(img)=(5,5))results_save = 'results'path_rst = path/results_save=True)def save_preds(names):i=0#names = dl.dataset.itemsfor 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)#Save Imagei += 1print(i)save_preds(fnames)
可是等等!圖像全部看起來都是黑色的,我們的結(jié)果在哪里???冷靜一下,這些就是結(jié)果,只是沒有顏色圖,如果在整個(gè)屏幕上以高亮度打開這些圖像之一,則可以看到小的變化,即“十一色灰色”。因此,讓我們對(duì)結(jié)果進(jìn)行上色以使其更具表現(xiàn)力嗎?現(xiàn)在,我們將使用OpenCV并創(chuàng)建一個(gè)新文件夾來保存彩色結(jié)果。
import osimport globimport base64import cv2 as cvcolored_results = 'results_color'path_crst = path/colored_resultspath_crst.mkdir(exist_ok=True)
因此,我們創(chuàng)建了一個(gè)函數(shù)來識(shí)別每個(gè)變化并為每個(gè)像素著色。
def colorfull(image):# grab the image dimensions#height = image.shape[0]#width = image.shape[1]width = 288height = 352# loop over the image, pixel by pixelfor x in range(width):for y in range(height):b, g, r = frame[x, y]if (b, g, r) == (0,0,0): #backgroundframe[x, y] = (0,0,0)elif (b, g, r) == (1,1,1): #roadAsphaltframe[x, y] = (85,85,255)elif (b, g, r) == (2,2,2): #roadPavedframe[x, y] = (85,170,127)elif (b, g, r) == (3,3,3): #roadUnpavedframe[x, y] = (255,170,127)elif (b, g, r) == (4,4,4): #roadMarkingframe[x, y] = (255,255,255)elif (b, g, r) == (5,5,5): #speedBumpframe[x, y] = (255,85,255)elif (b, g, r) == (6,6,6): #catsEyeframe[x, y] = (255,255,127)elif (b, g, r) == (7,7,7): #stormDrainframe[x, y] = (170,0,127)elif (b, g, r) == (8,8,8): #manholeCoverframe[x, y] = (0,255,255)elif (b, g, r) == (9,9,9): #patchsframe[x, y] = (0,0,127)elif (b, g, r) == (10,10,10): #waterPuddleframe[x, y] = (170,0,0)elif (b, g, r) == (11,11,11): #potholeframe[x, y] = (255,0,0)elif (b, g, r) == (12,12,12): #cracksframe[x, y] = (255,85,0)# return the colored imagereturn image
接下來,我們讀取每個(gè)圖像,調(diào)用函數(shù)并保存最終結(jié)果。
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"%fqtdcv.imwrite(os.path.join(path_crst, name), frame)fqtd += 1print(fqtd)print("Done!")
使用以下過程,%timeit我們可以達(dá)到以下目的,因此此過程可能會(huì)花費(fèi)不必要的時(shí)間:


在很多情況下,識(shí)別路面狀況都很重要,基于此車輛或駕駛員可以做出調(diào)整,使駕駛變的更加安全,舒適和高效。這在可能存在更多道路維護(hù)問題或相當(dāng)數(shù)量的未鋪設(shè)道路的發(fā)展中國家中尤其重要。對(duì)于處理路面變化的環(huán)境,對(duì)于高速公路分析和養(yǎng)護(hù)部門也很有用,以便使他們?cè)谠u(píng)估道路質(zhì)量和確定需要維護(hù)的地方的工作自動(dòng)化。
交流群
歡迎加入公眾號(hào)讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器、自動(dòng)駕駛、計(jì)算攝影、檢測(cè)、分割、識(shí)別、醫(yī)學(xué)影像、GAN、算法競(jìng)賽等微信群(以后會(huì)逐漸細(xì)分),請(qǐng)掃描下面微信號(hào)加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三?+?上海交大?+?視覺SLAM“。請(qǐng)按照格式備注,否則不予通過。添加成功后會(huì)根據(jù)研究方向邀請(qǐng)進(jìn)入相關(guān)微信群。請(qǐng)勿在群內(nèi)發(fā)送廣告,否則會(huì)請(qǐng)出群,謝謝理解~
