对输入图像进行大小调整,为什么要这样做呢?
因为图像输入,是转为向量(矩阵)输入的,向量的纬度一般是固定的,所以要进行大小调整
输入向量维数 = 输入层节点数
举例,假如训练输入的是一张张图片,每张图片对应一个矩阵,在这个矩阵里面,将每行看作一个向量,这个向量的列数是固定的
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在目标检测领域的始终:
现在很多的检测网络都不使用全连接层,就是考虑到图像输入尺寸不一致的问题,从而构建全卷积网络!对于全卷积网络,输入图片的尺度可以为任意的,相当于滑动窗口检测,但为什么很多算法要在测试阶段也变成统一尺寸呢?这就是考虑训练阶段的问题,由于现在训练集的图片可能大小也不一致,而batch的读取方式就限制了必须保持训练图像尺寸的一致性,所以最终网络的感受野也就限定了那个尺度范围内,从而在测试时不能直接使用原图像,还是要进行相同的缩放操作!
但对于超大分辨率的图片,但目标却很小,如kaggle的卫星图船舶检测,上述操作就不太管用了,这时候就要考虑到对图像进行切割,分别送到现在的神经网络,所以还是看你的数据怎么训练的!
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图片的时候,插值算法会影响到结果的训练结果的好坏。
上表中Performance应该是速度表现的意思,越简单的插值算法速度越快但效果不会很理想。
下文档是对于图片缩小和放大均能保持较高的图片质量的Image.LANCZOS插值算法:
img_size = 500
img3 = img.resize((img_size,img_size),Image.LANCZOS)
img3
以上不同插值算法的选用对图片resize时质量的影响是巨大的。如果不注意到这个点,直接将很大的图片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();
}
}
读入图像到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的宽高进行缩放的
以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 方式对最终的结果有一定的影响,尤其是用随机图片评估时会更加明显。
.
看似用的是同一个神经网络,同一个训练集,但在输入的处理上仍然会有各种不同。比如 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