概述
由于cpu版本速度太慢,真正應用實際環境中僅僅隻能通過cuda或者opencl實作,是以我将僅僅介紹cuda版本
- detection_output_layer層的輸入可以參考Caffe架構下SSD算法源碼綜述。它通過hpp,cpp和cu實作。
- 參看DetectionOutputLayer::Forward_gpu(),前向傳播通過decodeBBoxesGPU函數将預測得到的檢測框進行解碼操作
- 通過PermuteDataGPU函數重新reshape一下類别的預測值,在處理之前,deploy.prototxt可以看出已經将conf當做dtection_output_layer的輸入之前已經做了sofxmax。是以我們不需要在detection_output_layer中進行softmax
- 然後通過上述的兩個結果進行處理:将不同的類别應用極大抑制算法(類别間的極大抑制算法是互相獨立的)
- 最終将處理後的資料放入輸出層中
- 源碼還有存儲結果操作,不是必要項,是以不解析
源碼解析
- 和正常的layer層一樣,detection_output_layer函數主要Forward和Backward組成,但沒有實作Backward。
前向傳播使用到的函數有:
- DecodeBBoxesGPU函數
- PermuteDataGPU函數
- ApplyNMSFast函數
Forward_gpu
template <typename Dtype>
void DetectionOutputLayer<Dtype>::Forward_gpu(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
// loc_data 為輸入的資料,這裡表示預測得到的位置
const Dtype* loc_data = bottom[0]->gpu_data();
// prior_data 為輸入的資料,這裡表示loc_data對應的原圖的大小
const Dtype* prior_data = bottom[2]->gpu_data();
// num 為樣本數量
const int num = bottom[0]->num();
// caffe 一般通過mutable表示将會改變資料的指針,否則僅僅進行讀操作
Dtype* bbox_data = bbox_preds_.mutable_gpu_data();
// loc_count 為所有樣本預測結果綜合
const int loc_count = bbox_preds_.count();
// 是否将預測得到的框大小限定在原圖之内(預測得到的資料可能會産生)
const bool clip_bbox = false;
/*
* code_type_類型預設為 CENTER_SIZE
* variance_encoded_in_target_ 預設為false,表示不使用variance帶入到位置預測的結果計算結果
* num_priors_ 表示所有候選框的數目
* share_location 預設為true,表示位置預測預設将所有類的位置歸為一種類别進行位置預測
* num_loc_classes share_location ? 1 : num_classes
* background_label_id 背景标簽的id
* clip_bbox: 是否将位置預測值限定在0到1中,圖檔大小内
*/
DecodeBBoxesGPU<Dtype>(loc_count, loc_data, prior_data, code_type_,
variance_encoded_in_target_, num_priors_, share_location_,
num_loc_classes_, background_label_id_, clip_bbox, bbox_data);
// Retrieve all decoded location predictions.
const Dtype* bbox_cpu_data;
if (!share_location_) {
Dtype* bbox_permute_data = bbox_permute_.mutable_gpu_data();
PermuteDataGPU<Dtype>(loc_count, bbox_data, num_loc_classes_, num_priors_,
4, bbox_permute_data);
bbox_cpu_data = bbox_permute_.cpu_data();
} else {
bbox_cpu_data = bbox_preds_.cpu_data();
}
// Retrieve all confidences.
Dtype* conf_permute_data = conf_permute_.mutable_gpu_data();
// bottom[1] 為conf分類資料結果, num_classes_為類别數量,num_priors為單個樣本的所有先驗框數量
// 将 conf 資料由 num_batch d c num_dim 轉換成 num_batch c d num_dim
PermuteDataGPU<Dtype>(bottom[1]->count(), bottom[1]->gpu_data(),
num_classes_, num_priors_, 1, conf_permute_data);
const Dtype* conf_cpu_data = conf_permute_.cpu_data();
int num_kept = 0;
vector<map<int, vector<int> > > all_indices;
// i 傳輸的num索引, 最終
for (int i = 0; i < num; ++i) {
map<int, vector<int> > indices;
int num_det = 0;
// 目前所處的num所在的類别起始索引
const int conf_idx = i * num_classes_ * num_priors_;
int bbox_idx;
if (share_location_) {
// bbox_idx為目前所處的num其實位置索引
bbox_idx = i * num_priors_ * 4;
} else {
bbox_idx = conf_idx * 4;
}
// conf n 通道為num_batch,第二個通道是 classes,是以一層num循環後,緊接着進行classes循環
// 而indices記憶體儲的是經過nms篩選過後的樣本,其中第一個索引為對應的類别,第二個索引對應的值為相應類别下的結果位置
for (int c = 0; c < num_classes_; ++c) {
// 不處理背景
if (c == background_label_id_) {
// Ignore background class.
continue;
}
/* 擷取目前num以及目前類别所在的cur_conf的起始索引指針
* 其中conf_idx已經存儲到了num是以為了找到類别起始索引
* 僅僅需要加上c * num_proirs
*/
const Dtype* cur_conf_data = conf_cpu_data + conf_idx + c * num_priors_;
// 擷取目前num的位置起始位置
const Dtype* cur_bbox_data = bbox_cpu_data + bbox_idx;
// share_location為true,跳過
if (!share_location_) {
cur_bbox_data += c * num_priors_ * 4;
}
/*
* 應用非極大抑制算法
* confidence_threashold為門檻值設定,
* nms_threashold為設定的門檻值,
* era_
* top_k_表示儲存的最大數量
*/
ApplyNMSFast(cur_bbox_data, cur_conf_data, num_priors_,
confidence_threshold_, nms_threshold_, eta_, top_k_, &(indices[c]));
// 加上所有類别預測
num_det += indices[c].size();
}
// 如果結果大于top_k_,則需要對各個類總體的運用一次nms算法
if (keep_top_k_ > -1 && num_det > keep_top_k_) {
vector<pair<float, pair<int, int> > > score_index_pairs;
for (map<int, vector<int> >::iterator it = indices.begin();
it != indices.end(); ++it) {
int label = it->first;
const vector<int>& label_indices = it->second;
for (int j = 0; j < label_indices.size(); ++j) {
int idx = label_indices[j];
float score = conf_cpu_data[conf_idx + label * num_priors_ + idx];
score_index_pairs.push_back(std::make_pair(
score, std::make_pair(label, idx)));
}
}
// Keep top k results per image.
std::sort(score_index_pairs.begin(), score_index_pairs.end(),
SortScorePairDescend<pair<int, int> >);
score_index_pairs.resize(keep_top_k_);
// Store the new indices.
map<int, vector<int> > new_indices;
for (int j = 0; j < score_index_pairs.size(); ++j) {
int label = score_index_pairs[j].second.first;
int idx = score_index_pairs[j].second.second;
new_indices[label].push_back(idx);
}
all_indices.push_back(new_indices);
num_kept += keep_top_k_;
} else {
all_indices.push_back(indices);
num_kept += num_det;
}
}
// 下面比較容易了,将資料存儲到top中
vector<int> top_shape(2, 1);
// 輸出層的shape第一次元為預測的類别結果數量
top_shape.push_back(num_kept);
// top第二個次元為7
top_shape.push_back(7);
Dtype* top_data;
if (num_kept == 0) {
LOG(INFO) << "Couldn't find any detections";
top_shape[2] = num;
top[0]->Reshape(top_shape);
top_data = top[0]->mutable_cpu_data();
caffe_set<Dtype>(top[0]->count(), -1, top_data);
// Generate fake results per image.
for (int i = 0; i < num; ++i) {
top_data[0] = i;
top_data += 7;
}
} else {
top[0]->Reshape(top_shape);
top_data = top[0]->mutable_cpu_data();
}
int count = 0;
for (int i = 0; i < num; ++i) {
const int conf_idx = i * num_classes_ * num_priors_;
int bbox_idx;
if (share_location_) {
bbox_idx = i * num_priors_ * 4;
} else {
bbox_idx = conf_idx * 4;
}
for (map<int, vector<int> >::iterator it = all_indices[i].begin();
it != all_indices[i].end(); ++it) {
int label = it->first;
vector<int>& indices = it->second;
const Dtype* cur_conf_data =
conf_cpu_data + conf_idx + label * num_priors_;
const Dtype* cur_bbox_data = bbox_cpu_data + bbox_idx;
if (!share_location_) {
cur_bbox_data += label * num_priors_ * 4;
}
for (int j = 0; j < indices.size(); ++j) {
/*
* top第第二次元大小為7
* 0 為所處的在的num
* 1 為分類标簽
* 2 為分類置信度
* 3 , 4, 5, 6 分别為坐标xmin,ymin,xmax,ymax
*/
int idx = indices[j];
top_data[count * 7] = i;
top_data[count * 7 + 1] = label;
top_data[count * 7 + 2] = cur_conf_data[idx];
for (int k = 0; k < 4; ++k) {
top_data[count * 7 + 3 + k] = cur_bbox_data[idx * 4 + k];
}
++count;
}
}
}
}
DecodeBBoxesGPU實作:
DecodeBBoxesGpu使用到了DecodeBBoxesKernel核函數
template <typename Dtype>
void DecodeBBoxesGPU(const int nthreads,
const Dtype* loc_data, const Dtype* prior_data,
const CodeType code_type, const bool variance_encoded_in_target,
const int num_priors, const bool share_location,
const int num_loc_classes, const int background_label_id,
const bool clip_bbox, Dtype* bbox_data) {
/*
* 通過預測的位置結果以及先驗框計算預測結果
* 并儲存到bbox_data中
*/
// NOLINT_NEXT_LINE(whitespace/operators)
DecodeBBoxesKernel<Dtype><<<CAFFE_GET_BLOCKS(nthreads),
CAFFE_CUDA_NUM_THREADS>>>(nthreads, loc_data, prior_data, code_type,
variance_encoded_in_target, num_priors, share_location, num_loc_classes,
background_label_id, clip_bbox, bbox_data);
CUDA_POST_KERNEL_CHECK;
}
- DecoeBBoxesKernel核函數實作:
template <typename Dtype>
__global__ void DecodeBBoxesKernel(const int nthreads,
const Dtype* loc_data, const Dtype* prior_data,
const CodeType code_type, const bool variance_encoded_in_target,
const int num_priors, const bool share_location,
const int num_loc_classes, const int background_label_id,
const bool clip_bbox, Dtype* bbox_data) {
CUDA_KERNEL_LOOP(index, nthreads) {
// i 存儲的為除4的約數,表示是預測值x, y, center_x, center_y的哪一個
const int i = index % 4;
// 将num_loc_classes當做1對待
// c表示處在那個4的倍數中
const int c = (index / 4) % num_loc_classes;
// d表示處在對應樣本中的第幾個位置
const int d = (index / 4 / num_loc_classes) % num_priors;
if (!share_location && c == background_label_id) {
// Ignore background class if not share_location.
return;
}
/*
* pi 表示對應prior中先驗框位置的索引
* vi 表示對應prior中先驗框位置所對應的variance索引
* 需要說明的是nthreads的大小是随着檢測樣本數變化的,而proir先驗框的大小是不變的
*/
const int pi = d * 4;
const int vi = pi + num_priors * 4;
// 為了簡化源碼分析,預設僅僅分析CENTER,是以不考慮CORNER
if (code_type == PriorBoxParameter_CodeType_CORNER) {
if (variance_encoded_in_target) {
// variance is encoded in target, we simply need to add the offset
// predictions.
bbox_data[index] = prior_data[pi + i] + loc_data[index];
} else {
// variance is encoded in bbox, we need to scale the offset accordingly.
bbox_data[index] =
prior_data[pi + i] + loc_data[index] * prior_data[vi + i];
}
} else if (code_type == PriorBoxParameter_CodeType_CENTER_SIZE) {
// p_xmin, p_ymin. p_xmax,p_ymax表示預測結果對應的先驗框的結果
const Dtype p_xmin = prior_data[pi];
const Dtype p_ymin = prior_data[pi + 1];
const Dtype p_xmax = prior_data[pi + 2];
const Dtype p_ymax = prior_data[pi + 3];
// 通過對應與原圖坐标點,算出先驗框的長寬以及中心點坐标
const Dtype prior_width = p_xmax - p_xmin;
const Dtype prior_height = p_ymax - p_ymin;
const Dtype prior_center_x = (p_xmin + p_xmax) / 2.;
const Dtype prior_center_y = (p_ymin + p_ymax) / 2.;
// 擷取預測
const Dtype xmin = loc_data[index - i];
const Dtype ymin = loc_data[index - i + 1];
const Dtype xmax = loc_data[index - i + 2];
const Dtype ymax = loc_data[index - i + 3];
Dtype decode_bbox_center_x, decode_bbox_center_y;
Dtype decode_bbox_width, decode_bbox_height;
if (variance_encoded_in_target) {
// variance is encoded in target, we simply need to retore the offset
// predictions.
decode_bbox_center_x = xmin * prior_width + prior_center_x;
decode_bbox_center_y = ymin * prior_height + prior_center_y;
decode_bbox_width = exp(xmax) * prior_width;
decode_bbox_height = exp(ymax) * prior_height;
} else {
// variance is encoded in bbox, we need to scale the offset accordingly.
// 擷取編碼點
decode_bbox_center_x =
prior_data[vi] * xmin * prior_width + prior_center_x;
decode_bbox_center_y =
prior_data[vi + 1] * ymin * prior_height + prior_center_y;
decode_bbox_width =
exp(prior_data[vi + 2] * xmax) * prior_width;
decode_bbox_height =
exp(prior_data[vi + 3] * ymax) * prior_height;
}
// 将結果轉換到對應的存儲結果中的,
switch (i) {
// 結果的最小橫坐标
case 0:
bbox_data[index] = decode_bbox_center_x - decode_bbox_width / 2.;
break;
// 結果的最小縱坐标
case 1:
bbox_data[index] = decode_bbox_center_y - decode_bbox_height / 2.;
break;
// 結果的最大橫坐标
case 2:
bbox_data[index] = decode_bbox_center_x + decode_bbox_width / 2.;
break;
// 結果的最大縱坐标
case 3:
bbox_data[index] = decode_bbox_center_y + decode_bbox_height / 2.;
break;
}
// CORNER_SIZE暫時不考慮
} else if (code_type == PriorBoxParameter_CodeType_CORNER_SIZE) {
const Dtype p_xmin = prior_data[pi];
const Dtype p_ymin = prior_data[pi + 1];
const Dtype p_xmax = prior_data[pi + 2];
const Dtype p_ymax = prior_data[pi + 3];
const Dtype prior_width = p_xmax - p_xmin;
const Dtype prior_height = p_ymax - p_ymin;
Dtype p_size;
if (i == 0 || i == 2) {
p_size = prior_width;
} else {
p_size = prior_height;
}
if (variance_encoded_in_target) {
// variance is encoded in target, we simply need to add the offset
// predictions.
bbox_data[index] = prior_data[pi + i] + loc_data[index] * p_size;
} else {
// variance is encoded in bbox, we need to scale the offset accordingly.
bbox_data[index] =
prior_data[pi + i] + loc_data[index] * prior_data[vi + i] * p_size;
}
} else {
// Unknown code type.
}
// 将預測得到的bbox_data值限定在0,1之間
if (clip_bbox) {
bbox_data[index] = max(min(bbox_data[index], Dtype(1.)), Dtype(0.));
}
}
}
PermuteDataGPU實作
DecodeBBoxesGpu使用到了PermuteDataKernel核函數
// num_classes_, num_priors_, 1
template <typename Dtype>
void PermuteDataGPU(const int nthreads,
const Dtype* data, const int num_classes, const int num_data,
const int num_dim, Dtype* new_data) {
// NOLINT_NEXT_LINE(whitespace/operators)
PermuteDataKernel<Dtype><<<CAFFE_GET_BLOCKS(nthreads),
CAFFE_CUDA_NUM_THREADS>>>(nthreads, data, num_classes, num_data,
num_dim, new_data);
CUDA_POST_KERNEL_CHECK;
}
PermuteDataKernel實作
// 将輸出通道從num_batch d classes num_dim 轉換成 num_batch classes d num_dim
template <typename Dtype>
__global__ void PermuteDataKernel(const int nthreads,
const Dtype* data, const int num_classes, const int num_data,
const int num_dim, Dtype* new_data) {
CUDA_KERNEL_LOOP(index, nthreads) {
// i 表示處在樣本的次元
const int i = index % num_dim;
// c 表示類别
const int c = (index / num_dim) % num_classes;
// d 表示所處的位置
const int d = (index / num_dim / num_classes) % num_data;
// n 表示處在第幾個batch裡面
const int n = index / num_dim / num_classes / num_data;
const int new_index = ((n * num_classes + c) * num_data + d) * num_dim + i;
new_data[new_index] = data[index];
}
}
ApplyNMSFast實作
GetMaxScoreIndex使用到了GetMaxScoreIndex函數
template <typename Dtype>
void ApplyNMSFast(const Dtype* bboxes, const Dtype* scores, const int num,
const float score_threshold, const float nms_threshold,
const float eta, const int top_k, vector<int>* indices) {
// Get top_k scores (with corresponding indices).
// std::pair 存儲的是成對變量第一個索引為模闆類定義的變量為存儲的分類得分,而第二個值對int型變量
vector<pair<Dtype, int> > score_index_vec;
// score_index_vec 内部存儲的是分數以及位置,以分數的降序排序,大小小于或者等于top_k_
GetMaxScoreIndex(scores, num, score_threshold, top_k, &score_index_vec);
// Do nms.
float adaptive_threshold = nms_threshold;
indices->clear();
while (score_index_vec.size() != 0) {
const int idx = score_index_vec.front().second;
bool keep = true;
// 與indices所存儲的資料進行比對,如果公共比例小于adaptive_threshold門檻值,
// 則将keep設定為true,否則為false
for (int k = 0; k < indices->size(); ++k) {
if (keep) {
const int kept_idx = (*indices)[k];
float overlap = JaccardOverlap(bboxes + idx * 4, bboxes + kept_idx * 4);
keep = overlap <= adaptive_threshold;
} else {
break;
}
}
// 将其添加進indices中
if (keep) {
indices->push_back(idx);
}
// 删除score_index_vec中的第一個資料
score_index_vec.erase(score_index_vec.begin());
if (keep && eta < 1 && adaptive_threshold > 0.5) {
adaptive_threshold *= eta;
}
}
}
GetMaxScoreIndex實作
template <typename Dtype>
void GetMaxScoreIndex(const Dtype* scores, const int num, const float threshold,
const int top_k, vector<pair<Dtype, int> >* score_index_vec) {
// Generate index score pairs.
// 将得分與所處的piror位置做成比對變量存儲到score_index_vec中
for (int i = 0; i < num; ++i) {
if (scores[i] > threshold) {
score_index_vec->push_back(std::make_pair(scores[i], i));
}
}
// Sort the score pair according to the scores in descending order
// 對score_index_vec的first進行降序排序, std::sort用法可以自己百度一下
std::sort(score_index_vec->begin(), score_index_vec->end(),
SortScorePairDescend<int>);
// Keep top_k scores if needed.
// 如何score_index_vec大小大于top_k,通過resize可以設定其大小
if (top_k > -1 && top_k < score_index_vec->size()) {
score_index_vec->resize(top_k);
}
}
後記
竟然使用了一下午的時間注釋源碼。