blob: 6868180b0bc41800b454efe4fc1e0369acc417a8 [file] [log] [blame]
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +00001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5
6#include "DetectionPostProcess.hpp"
7
8#include <armnn/ArmNN.hpp>
9
10#include <boost/numeric/conversion/cast.hpp>
11
12#include <algorithm>
13#include <numeric>
14
15namespace
16{
17
18std::vector<unsigned int> GenerateRangeK(unsigned int k)
19{
20 std::vector<unsigned int> range(k);
21 std::iota(range.begin(), range.end(), 0);
22 return range;
23}
24
25void TopKSort(unsigned int k, unsigned int* indices, const float* values, unsigned int numElement)
26{
27 std::partial_sort(indices, indices + k, indices + numElement,
28 [&values](unsigned int i, unsigned int j) { return values[i] > values[j]; });
29}
30
31float IntersectionOverUnion(const float* boxI, const float* boxJ)
32{
33 // Box-corner format: ymin, xmin, ymax, xmax.
34 const int yMin = 0;
35 const int xMin = 1;
36 const int yMax = 2;
37 const int xMax = 3;
38 float areaI = (boxI[yMax] - boxI[yMin]) * (boxI[xMax] - boxI[xMin]);
39 float areaJ = (boxJ[yMax] - boxJ[yMin]) * (boxJ[xMax] - boxJ[xMin]);
40 float yMinIntersection = std::max(boxI[yMin], boxJ[yMin]);
41 float xMinIntersection = std::max(boxI[xMin], boxJ[xMin]);
42 float yMaxIntersection = std::min(boxI[yMax], boxJ[yMax]);
43 float xMaxIntersection = std::min(boxI[xMax], boxJ[xMax]);
44 float areaIntersection = std::max(yMaxIntersection - yMinIntersection, 0.0f) *
45 std::max(xMaxIntersection - xMinIntersection, 0.0f);
46 float areaUnion = areaI + areaJ - areaIntersection;
47 return areaIntersection / areaUnion;
48}
49
50std::vector<unsigned int> NonMaxSuppression(unsigned int numBoxes, const std::vector<float>& boxCorners,
51 const std::vector<float>& scores, float nmsScoreThreshold,
52 unsigned int maxDetection, float nmsIouThreshold)
53{
54 // Select boxes that have scores above a given threshold.
55 std::vector<float> scoresAboveThreshold;
56 std::vector<unsigned int> indicesAboveThreshold;
57 for (unsigned int i = 0; i < numBoxes; ++i)
58 {
59 if (scores[i] >= nmsScoreThreshold)
60 {
61 scoresAboveThreshold.push_back(scores[i]);
62 indicesAboveThreshold.push_back(i);
63 }
64 }
65
66 // Sort the indices based on scores.
67 unsigned int numAboveThreshold = boost::numeric_cast<unsigned int>(scoresAboveThreshold.size());
68 std::vector<unsigned int> sortedIndices = GenerateRangeK(numAboveThreshold);
69 TopKSort(numAboveThreshold,sortedIndices.data(), scoresAboveThreshold.data(), numAboveThreshold);
70
71 // Number of output cannot be more than max detections specified in the option.
72 unsigned int numOutput = std::min(maxDetection, numAboveThreshold);
73 std::vector<unsigned int> outputIndices;
74 std::vector<bool> visited(numAboveThreshold, false);
75
76 // Prune out the boxes with high intersection over union by keeping the box with higher score.
77 for (unsigned int i = 0; i < numAboveThreshold; ++i)
78 {
79 if (outputIndices.size() >= numOutput)
80 {
81 break;
82 }
83 if (!visited[sortedIndices[i]])
84 {
85 outputIndices.push_back(indicesAboveThreshold[sortedIndices[i]]);
86 }
87 for (unsigned int j = i + 1; j < numAboveThreshold; ++j)
88 {
89 unsigned int iIndex = indicesAboveThreshold[sortedIndices[i]] * 4;
90 unsigned int jIndex = indicesAboveThreshold[sortedIndices[j]] * 4;
91 if (IntersectionOverUnion(&boxCorners[iIndex], &boxCorners[jIndex]) > nmsIouThreshold)
92 {
93 visited[sortedIndices[j]] = true;
94 }
95 }
96 }
97 return outputIndices;
98}
99
100void AllocateOutputData(unsigned int numOutput, unsigned int numSelected, const std::vector<float>& boxCorners,
101 const std::vector<unsigned int>& outputIndices, const std::vector<unsigned int>& selectedBoxes,
102 const std::vector<unsigned int>& selectedClasses, const std::vector<float>& selectedScores,
103 float* detectionBoxes, float* detectionScores, float* detectionClasses, float* numDetections)
104{
105 for (unsigned int i = 0; i < numOutput; ++i)
106 {
107 unsigned int boxIndex = i * 4;
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000108 if (i < numSelected)
109 {
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000110 unsigned int boxCornorIndex = selectedBoxes[outputIndices[i]] * 4;
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000111 detectionScores[i] = selectedScores[outputIndices[i]];
112 detectionClasses[i] = boost::numeric_cast<float>(selectedClasses[outputIndices[i]]);
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000113 detectionBoxes[boxIndex] = boxCorners[boxCornorIndex];
114 detectionBoxes[boxIndex + 1] = boxCorners[boxCornorIndex + 1];
115 detectionBoxes[boxIndex + 2] = boxCorners[boxCornorIndex + 2];
116 detectionBoxes[boxIndex + 3] = boxCorners[boxCornorIndex + 3];
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000117 }
118 else
119 {
120 detectionScores[i] = 0.0f;
121 detectionClasses[i] = 0.0f;
122 detectionBoxes[boxIndex] = 0.0f;
123 detectionBoxes[boxIndex + 1] = 0.0f;
124 detectionBoxes[boxIndex + 2] = 0.0f;
125 detectionBoxes[boxIndex + 3] = 0.0f;
126 }
127 }
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000128 numDetections[0] = boost::numeric_cast<float>(numSelected);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000129}
130
131} // anonymous namespace
132
133namespace armnn
134{
135
136void DetectionPostProcess(const TensorInfo& boxEncodingsInfo,
137 const TensorInfo& scoresInfo,
138 const TensorInfo& anchorsInfo,
139 const TensorInfo& detectionBoxesInfo,
140 const TensorInfo& detectionClassesInfo,
141 const TensorInfo& detectionScoresInfo,
142 const TensorInfo& numDetectionsInfo,
143 const DetectionPostProcessDescriptor& desc,
144 const float* boxEncodings,
145 const float* scores,
146 const float* anchors,
147 float* detectionBoxes,
148 float* detectionClasses,
149 float* detectionScores,
150 float* numDetections)
151{
152 // Transform center-size format which is (ycenter, xcenter, height, width) to box-corner format,
153 // which represents the lower left corner and the upper right corner (ymin, xmin, ymax, xmax)
154 std::vector<float> boxCorners(boxEncodingsInfo.GetNumElements());
155 unsigned int numBoxes = boxEncodingsInfo.GetShape()[1];
156 for (unsigned int i = 0; i < numBoxes; ++i)
157 {
158 unsigned int indexY = i * 4;
159 unsigned int indexX = indexY + 1;
160 unsigned int indexH = indexX + 1;
161 unsigned int indexW = indexH + 1;
162 float yCentre = boxEncodings[indexY] / desc.m_ScaleY * anchors[indexH] + anchors[indexY];
163 float xCentre = boxEncodings[indexX] / desc.m_ScaleX * anchors[indexW] + anchors[indexX];
164 float halfH = 0.5f * expf(boxEncodings[indexH] / desc.m_ScaleH) * anchors[indexH];
165 float halfW = 0.5f * expf(boxEncodings[indexW] / desc.m_ScaleW) * anchors[indexW];
166 // ymin
167 boxCorners[indexY] = yCentre - halfH;
168 // xmin
169 boxCorners[indexX] = xCentre - halfW;
170 // ymax
171 boxCorners[indexH] = yCentre + halfH;
172 // xmax
173 boxCorners[indexW] = xCentre + halfW;
174
175 BOOST_ASSERT(boxCorners[indexY] < boxCorners[indexH]);
176 BOOST_ASSERT(boxCorners[indexX] < boxCorners[indexW]);
177 }
178
179 unsigned int numClassesWithBg = desc.m_NumClasses + 1;
180
181 // Perform Non Max Suppression.
182 if (desc.m_UseRegularNms)
183 {
184 // Perform Regular NMS.
185 // For each class, perform NMS and select max detection numbers of the highest score across all classes.
186 std::vector<float> classScores(numBoxes);
187 std::vector<unsigned int>selectedBoxesAfterNms;
188 std::vector<float> selectedScoresAfterNms;
189 std::vector<unsigned int> selectedClasses;
190
191 for (unsigned int c = 0; c < desc.m_NumClasses; ++c)
192 {
193 // For each boxes, get scores of the boxes for the class c.
194 for (unsigned int i = 0; i < numBoxes; ++i)
195 {
196 classScores[i] = scores[i * numClassesWithBg + c + 1];
197 }
198 std::vector<unsigned int> selectedIndices = NonMaxSuppression(numBoxes, boxCorners, classScores,
199 desc.m_NmsScoreThreshold,
Narumol Prangnawarat4628d052019-02-25 17:26:05 +0000200 desc.m_DetectionsPerClass,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000201 desc.m_NmsIouThreshold);
202
203 for (unsigned int i = 0; i < selectedIndices.size(); ++i)
204 {
205 selectedBoxesAfterNms.push_back(selectedIndices[i]);
206 selectedScoresAfterNms.push_back(classScores[selectedIndices[i]]);
207 selectedClasses.push_back(c);
208 }
209 }
210
211 // Select max detection numbers of the highest score across all classes
212 unsigned int numSelected = boost::numeric_cast<unsigned int>(selectedBoxesAfterNms.size());
213 unsigned int numOutput = std::min(desc.m_MaxDetections, numSelected);
214
215 // Sort the max scores among the selected indices.
216 std::vector<unsigned int> outputIndices = GenerateRangeK(numSelected);
217 TopKSort(numOutput, outputIndices.data(), selectedScoresAfterNms.data(), numSelected);
218
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000219 AllocateOutputData(detectionBoxesInfo.GetShape()[1], numOutput, boxCorners, outputIndices,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000220 selectedBoxesAfterNms, selectedClasses, selectedScoresAfterNms,
221 detectionBoxes, detectionScores, detectionClasses, numDetections);
222 }
223 else
224 {
225 // Perform Fast NMS.
226 // Select max scores of boxes and perform NMS on max scores,
227 // select max detection numbers of the highest score
228 unsigned int numClassesPerBox = std::min(desc.m_MaxClassesPerDetection, desc.m_NumClasses);
229 std::vector<float> maxScores;
230 std::vector<unsigned int>boxIndices;
231 std::vector<unsigned int>maxScoreClasses;
232
233 for (unsigned int box = 0; box < numBoxes; ++box)
234 {
235 unsigned int scoreIndex = box * numClassesWithBg + 1;
236
237 // Get the max scores of the box.
238 std::vector<unsigned int> maxScoreIndices = GenerateRangeK(desc.m_NumClasses);
239 TopKSort(numClassesPerBox, maxScoreIndices.data(), scores + scoreIndex, desc.m_NumClasses);
240
241 for (unsigned int i = 0; i < numClassesPerBox; ++i)
242 {
243 maxScores.push_back(scores[scoreIndex + maxScoreIndices[i]]);
244 maxScoreClasses.push_back(maxScoreIndices[i]);
245 boxIndices.push_back(box);
246 }
247 }
248
249 // Perform NMS on max scores
250 std::vector<unsigned int> selectedIndices = NonMaxSuppression(numBoxes, boxCorners, maxScores,
251 desc.m_NmsScoreThreshold,
252 desc.m_MaxDetections,
253 desc.m_NmsIouThreshold);
254
255 unsigned int numSelected = boost::numeric_cast<unsigned int>(selectedIndices.size());
256 unsigned int numOutput = std::min(desc.m_MaxDetections, numSelected);
257
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000258 AllocateOutputData(detectionBoxesInfo.GetShape()[1], numOutput, boxCorners, selectedIndices,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000259 boxIndices, maxScoreClasses, maxScores,
260 detectionBoxes, detectionScores, detectionClasses, numDetections);
261 }
262}
263
264} // namespace armnn