路面语义分割
重磅干货,第一时间送达
检测坑洼,水坑,不同类型的地形等
本期是关于路面语义分割方法的。因此,这里的重点是路面模式,例如:车辆行驶在哪种路面上或道路上是否有损坏,还有道路标记和减速带等等。
第一步-初始设置
from fastai.vision import *
from fastai.vision.interpret import *
from fastai.callbacks.hooks import *
from pathlib import Path
from fastai.utils.mem import *
torch.backends.cudnn.benchmark=True
由于我们将使用Google驱动器中的数据集,因此需要对其进行挂载:
from google.colab import drive
drive.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_size
free = gpu_mem_get_free_no_cache()
# the max size of bs depends on the available GPU RAM
if free > 8200: bs=8
else: bs=4
print(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_rtk
wd=1e-2
learn = unet_learner(data, models.resnet34, metrics=metrics, wd=wd)
lr_find(learn)
learn.recorder.plot()
接下来,我们运行fit_one_cycle()10次以检查模型的运行情况。
lr=1e-4
learn.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_save
path_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 os
import glob
import base64
import cv2 as cv
colored_results = 'results_color'
path_crst = path/colored_results
path_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我们可以达到以下目的,因此此过程可能会花费不必要的时间:
在很多情况下,识别路面状况都很重要,基于此车辆或驾驶员可以做出调整,使驾驶变的更加安全,舒适和高效。这在可能存在更多道路维护问题或相当数量的未铺设道路的发展中国家中尤其重要。对于处理路面变化的环境,对于高速公路分析和养护部门也很有用,以便使他们在评估道路质量和确定需要维护的地方的工作自动化。
交流群