深度学习(5)数据处理-resize

resize由来与发展

对输入图像进行大小调整,为什么要这样做呢?

因为图像输入,是转为向量(矩阵)输入的,向量的纬度一般是固定的,所以要进行大小调整

输入向量维数 = 输入层节点数

举例,假如训练输入的是一张张图片,每张图片对应一个矩阵,在这个矩阵里面,将每行看作一个向量,这个向量的列数是固定的

tf.placeholder(tf.float32, [None, 32, 32, 3])

这个函数接收输入的大小就是32*32pixel,当然你可以指定其他的大小,那么在数据预处理的时候,就需要先将图片整理成这样的大小

几何变换的一个常规操作是要把图片的大小resize到相同尺寸,便于统一处理。同时也是要考虑到机器的配置,图片越大在进行运算处理时要求的配置就越高,常规情况下我们会把图片resize到500以下进行处理。很多预训练模型的图片size大小为299×299×3(Xception、InceptionV3…)、224×224×3(VGG16、ResNet50…)。

  • 将输入图像resize到一个固定的尺寸这件事贯穿了DL在目标检测领域的始终:

    1. r-cnn刚提出的阶段,由于网络结构的限制,进入 全连接层的输入维度必须是固定的,那么一个最简单的解决方案就是把输入图像归一化到固定的尺寸,得到输出后反变换回去。这种resize的做法实际上是为了适应卷积神经网络的一种让步。
    2. 随着人们对检测精度的追求,大家开始关注resize所带来的损失。不论是sppnet还是roi pooling,他们都致力于将任意维度的特征图转化成固定的维度以适应全连接的输入,这一思路从理论上解除了整个检测网络对输入的尺寸依赖,这也是fast r-cnn的一个改进点,从此输入图像可以是任意宽高。
    3. 然而各路算法在使用gpu做加速的过程中遇到了新的问题,每一个batch的计算必须拥有相同的size,这是算法本身之外的一个限制,但这个限制却成为了并行加速计算的拦路虎。yolo的作者针对这个情况,在不改变原始图像宽高比的前提下进行了resize操作,具体的做法是对不符合原图比例的区域进行padding。

    现在很多的检测网络都不使用全连接层,就是考虑到图像输入尺寸不一致的问题,从而构建全卷积网络!对于全卷积网络,输入图片的尺度可以为任意的,相当于滑动窗口检测,但为什么很多算法要在测试阶段也变成统一尺寸呢?这就是考虑训练阶段的问题,由于现在训练集的图片可能大小也不一致,而batch的读取方式就限制了必须保持训练图像尺寸的一致性,所以最终网络的感受野也就限定了那个尺度范围内,从而在测试时不能直接使用原图像,还是要进行相同的缩放操作!

但对于超大分辨率的图片,但目标却很小,如kaggle的卫星图船舶检测,上述操作就不太管用了,这时候就要考虑到对图像进行切割,分别送到现在的神经网络,所以还是看你的数据怎么训练的!

resize的方式

  • 对于图片尺寸变化时的插值算法:
    INTER_NEAREST - 最近邻插补
    INTER_LINEAR - 双线性插值(默认方法)
    INTER_AREA - 像素面积相关重采样,图片由大resize小时效果较好
    INTER_CUBIC - 双三次插值,图片由小resize大时效果较好
    INTER_LANCZOS4 - 兰索斯插值
img = cv2.imread(img_path) #读进来的为BGR
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #BGR转RGB
img4 = cv2.resize(img,(500,500)) #默认用INTER_LINEAR插值算法
plt.imshow(img4)

在这里插入图片描述

img5 = cv2.resize(img,(500,500),interpolation=cv2.INTER_AREA)
plt.imshow(img5)

在这里插入图片描述
其实改变效果很明显,特别是在图片处理任务中发现怎么样改变模型都提高不了效果时,不妨回过头来查看下插值算法有没有用好。

不同插值算法对训练结果的影响

很多时候在resize图片的时候,插值算法会影响到结果的训练结果的好坏。

pillow文档里给出了其各个插值算法的使用情况和优劣。
在这里插入图片描述

上表中Performance应该是速度表现的意思,越简单的插值算法速度越快但效果不会很理想。
下文档是对于图片缩小和放大均能保持较高的图片质量的Image.LANCZOS插值算法:

img_size = 500
img3 = img.resize((img_size,img_size),Image.LANCZOS)
img3

以上不同插值算法的选用对图片resize时质量的影响是巨大的。如果不注意到这个点,直接将很大的图片resize到较小的尺寸后进行训练,图片其实已经丢失掉了很多特征,最终的效果自然不会很好。

caffe数据转换成lmdb中的resize

caffe,将数据转换为lmdb/leveldb的convert_imageset.cpp源码可见,是使用ReadImageToDatum函数读入图片,根据输入参数可知,输出为resize后的图像

......
DEFINE_int32(resize_width, 0, "Width images are resized to");  //图像宽度重置  
DEFINE_int32(resize_height, 0, "Height images are resized to");//图像高度重置  
......
 //设置图像的长度,宽度  
  int resize_height = std::max<int>(0, FLAGS_resize_height);  
  int resize_width = std::max<int>(0, FLAGS_resize_width);  
......
//检查图像尺寸  
    if (check_size) {
    
      
      if (!data_size_initialized) {
    
      
        data_size = datum.channels() * datum.height() * datum.width();  
        data_size_initialized = true;  
      } else {
    
      
        const std::string& data = datum.data();  
        CHECK_EQ(data.size(), data_size) << "Incorrect data field size "  
            << data.size();  
      }  
    }  
  • ReadImageToDatum 函数:

读入图像到Datum

bool ReadImageToDatum(const string& filename, const int label,
    const int height, const int width, const bool is_color,
    const std::string & encoding, Datum* datum) {
    
    
  cv::Mat cv_img = ReadImageToCVMat(filename, height, width, is_color);
  if (cv_img.data) {
    
    
    if (encoding.size()) {
    
    
      if ( (cv_img.channels() == 3) == is_color && !height && !width &&
          matchExt(filename, encoding) )
        return ReadFileToDatum(filename, label, datum);
      std::vector<uchar> buf;
      cv::imencode("."+encoding, cv_img, buf);
      datum->set_data(std::string(reinterpret_cast<char*>(&buf[0]),
                      buf.size()));
      datum->set_label(label);
      datum->set_encoded(true);
      return true;
    }
    CVMatToDatum(cv_img, datum);//cvmat转为Datum格式
    datum->set_label(label);
    return true;
  } else {
    
    
    return false;
  }
}

ReadImageToDatum函数又是调用ReadImageToCVMat函数读入图像和resize的宽高进行缩放的

  • ReadImageToCVMat 函数:

以cvMat格式读入图像

cv::Mat ReadImageToCVMat(const string& filename,//is_color 为1读入彩色图像,0灰度图
    const int height, const int width, const bool is_color) {
    
    
    //height,width都不为0则把图像resize 到height*width
  cv::Mat cv_img;
  int cv_read_flag = (is_color ? CV_LOAD_IMAGE_COLOR :
    CV_LOAD_IMAGE_GRAYSCALE);
  cv::Mat cv_img_origin = cv::imread(filename, cv_read_flag);//读入图像
  if (!cv_img_origin.data) {
    
    
    LOG(ERROR) << "Could not open or find file " << filename;
    return cv_img_origin;
  }
  if (height > 0 && width > 0) {
    
    
    cv::resize(cv_img_origin, cv_img, cv::Size(width, height));
  } else {
    
    
    cv_img = cv_img_origin;
  }
  return cv_img;
}

可以看出caffe底层是使用opencv的 默认双线性差值的resize方法 ,通过比较可知opencv的resize属性只有area后的图像质量较好,保存信息较为丰富,不会失去边缘信息,因此改resize方式,重新make,使用area属性的caffe。

不同resize的影响(总)

  • 不同的 resize 方式对最终的结果有一定的影响,尤其是用随机图片评估时会更加明显。
    .
    看似用的是同一个神经网络,同一个训练集,但在输入的处理上仍然会有各种不同。比如 Inception 要求 299x299,你可以直接用 ImageMagick 将原始图片处理成 299x299 再输入,也可以用 OpenCV 读入图片后再转成 299x299,还可以直接用深度学习框架(TensorFlow/Caffe)进行 resize。甚至同一种方法也可能有各种不同的参数控制,比如最邻近插值、双线性插值、双立方插值等。 通过不同的 resize 方法训练出来的网络参数,或者同一张图片不同方法 resize 后预测的输出,数值是存在差异的。 如果使用的是质量较低的大规模数据集,差异可能会非常明显。

  • 不同的 resize 方式对最终结果的影响无法确定。
    .
    换种说法,这可能是个玄学。这算是一个经验总结,就不多讲了。也就是说,某种 resize 方式有时可能让结果变好,有时也可能让结果变差。

  • 训练、评估和线上预测时统一图片处理方式有一些好处
    .
    有的公司在训练神经网络时使用一种框架,上线时使用另一种框架;或者训练时采取一种输入,上线时采取另一种输入。都会导致线上服务的预测结果跟评估结果不一致,导致排查问题较为复杂。
    .
    有时候为了性能考虑,必须在客户端完成图片处理,resize 成较小图片后再传给服务端。而客户端往往使用的不同的库,比如 iOS 可以使用 Core Graphics 库或者 UIKit 库,Android 的 Bitmap 库,这些库在服务端是基本上无法使用的。这时候就需要要知道这可能会导致线上效果与评估结果有不一致的可能,并且采取一定的措施来消减这样的不同。

综上,这一问题至今为止并没有人尝试去完美的解决,而更多的是在理论与工程,精度的速度上做取舍和权衡。

  • 得到的都是侥幸,失去才是人生!
  • 钱到用时方恨少,点赞不够吃不饱,手有余香请点赞,您要赏点我不敢!

参考文章
链接:https://www.jianshu.com/p/c45d89776d85
链接:https://zhuanlan.zhihu.com/p/43268790
链接:https://www.zhihu.com/question/310237588/answer/626222815

猜你喜欢

转载自blog.csdn.net/weixin_42535423/article/details/103720514
今日推荐