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<typename Dtype>
void DataTransformer<Dtype>::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<cv::Mat> 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<cv::Mat> 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;
}
}