blob: f80f20a44121d6f9ef2e14995470734fdd8d0730 [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
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01008#include <armnn/utility/Assert.hpp>
Matthew Sloyan171214c2020-09-09 09:07:37 +01009#include <armnn/utility/NumericCast.hpp>
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000010
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000011#include <algorithm>
12#include <numeric>
13
Aron Virginas-Tar6331f912019-06-03 17:10:02 +010014namespace armnn
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000015{
16
17std::vector<unsigned int> GenerateRangeK(unsigned int k)
18{
19 std::vector<unsigned int> range(k);
20 std::iota(range.begin(), range.end(), 0);
21 return range;
22}
23
24void TopKSort(unsigned int k, unsigned int* indices, const float* values, unsigned int numElement)
25{
26 std::partial_sort(indices, indices + k, indices + numElement,
27 [&values](unsigned int i, unsigned int j) { return values[i] > values[j]; });
28}
29
30float IntersectionOverUnion(const float* boxI, const float* boxJ)
31{
32 // Box-corner format: ymin, xmin, ymax, xmax.
33 const int yMin = 0;
34 const int xMin = 1;
35 const int yMax = 2;
36 const int xMax = 3;
37 float areaI = (boxI[yMax] - boxI[yMin]) * (boxI[xMax] - boxI[xMin]);
38 float areaJ = (boxJ[yMax] - boxJ[yMin]) * (boxJ[xMax] - boxJ[xMin]);
39 float yMinIntersection = std::max(boxI[yMin], boxJ[yMin]);
40 float xMinIntersection = std::max(boxI[xMin], boxJ[xMin]);
41 float yMaxIntersection = std::min(boxI[yMax], boxJ[yMax]);
42 float xMaxIntersection = std::min(boxI[xMax], boxJ[xMax]);
43 float areaIntersection = std::max(yMaxIntersection - yMinIntersection, 0.0f) *
44 std::max(xMaxIntersection - xMinIntersection, 0.0f);
45 float areaUnion = areaI + areaJ - areaIntersection;
46 return areaIntersection / areaUnion;
47}
48
Aron Virginas-Tar6331f912019-06-03 17:10:02 +010049std::vector<unsigned int> NonMaxSuppression(unsigned int numBoxes,
50 const std::vector<float>& boxCorners,
51 const std::vector<float>& scores,
52 float nmsScoreThreshold,
53 unsigned int maxDetection,
54 float nmsIouThreshold)
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000055{
56 // Select boxes that have scores above a given threshold.
57 std::vector<float> scoresAboveThreshold;
58 std::vector<unsigned int> indicesAboveThreshold;
59 for (unsigned int i = 0; i < numBoxes; ++i)
60 {
61 if (scores[i] >= nmsScoreThreshold)
62 {
63 scoresAboveThreshold.push_back(scores[i]);
64 indicesAboveThreshold.push_back(i);
65 }
66 }
67
68 // Sort the indices based on scores.
Matthew Sloyan171214c2020-09-09 09:07:37 +010069 unsigned int numAboveThreshold = armnn::numeric_cast<unsigned int>(scoresAboveThreshold.size());
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000070 std::vector<unsigned int> sortedIndices = GenerateRangeK(numAboveThreshold);
Aron Virginas-Tar6331f912019-06-03 17:10:02 +010071 TopKSort(numAboveThreshold, sortedIndices.data(), scoresAboveThreshold.data(), numAboveThreshold);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000072
73 // Number of output cannot be more than max detections specified in the option.
74 unsigned int numOutput = std::min(maxDetection, numAboveThreshold);
75 std::vector<unsigned int> outputIndices;
76 std::vector<bool> visited(numAboveThreshold, false);
77
78 // Prune out the boxes with high intersection over union by keeping the box with higher score.
79 for (unsigned int i = 0; i < numAboveThreshold; ++i)
80 {
81 if (outputIndices.size() >= numOutput)
82 {
83 break;
84 }
85 if (!visited[sortedIndices[i]])
86 {
87 outputIndices.push_back(indicesAboveThreshold[sortedIndices[i]]);
88 }
89 for (unsigned int j = i + 1; j < numAboveThreshold; ++j)
90 {
91 unsigned int iIndex = indicesAboveThreshold[sortedIndices[i]] * 4;
92 unsigned int jIndex = indicesAboveThreshold[sortedIndices[j]] * 4;
93 if (IntersectionOverUnion(&boxCorners[iIndex], &boxCorners[jIndex]) > nmsIouThreshold)
94 {
95 visited[sortedIndices[j]] = true;
96 }
97 }
98 }
99 return outputIndices;
100}
101
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100102void AllocateOutputData(unsigned int numOutput,
103 unsigned int numSelected,
104 const std::vector<float>& boxCorners,
105 const std::vector<unsigned int>& outputIndices,
106 const std::vector<unsigned int>& selectedBoxes,
107 const std::vector<unsigned int>& selectedClasses,
108 const std::vector<float>& selectedScores,
109 float* detectionBoxes,
110 float* detectionScores,
111 float* detectionClasses,
112 float* numDetections)
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000113{
114 for (unsigned int i = 0; i < numOutput; ++i)
115 {
116 unsigned int boxIndex = i * 4;
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000117 if (i < numSelected)
118 {
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000119 unsigned int boxCornorIndex = selectedBoxes[outputIndices[i]] * 4;
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000120 detectionScores[i] = selectedScores[outputIndices[i]];
Matthew Sloyan24ac8592020-09-23 16:57:23 +0100121 detectionClasses[i] = armnn::numeric_cast<float>(selectedClasses[outputIndices[i]]);
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000122 detectionBoxes[boxIndex] = boxCorners[boxCornorIndex];
123 detectionBoxes[boxIndex + 1] = boxCorners[boxCornorIndex + 1];
124 detectionBoxes[boxIndex + 2] = boxCorners[boxCornorIndex + 2];
125 detectionBoxes[boxIndex + 3] = boxCorners[boxCornorIndex + 3];
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000126 }
127 else
128 {
129 detectionScores[i] = 0.0f;
130 detectionClasses[i] = 0.0f;
131 detectionBoxes[boxIndex] = 0.0f;
132 detectionBoxes[boxIndex + 1] = 0.0f;
133 detectionBoxes[boxIndex + 2] = 0.0f;
134 detectionBoxes[boxIndex + 3] = 0.0f;
135 }
136 }
Matthew Sloyan24ac8592020-09-23 16:57:23 +0100137 numDetections[0] = armnn::numeric_cast<float>(numSelected);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000138}
139
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000140void DetectionPostProcess(const TensorInfo& boxEncodingsInfo,
141 const TensorInfo& scoresInfo,
142 const TensorInfo& anchorsInfo,
143 const TensorInfo& detectionBoxesInfo,
144 const TensorInfo& detectionClassesInfo,
145 const TensorInfo& detectionScoresInfo,
146 const TensorInfo& numDetectionsInfo,
147 const DetectionPostProcessDescriptor& desc,
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100148 Decoder<float>& boxEncodings,
149 Decoder<float>& scores,
150 Decoder<float>& anchors,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000151 float* detectionBoxes,
152 float* detectionClasses,
153 float* detectionScores,
154 float* numDetections)
155{
Jan Eilers8eb25602020-03-09 12:13:48 +0000156 IgnoreUnused(anchorsInfo, detectionClassesInfo, detectionScoresInfo, numDetectionsInfo);
Derek Lamberti901ea112019-12-10 22:07:09 +0000157
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000158 // Transform center-size format which is (ycenter, xcenter, height, width) to box-corner format,
159 // which represents the lower left corner and the upper right corner (ymin, xmin, ymax, xmax)
160 std::vector<float> boxCorners(boxEncodingsInfo.GetNumElements());
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100161
162 const unsigned int numBoxes = boxEncodingsInfo.GetShape()[1];
163 const unsigned int numScores = scoresInfo.GetNumElements();
164
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000165 for (unsigned int i = 0; i < numBoxes; ++i)
166 {
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100167 // Y
168 float boxEncodingY = boxEncodings.Get();
169 float anchorY = anchors.Get();
170
171 ++boxEncodings;
172 ++anchors;
173
174 // X
175 float boxEncodingX = boxEncodings.Get();
176 float anchorX = anchors.Get();
177
178 ++boxEncodings;
179 ++anchors;
180
181 // H
182 float boxEncodingH = boxEncodings.Get();
183 float anchorH = anchors.Get();
184
185 ++boxEncodings;
186 ++anchors;
187
188 // W
189 float boxEncodingW = boxEncodings.Get();
190 float anchorW = anchors.Get();
191
192 ++boxEncodings;
193 ++anchors;
194
195 float yCentre = boxEncodingY / desc.m_ScaleY * anchorH + anchorY;
196 float xCentre = boxEncodingX / desc.m_ScaleX * anchorW + anchorX;
197
198 float halfH = 0.5f * expf(boxEncodingH / desc.m_ScaleH) * anchorH;
199 float halfW = 0.5f * expf(boxEncodingW / desc.m_ScaleW) * anchorW;
200
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000201 unsigned int indexY = i * 4;
202 unsigned int indexX = indexY + 1;
203 unsigned int indexH = indexX + 1;
204 unsigned int indexW = indexH + 1;
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100205
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000206 // ymin
207 boxCorners[indexY] = yCentre - halfH;
208 // xmin
209 boxCorners[indexX] = xCentre - halfW;
210 // ymax
211 boxCorners[indexH] = yCentre + halfH;
212 // xmax
213 boxCorners[indexW] = xCentre + halfW;
214
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100215 ARMNN_ASSERT(boxCorners[indexY] < boxCorners[indexH]);
216 ARMNN_ASSERT(boxCorners[indexX] < boxCorners[indexW]);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000217 }
218
219 unsigned int numClassesWithBg = desc.m_NumClasses + 1;
220
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100221 // Decode scores
222 std::vector<float> decodedScores;
223 decodedScores.reserve(numScores);
224
225 for (unsigned int i = 0u; i < numScores; ++i)
226 {
227 decodedScores.emplace_back(scores.Get());
228 ++scores;
229 }
230
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000231 // Perform Non Max Suppression.
232 if (desc.m_UseRegularNms)
233 {
234 // Perform Regular NMS.
235 // For each class, perform NMS and select max detection numbers of the highest score across all classes.
236 std::vector<float> classScores(numBoxes);
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100237
238 std::vector<unsigned int> selectedBoxesAfterNms;
239 selectedBoxesAfterNms.reserve(numBoxes);
240
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000241 std::vector<float> selectedScoresAfterNms;
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100242 selectedBoxesAfterNms.reserve(numScores);
243
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000244 std::vector<unsigned int> selectedClasses;
245
246 for (unsigned int c = 0; c < desc.m_NumClasses; ++c)
247 {
248 // For each boxes, get scores of the boxes for the class c.
249 for (unsigned int i = 0; i < numBoxes; ++i)
250 {
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100251 classScores[i] = decodedScores[i * numClassesWithBg + c + 1];
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000252 }
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100253 std::vector<unsigned int> selectedIndices = NonMaxSuppression(numBoxes,
254 boxCorners,
255 classScores,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000256 desc.m_NmsScoreThreshold,
Narumol Prangnawarat4628d052019-02-25 17:26:05 +0000257 desc.m_DetectionsPerClass,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000258 desc.m_NmsIouThreshold);
259
260 for (unsigned int i = 0; i < selectedIndices.size(); ++i)
261 {
262 selectedBoxesAfterNms.push_back(selectedIndices[i]);
263 selectedScoresAfterNms.push_back(classScores[selectedIndices[i]]);
264 selectedClasses.push_back(c);
265 }
266 }
267
268 // Select max detection numbers of the highest score across all classes
Matthew Sloyan171214c2020-09-09 09:07:37 +0100269 unsigned int numSelected = armnn::numeric_cast<unsigned int>(selectedBoxesAfterNms.size());
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000270 unsigned int numOutput = std::min(desc.m_MaxDetections, numSelected);
271
272 // Sort the max scores among the selected indices.
273 std::vector<unsigned int> outputIndices = GenerateRangeK(numSelected);
274 TopKSort(numOutput, outputIndices.data(), selectedScoresAfterNms.data(), numSelected);
275
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000276 AllocateOutputData(detectionBoxesInfo.GetShape()[1], numOutput, boxCorners, outputIndices,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000277 selectedBoxesAfterNms, selectedClasses, selectedScoresAfterNms,
278 detectionBoxes, detectionScores, detectionClasses, numDetections);
279 }
280 else
281 {
282 // Perform Fast NMS.
283 // Select max scores of boxes and perform NMS on max scores,
284 // select max detection numbers of the highest score
285 unsigned int numClassesPerBox = std::min(desc.m_MaxClassesPerDetection, desc.m_NumClasses);
286 std::vector<float> maxScores;
287 std::vector<unsigned int>boxIndices;
288 std::vector<unsigned int>maxScoreClasses;
289
290 for (unsigned int box = 0; box < numBoxes; ++box)
291 {
292 unsigned int scoreIndex = box * numClassesWithBg + 1;
293
294 // Get the max scores of the box.
295 std::vector<unsigned int> maxScoreIndices = GenerateRangeK(desc.m_NumClasses);
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100296 TopKSort(numClassesPerBox, maxScoreIndices.data(),
297 decodedScores.data() + scoreIndex, desc.m_NumClasses);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000298
299 for (unsigned int i = 0; i < numClassesPerBox; ++i)
300 {
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100301 maxScores.push_back(decodedScores[scoreIndex + maxScoreIndices[i]]);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000302 maxScoreClasses.push_back(maxScoreIndices[i]);
303 boxIndices.push_back(box);
304 }
305 }
306
307 // Perform NMS on max scores
308 std::vector<unsigned int> selectedIndices = NonMaxSuppression(numBoxes, boxCorners, maxScores,
309 desc.m_NmsScoreThreshold,
310 desc.m_MaxDetections,
311 desc.m_NmsIouThreshold);
312
Matthew Sloyan171214c2020-09-09 09:07:37 +0100313 unsigned int numSelected = armnn::numeric_cast<unsigned int>(selectedIndices.size());
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000314 unsigned int numOutput = std::min(desc.m_MaxDetections, numSelected);
315
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000316 AllocateOutputData(detectionBoxesInfo.GetShape()[1], numOutput, boxCorners, selectedIndices,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000317 boxIndices, maxScoreClasses, maxScores,
318 detectionBoxes, detectionScores, detectionClasses, numDetections);
319 }
320}
321
322} // namespace armnn