Nice 发现
目前在图像分割中普遍会对训练图像和标签图像进行resize,一般采用最近邻插值法对训练图像和标签图像进行缩放,这样可以把图像resize成网络结构所需要的输入尺寸。但这会存在一个严重的问题,就算是最近邻插值法对标签图像进行resize也会在色彩边缘处插入错误的值,这意味着label image会插入错误的标签,这个问题通常会导致网络不能很好的训练。
所以只能采用下采样的方式对图片进行一个缩小操作,这样可以提高分割网络的训练精度。图像下采样的C++实现代码如下:
void scaleDownSampling(const Mat &src, Mat &dst, double xRatio, double yRatio)
{
//判断是否为uchar型的像素
CV_Assert(src.depth() == CV_8U);
// 计算缩小后图像的大小,取整防止越界
int rows = static_cast<int>(src.rows * xRatio);
int cols = static_cast<int>(src.cols * yRatio);
dst.create(rows, cols, src.type());
const int channels = src.channels();
switch (channels)
{
case 1: //单通道图像
{
uchar *p;
const uchar *origal;
for (int i = 0; i < rows; i++){
p = dst.ptr<uchar>(i);
//间隔采样取基数行
int row = static_cast<int>((i + 1) / xRatio + 0.5) - 1;
origal = src.ptr<uchar>(row);
for (int j = 0; j < cols; j++){
//间隔采样取基数列
int col = static_cast<int>((j + 1) / yRatio + 0.5) - 1;
p[j] = origal[col]; //把像素值赋给dst
}
}
break;
}
case 3://三通道图像
{
Vec3b *p;
const Vec3b *origal;
for (int i = 0; i < rows; i++) {
p = dst.ptr<Vec3b>(i);
int row = static_cast<int>((i + 1) / xRatio + 0.5) - 1;
origal = src.ptr<Vec3b>(row);
for (int j = 0; j < cols; j++){
int col = static_cast<int>((j + 1) / yRatio + 0.5) - 1;
p[j] = origal[col]; //把RGB三个值赋给dst
}
}
break;
}
}
}
对于CItyscapes数据集要resize成1024*512的尺寸,可以采用这个工具进行实现,可以运行命令
./scaleDownSampling help
来查看参数列表。
训练ENet
训练ENet网络可以分为两部分,第一部分仅训练ENet网络的编码结构,第二部分是训练ENet的编码-解码结构。先来看个采用cityscapes数据集的训练效果图吧,刺激一下感官:
效果还不错!!!
训练ENet_encoder结构
如果仅训练ENet的编码结构,需要把标签图像缩小8倍的尺寸,因为在编码结构最终会把图片卷成8倍小的尺寸。
训练encoder-decoder结构
训练ENet完整的结构保持图片和标签图片一致即可。对于cityscapes数据集建议采用1024512的输入尺寸,对于CamVid数据集建议采用480360的输入尺寸。
训练技巧
1、由于cityscapes数据集计算一次分类权重值的耗时很长,所以建议记录第一次训练的分类权重值,在之后重新训练时直接采用已有分类权重值来初始化优化器。同时保持分类权重值一致也是对恢复断点训练可以保持一致性。
2、训练ENet在误差值不再降低的时候,可以采用误差最小的模型并适当提高学习率重新训练,或许可以得到更低误差的模型。
完整的训练工程
github: https://github.com/Dawson-huang/Pytorch-ENet-Nice
欢迎star和fork,你的star可以给予我莫大的信心!!