当训练数据时,如果数据只是一列,而输出也是一列,那就对应着(train,label)输入模型即可。但是如果处理对象是图片时,应该如何将图片作为数据输入模型。我只记录到目前为止的理解作为参考,后续还会修改(应该会的吧!)
一、对图片的结构的了解
图片分成了彩色图和非彩色图:
彩色图,我们用python中任意一个可以读入图片的函数读入查看,可以得到彩色图的维度是(H,W,channel=3)
非彩色图,我认为可以分为灰度图和黑白图,非彩色图他们都只有二维,即(H,W),我原本以为会有一个channel=1,后来发现并木有。灰度图是(H,W)维的0~255的数值,而黑白图除了0就是255,没有别的其他值(哦,顺便提一句,H是height,W是width,channel是颜色通道)
在这里,我想要着重的对mask图(也就是image对应的label)进行分析,我们输入的image就是良民,没什么特殊的。但是我们输入的mask图却有的不太对劲:
如果是二分类问题,那么mask图应当是二值的,如果二值指的是0和255,那么画面就十分和谐了,就像我们常规认识的那样:
![](https://i-blog.csdnimg.cn/blog_migrate/1870423a23b4371a935e7e3aa978b2fe.png)
![](https://i-blog.csdnimg.cn/blog_migrate/00be6a1b6ee6d532c29cb46f26167414.png)
如果是多分类问题,当我们输下左边图一的照片时,在我的印象中应该对应的mask是一个美美的就像图二有木有,
但是实际的图是个什么玩意,居然全是黑的图三!!(于是我一度认为这个数据集是坏的)
![](https://i-blog.csdnimg.cn/blog_migrate/74009ca68b1090bf54c4cba509a79ce1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7895a001c82b666df61e4886dbd1dc21.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3794ba4a0031c4b67587b51f87d7ad56.png)
实际上这个黑乎乎的图并没有坏!它就是这个样儿!因为多分类的mask图里每一个像素点的值其实是类别值,比如说我们要分成12类,那么这张图里面的像素点的值全都是在0-11的数字。而我们知道0是黑,255是白,在0-11之间的数字在我们人眼看来也和全黑差不多了。但是当你把这张图读入并查看它的每一个像素点时,你就会发现其实里面是有蹊跷的。
如果想要将这张黑图显示出来,我们可以使用一些涂色的手段,在这里贴一下我之前找到的函数。
(1)首先是创建一个颜色字典,留着为后面用
Sky = [128,128,128]
Building = [128,0,0]
Pole = [192,192,128]
Road = [128,64,128]
Pavement = [60,40,222]
Tree = [128,128,0]
SignSymbol = [192,128,128]
Fence = [64,64,128]
Car = [64,0,128]
Pedestrian = [64,64,0]
Bicyclist = [0,128,192]
Unlabelled = [0,0,0]
COLOR_DICT = np.array([Sky, Building, Pole, Road, Pavement,
Tree, SignSymbol, Fence, Car, Pedestrian, Bicyclist, Unlabelled])
#首先创建一个颜色字典,可以把它想象成一个染色盘(小学美术课用的那种),然后我们按照序号每一类取一个
#颜色出来涂上,至于名字为什么取什么sky,buliding?哎想怎么叫怎么叫呗!
(2)其次是调用染色函数(染好色以后上述的黑黑的mask图三就会变成美美的图二了)(问题1:除255如果有人看懂了可以留个言告告我哈哈。。我也没整明白,感jio不除才对,结果除了数值没变,不除反而报错,哎你说我这暴脾气!)
def labelVisualize(num_class,color_dict,img):
#img就是我们的黑黑的mask图,而img是(H,W)二维的,我们需要加上一维,也就是下面这行
img_out = np.zeros(img.shape + (3,))
#img.shape返回的是(H,W)的元组,而(3,)也代表元组,只不过是一维的,二者相加后就是一个
#三维的元组(H,W,channel)
for i in range(num_class):
img_out[img == i,:] = color_dict[i]
#总共循环num_class次,比如numclass=11,那么我们在循环11次过程中,每一次我们都找到img上像素点等于i
#的位置为它涂上第i种颜色(因为我们的黑黑的mask图像素点都是0-11之间的值),涂完后就可以了
return img_out / 255
#这儿为啥除以255呢???
#不除的话会报错ValueError: Images of type float must be between -1 and 1 具体原因希望有人可以tell me
此外,虽然我没有见过,但是如果某一天二分类问题时候的mask图也是黑色说不定里边就只有0和1,处理同上 。
二、训练过程中图片结构的变化了解
我们知道,我们在训练时候加入输入的image结构是s1,而训练时输入的mask是s2。那么,我们在训练好后测试时也应当输入结构为s1的图片,并且我们会得到结构为s2的结果。另外,有一点需要注意的是,s1和s2我们也不能随意规定,因为你的model的输入输出维度才可以决定你要放入的图片维度。那么,我们怎么去修改图片的结构呢?
为了具体,我在这里选择了一个模型(FCN)作为讲解的model。首先,我们将模型summary一下观察一下它的整体结构:
省略了中间部分,这里只截取了输入和输出模型时的维度:
![](https://i-blog.csdnimg.cn/blog_migrate/58f3950a1a90c51dcd8cbef1e30ab865.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3dd97ed7c5752f391dfd787ca4732851.png)
首先分析输入,输入部分这里只放了第一层卷积后的层为(none,256,256,64),none指的是batchsize可以为任意值,256,256是指本次卷积后的feature-map的长和宽,最后一维64指的是有64个filter因此生成深度为64。从这些分析,我认为输入的image应该是(batchsize,H,W,channel),channel应该是可有可无的,因为它并没有什么作用(因为卷积过程会根据是否为彩色图对卷积核作相应的调整,因为我在本次实验中用到了flow_from_directory这样一个生成器函数,生成的返回值中是四维,也就是包括了channel所以我在这里也带上channel一起说)。
其次分析输出,输出的(none,65536,2)是指(none,H*W,num_class)。这里稍作解释,模型的输出是将一个(H,W)的mask图(上面包括了num_class种种类),转变成了一个(H*W,num_class)维度的one-hot编码形式。并且这里的模型由于加入了softmax层,输出的是one-hot编码下各种类的概率值。什么??没听懂怎么就是one-hot编码形式了??那么我手画个图给大家演示一波!
大致就是这样转换成了one-hot编码方式,具体转换的代码如下:
new_mask = np.zeros(mask.shape+(num_class,))
# new_mask的shape是(batchsize,H,W,num_class)
for i in range(num_class):
new_mask[mask==i,i]=1
#new_mask的每一层都标记上该层上像素值等于i的位置
new_mask=np.reshape(new_mask,new_mask.shape[0],new_mask.shape[1]*new_mask.shape[2],mask.shape[3]))
#在reshape后的new_mask的维度成了(batchsize,H*W,num_class)也就是onehot编码了
那么,既然我们训练时候的mask是one-hot形式,那么我想,当我们输入训练集的时候,输出应该也是一个onehot编码方式,并且因为加入了softmax层后输出的是概率值,应该不只是在对应的类别那一列标1,应该是每一类的概率。那么如果我们想要把每一个像素究竟属于哪一类找出来,应该找出每一列中概率值最大的作为该像素点的类别,之后再reshape成正常的(H,W)图就可以了。
具体的实现可以这样做:
mask=np.argmax(item,axis=1)#横着一行一行看,记录下来每一行的最大值概率所在的位置也就是该像素的类别
mask = np.reshape(mask,(H,W))
这样我们得到的就是和训练时候一样的(H,W)的mask图了,如果是多分类可以用上面的涂色的函数显示,如果是二分类可以直接乘以255。然后保存即可!
三、一个bug,but我还没有想好,但是有可能是你出错的地方
我们知道,在训练的时候,我们会使用数据增强来增加数据,比如说我使用的方法如下:
data_gen_args = dict(rotation_range=0.2,
width_shift_range=0.05,
height_shift_range=0.05,
shear_range=0.05,
zoom_range=0.05,
horizontal_flip=True,
fill_mode='nearest')
#用于数据增强的选项
mask_datagen = ImageDataGenerator(**aug_dict)
mask_generator = mask_datagen.flow_from_directory(
train_path,
classes = [mask_folder],
class_mode = None,
color_mode = mask_color_mode,
target_size = target_size,
batch_size = batch_size,
save_to_dir = save_to_dir,
save_prefix = mask_save_prefix,
seed = seed
)
#通过flow_from_directory可以无限的生成增强后的图片,是一个棒棒的生成器函数
但是如果使用生成器的话,就会产生如下的问题:
我输入的mask是二值图(只有0和255)
当我归一化后应当只有0和1,然后采用上述转化方法转换为one-hot编码是没有问题的,但是如果数据增强的话,其中就会不只是0和255,而是产生了一些浮动的float类型的数字,如下:
当前的mask图状态呢如下
可见,这里的image和mask都变成float类型了,而且我发现mask中除了0和255还多出来不少别的数,怪不得我用newmask[mask==i,i]发现白色(也就是类别1)全是0,因为这个图上根本就找不到1啊(归一化后)!!
并且如果是多分类问题的话,那么原本输入的mask图中可能是0-numclass的一些整数,使用newmask[mask==i,i]就可以转为one-hot编码,但是若是数据增强后便会有浮动而非是0-numclass的整数,这样的话,想要转为onehot编码采用我上述的手段就不行了,bug源头出在这里,如果是二分类我们可以直接除以255后根据结果是否小于0.5来赋值0还是1,但是如果是多分类问题该如何转换呢?暂时还没想出来,这是我的第二个问题。
先记录至此,未完待续。。。。。希望有人可以告我如何解决(#^.^#)!