distort image作为NVCaffe一项常用的数据增量策略,其参数(distort_param)配置大体如下:
distort_param {
brightness_prob: 0.5
brightness_delta: 32
contrast_prob: 0.5
contrast_lower: 0.5
contrast_upper: 1.5
hue_prob: 0.5
hue_delta: 18
saturation_prob: 0.5
saturation_lower: 0.5
saturation_upper: 1.5
random_order_prob: 0
}
下面通过分析DistortImage源码来具体了解这些参数配置的意义,DistortImage函数在data_transform.cpp函数中定义,并在annotated_data_layer.cpp中根据参数配置情况来决定是否调用。
template
void DataTransformer::DistortImage(const Datum& datum,
Datum* distort_datum) {
if (!param_.has_distort_param()) {
distort_datum->CopyFrom(datum);
return;
}
// If datum is encoded, decode and crop the cv::image.
if (datum.encoded()) {
CHECK(!(param_.force_color() && param_.force_gray()))
<< "cannot set both force_color and force_gray";
cv::Mat cv_img;
if (param_.force_color() || param_.force_gray()) {
// If force_color then decode in color otherwise decode in gray.
cv_img = DecodeDatumToCVMat(datum, param_.force_color());
} else {
cv_img = DecodeDatumToCVMatNative(datum);
}
// Distort the image.
cv::Mat distort_img = ApplyDistort(cv_img, param_.distort_param());
// Save the image into datum.
EncodeCVMatToDatum(distort_img, "jpg", distort_datum);
distort_datum->set_label(datum.label());
return;
} else {
LOG(ERROR) << "Only support encoded datum now";
}
}
因为要用opencv来做distort,所以先要将数据转成cv::Mat格式,然后调用ApplyDistort来具体实现。
cv::Mat ApplyDistort(const cv::Mat& in_img, const DistortionParameter& param) {
cv::Mat out_img = in_img;
float prob;
caffe_rng_uniform(1, 0.f, 1.f, &prob);
if (prob > 0.5) {
// Do random brightness distortion.调整亮度
RandomBrightness(out_img, &out_img, param.brightness_prob(),
param.brightness_delta());
// Do random contrast distortion.调整对比度
RandomContrast(out_img, &out_img, param.contrast_prob(),
param.contrast_lower(), param.contrast_upper());
// Do random saturation distortion. 调整饱和度
/*饱和度是指色彩的鲜艳程度,也称色彩的纯度.饱和度取决于訪色中含色成分和消色成分(灰色)的比例*/
RandomSaturation(out_img, &out_img, param.saturation_prob(),
param.saturation_lower(), param.saturation_upper());
// Do random hue distortion.调整色度,类似上面调整饱和度
RandomHue(out_img, &out_img, param.hue_prob(), param.hue_delta());
// Do random reordering of the channels.随机排列图像通道,这个一般不会去用啦
RandomOrderChannels(out_img, &out_img, param.random_order_prob());
} else {
...
}
return out_img;
}
亮度和对比度调整的理论基础:
#调整亮度
void RandomBrightness(const cv::Mat& in_img, cv::Mat* out_img,
const float brightness_prob, const float brightness_delta) {
float prob;
caffe_rng_uniform(1, 0.f, 1.f, &prob); //产生1个在0,1之间均匀分布的随机数
if (prob < brightness_prob) {
CHECK_GE(brightness_delta, 0) << "brightness_delta must be non-negative.";
float delta;
//例如,brightness_delta为32,则产生一个在[-32,32]范围内均匀分布的随机数
caffe_rng_uniform(1, -brightness_delta, brightness_delta, &delta);
AdjustBrightness(in_img, delta, out_img);
} else {
*out_img = in_img;
}
}
void AdjustBrightness(const cv::Mat& in_img, const float delta,
cv::Mat* out_img) {
if (fabs(delta) > 0) {
/*
在使用Opencv中,常常会出现读取一个图片内容后要把图片内容的像素信息转为浮点并把当前的mat作为矩形进行矩阵计算,
那么这里就有一个类型转换问你,在新的opencv中mat有一个函数可以用于类型的相互转换。
void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;
m – 目标矩阵。如果m在运算前没有合适的尺寸或类型,将被重新分配。
rtype – 目标矩阵的类型。因为目标矩阵的通道数与源矩阵一样,所以rtype也可以看做是目标矩阵的位深度。如果rtype为负值,目标矩阵和源矩阵将使用同样的类型。
alpha – 尺度变换因子(可选)。
beta – 附加到尺度变换后的值上的偏移量(可选)
*/
in_img.convertTo(*out_img, -1, 1, delta);
} else { //等于0啥都不用干嘛
*out_img = in_img;
}
}
#调整对比度
void RandomContrast(const cv::Mat& in_img, cv::Mat* out_img,
const float contrast_prob, const float lower, const float upper) {
float prob;
caffe_rng_uniform(1, 0.f, 1.f, &prob);
if (prob < contrast_prob) {
CHECK_GE(upper, lower) << "contrast upper must be >= lower.";
CHECK_GE(lower, 0) << "contrast lower must be non-negative.";
float delta;
caffe_rng_uniform(1, lower, upper, &delta);
AdjustContrast(in_img, delta, out_img);
} else {
*out_img = in_img;
}
}
void AdjustContrast(const cv::Mat& in_img, const float delta,
cv::Mat* out_img) {
if (fabs(delta - 1.f) > 1e-3) {
in_img.convertTo(*out_img, -1, delta, 0); //delta就是参数alpha,尺度变换因子
} else {
*out_img = in_img;
}
}
补充:关于opencv convertTo函数说明.
#调整饱和度
void RandomSaturation(const cv::Mat& in_img, cv::Mat* out_img,
const float saturation_prob, const float lower, const float upper) {
float prob;
caffe_rng_uniform(1, 0.f, 1.f, &prob);
if (prob < saturation_prob) {
CHECK_GE(upper, lower) << "saturation upper must be >= lower.";
CHECK_GE(lower, 0) << "saturation lower must be non-negative.";
float delta;
caffe_rng_uniform(1, lower, upper, &delta);
AdjustSaturation(in_img, delta, out_img);
} else {
*out_img = in_img;
}
}
void AdjustSaturation(const cv::Mat& in_img, const float delta,
cv::Mat* out_img) {
if (fabs(delta - 1.f) != 1e-3) {
// Convert to HSV colorspae.
// RGB: 三原色
// HSV:色度、饱和度、亮度
// YUV:亮度、色度
cv::cvtColor(in_img, *out_img, CV_BGR2HSV);
// Split the image to 3 channels.
vector channels;
cv::split(*out_img, channels);
// Adjust the saturation.
channels[1].convertTo(channels[1], -1, delta, 0);
cv::merge(channels, *out_img);
// Back to BGR colorspace.
cvtColor(*out_img, *out_img, CV_HSV2BGR);
} else {
*out_img = in_img;
}
}
#调整色度
void RandomHue(const cv::Mat& in_img, cv::Mat* out_img,
const float hue_prob, const float hue_delta) {
float prob;
caffe_rng_uniform(1, 0.f, 1.f, &prob);
if (prob < hue_prob) {
CHECK_GE(hue_delta, 0) << "hue_delta must be non-negative.";
float delta;
caffe_rng_uniform(1, -hue_delta, hue_delta, &delta);
AdjustHue(in_img, delta, out_img);
} else {
*out_img = in_img;
}
}
void AdjustHue(const cv::Mat& in_img, const float delta, cv::Mat* out_img) {
if (fabs(delta) > 0) {
// Convert to HSV colorspae.
cv::cvtColor(in_img, *out_img, CV_BGR2HSV);
// Split the image to 3 channels.
vector channels;
cv::split(*out_img, channels);
// Adjust the hue.
//和AdjustSaturation的区别就是这里用的channels[0],因为0表示色度
channels[0].convertTo(channels[0], -1, 1, delta);
cv::merge(channels, *out_img);
// Back to BGR colorspace.
cvtColor(*out_img, *out_img, CV_HSV2BGR);
} else {
*out_img = in_img;
}
}