blob: ce07110da977ca4ad086090c1a03134ec38836b1 [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
11#include <boost/numeric/conversion/cast.hpp>
12
13#include <algorithm>
14#include <numeric>
15
Aron Virginas-Tar6331f912019-06-03 17:10:02 +010016namespace armnn
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000017{
18
19std::vector<unsigned int> GenerateRangeK(unsigned int k)
20{
21 std::vector<unsigned int> range(k);
22 std::iota(range.begin(), range.end(), 0);
23 return range;
24}
25
26void TopKSort(unsigned int k, unsigned int* indices, const float* values, unsigned int numElement)
27{
28 std::partial_sort(indices, indices + k, indices + numElement,
29 [&values](unsigned int i, unsigned int j) { return values[i] > values[j]; });
30}
31
32float IntersectionOverUnion(const float* boxI, const float* boxJ)
33{
34 // Box-corner format: ymin, xmin, ymax, xmax.
35 const int yMin = 0;
36 const int xMin = 1;
37 const int yMax = 2;
38 const int xMax = 3;
39 float areaI = (boxI[yMax] - boxI[yMin]) * (boxI[xMax] - boxI[xMin]);
40 float areaJ = (boxJ[yMax] - boxJ[yMin]) * (boxJ[xMax] - boxJ[xMin]);
41 float yMinIntersection = std::max(boxI[yMin], boxJ[yMin]);
42 float xMinIntersection = std::max(boxI[xMin], boxJ[xMin]);
43 float yMaxIntersection = std::min(boxI[yMax], boxJ[yMax]);
44 float xMaxIntersection = std::min(boxI[xMax], boxJ[xMax]);
45 float areaIntersection = std::max(yMaxIntersection - yMinIntersection, 0.0f) *
46 std::max(xMaxIntersection - xMinIntersection, 0.0f);
47 float areaUnion = areaI + areaJ - areaIntersection;
48 return areaIntersection / areaUnion;
49}
50
Aron Virginas-Tar6331f912019-06-03 17:10:02 +010051std::vector<unsigned int> NonMaxSuppression(unsigned int numBoxes,
52 const std::vector<float>& boxCorners,
53 const std::vector<float>& scores,
54 float nmsScoreThreshold,
55 unsigned int maxDetection,
56 float nmsIouThreshold)
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000057{
58 // Select boxes that have scores above a given threshold.
59 std::vector<float> scoresAboveThreshold;
60 std::vector<unsigned int> indicesAboveThreshold;
61 for (unsigned int i = 0; i < numBoxes; ++i)
62 {
63 if (scores[i] >= nmsScoreThreshold)
64 {
65 scoresAboveThreshold.push_back(scores[i]);
66 indicesAboveThreshold.push_back(i);
67 }
68 }
69
70 // Sort the indices based on scores.
Matthew Sloyan171214c2020-09-09 09:07:37 +010071 unsigned int numAboveThreshold = armnn::numeric_cast<unsigned int>(scoresAboveThreshold.size());
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000072 std::vector<unsigned int> sortedIndices = GenerateRangeK(numAboveThreshold);
Aron Virginas-Tar6331f912019-06-03 17:10:02 +010073 TopKSort(numAboveThreshold, sortedIndices.data(), scoresAboveThreshold.data(), numAboveThreshold);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000074
75 // Number of output cannot be more than max detections specified in the option.
76 unsigned int numOutput = std::min(maxDetection, numAboveThreshold);
77 std::vector<unsigned int> outputIndices;
78 std::vector<bool> visited(numAboveThreshold, false);
79
80 // Prune out the boxes with high intersection over union by keeping the box with higher score.
81 for (unsigned int i = 0; i < numAboveThreshold; ++i)
82 {
83 if (outputIndices.size() >= numOutput)
84 {
85 break;
86 }
87 if (!visited[sortedIndices[i]])
88 {
89 outputIndices.push_back(indicesAboveThreshold[sortedIndices[i]]);
90 }
91 for (unsigned int j = i + 1; j < numAboveThreshold; ++j)
92 {
93 unsigned int iIndex = indicesAboveThreshold[sortedIndices[i]] * 4;
94 unsigned int jIndex = indicesAboveThreshold[sortedIndices[j]] * 4;
95 if (IntersectionOverUnion(&boxCorners[iIndex], &boxCorners[jIndex]) > nmsIouThreshold)
96 {
97 visited[sortedIndices[j]] = true;
98 }
99 }
100 }
101 return outputIndices;
102}
103
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100104void AllocateOutputData(unsigned int numOutput,
105 unsigned int numSelected,
106 const std::vector<float>& boxCorners,
107 const std::vector<unsigned int>& outputIndices,
108 const std::vector<unsigned int>& selectedBoxes,
109 const std::vector<unsigned int>& selectedClasses,
110 const std::vector<float>& selectedScores,
111 float* detectionBoxes,
112 float* detectionScores,
113 float* detectionClasses,
114 float* numDetections)
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000115{
116 for (unsigned int i = 0; i < numOutput; ++i)
117 {
118 unsigned int boxIndex = i * 4;
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000119 if (i < numSelected)
120 {
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000121 unsigned int boxCornorIndex = selectedBoxes[outputIndices[i]] * 4;
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000122 detectionScores[i] = selectedScores[outputIndices[i]];
123 detectionClasses[i] = boost::numeric_cast<float>(selectedClasses[outputIndices[i]]);
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000124 detectionBoxes[boxIndex] = boxCorners[boxCornorIndex];
125 detectionBoxes[boxIndex + 1] = boxCorners[boxCornorIndex + 1];
126 detectionBoxes[boxIndex + 2] = boxCorners[boxCornorIndex + 2];
127 detectionBoxes[boxIndex + 3] = boxCorners[boxCornorIndex + 3];
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000128 }
129 else
130 {
131 detectionScores[i] = 0.0f;
132 detectionClasses[i] = 0.0f;
133 detectionBoxes[boxIndex] = 0.0f;
134 detectionBoxes[boxIndex + 1] = 0.0f;
135 detectionBoxes[boxIndex + 2] = 0.0f;
136 detectionBoxes[boxIndex + 3] = 0.0f;
137 }
138 }
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000139 numDetections[0] = boost::numeric_cast<float>(numSelected);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000140}
141
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000142void DetectionPostProcess(const TensorInfo& boxEncodingsInfo,
143 const TensorInfo& scoresInfo,
144 const TensorInfo& anchorsInfo,
145 const TensorInfo& detectionBoxesInfo,
146 const TensorInfo& detectionClassesInfo,
147 const TensorInfo& detectionScoresInfo,
148 const TensorInfo& numDetectionsInfo,
149 const DetectionPostProcessDescriptor& desc,
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100150 Decoder<float>& boxEncodings,
151 Decoder<float>& scores,
152 Decoder<float>& anchors,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000153 float* detectionBoxes,
154 float* detectionClasses,
155 float* detectionScores,
156 float* numDetections)
157{
Jan Eilers8eb25602020-03-09 12:13:48 +0000158 IgnoreUnused(anchorsInfo, detectionClassesInfo, detectionScoresInfo, numDetectionsInfo);
Derek Lamberti901ea112019-12-10 22:07:09 +0000159
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000160 // Transform center-size format which is (ycenter, xcenter, height, width) to box-corner format,
161 // which represents the lower left corner and the upper right corner (ymin, xmin, ymax, xmax)
162 std::vector<float> boxCorners(boxEncodingsInfo.GetNumElements());
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100163
164 const unsigned int numBoxes = boxEncodingsInfo.GetShape()[1];
165 const unsigned int numScores = scoresInfo.GetNumElements();
166
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000167 for (unsigned int i = 0; i < numBoxes; ++i)
168 {
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100169 // Y
170 float boxEncodingY = boxEncodings.Get();
171 float anchorY = anchors.Get();
172
173 ++boxEncodings;
174 ++anchors;
175
176 // X
177 float boxEncodingX = boxEncodings.Get();
178 float anchorX = anchors.Get();
179
180 ++boxEncodings;
181 ++anchors;
182
183 // H
184 float boxEncodingH = boxEncodings.Get();
185 float anchorH = anchors.Get();
186
187 ++boxEncodings;
188 ++anchors;
189
190 // W
191 float boxEncodingW = boxEncodings.Get();
192 float anchorW = anchors.Get();
193
194 ++boxEncodings;
195 ++anchors;
196
197 float yCentre = boxEncodingY / desc.m_ScaleY * anchorH + anchorY;
198 float xCentre = boxEncodingX / desc.m_ScaleX * anchorW + anchorX;
199
200 float halfH = 0.5f * expf(boxEncodingH / desc.m_ScaleH) * anchorH;
201 float halfW = 0.5f * expf(boxEncodingW / desc.m_ScaleW) * anchorW;
202
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000203 unsigned int indexY = i * 4;
204 unsigned int indexX = indexY + 1;
205 unsigned int indexH = indexX + 1;
206 unsigned int indexW = indexH + 1;
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100207
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000208 // ymin
209 boxCorners[indexY] = yCentre - halfH;
210 // xmin
211 boxCorners[indexX] = xCentre - halfW;
212 // ymax
213 boxCorners[indexH] = yCentre + halfH;
214 // xmax
215 boxCorners[indexW] = xCentre + halfW;
216
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100217 ARMNN_ASSERT(boxCorners[indexY] < boxCorners[indexH]);
218 ARMNN_ASSERT(boxCorners[indexX] < boxCorners[indexW]);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000219 }
220
221 unsigned int numClassesWithBg = desc.m_NumClasses + 1;
222
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100223 // Decode scores
224 std::vector<float> decodedScores;
225 decodedScores.reserve(numScores);
226
227 for (unsigned int i = 0u; i < numScores; ++i)
228 {
229 decodedScores.emplace_back(scores.Get());
230 ++scores;
231 }
232
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000233 // Perform Non Max Suppression.
234 if (desc.m_UseRegularNms)
235 {
236 // Perform Regular NMS.
237 // For each class, perform NMS and select max detection numbers of the highest score across all classes.
238 std::vector<float> classScores(numBoxes);
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100239
240 std::vector<unsigned int> selectedBoxesAfterNms;
241 selectedBoxesAfterNms.reserve(numBoxes);
242
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000243 std::vector<float> selectedScoresAfterNms;
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100244 selectedBoxesAfterNms.reserve(numScores);
245
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000246 std::vector<unsigned int> selectedClasses;
247
248 for (unsigned int c = 0; c < desc.m_NumClasses; ++c)
249 {
250 // For each boxes, get scores of the boxes for the class c.
251 for (unsigned int i = 0; i < numBoxes; ++i)
252 {
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100253 classScores[i] = decodedScores[i * numClassesWithBg + c + 1];
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000254 }
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100255 std::vector<unsigned int> selectedIndices = NonMaxSuppression(numBoxes,
256 boxCorners,
257 classScores,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000258 desc.m_NmsScoreThreshold,
Narumol Prangnawarat4628d052019-02-25 17:26:05 +0000259 desc.m_DetectionsPerClass,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000260 desc.m_NmsIouThreshold);
261
262 for (unsigned int i = 0; i < selectedIndices.size(); ++i)
263 {
264 selectedBoxesAfterNms.push_back(selectedIndices[i]);
265 selectedScoresAfterNms.push_back(classScores[selectedIndices[i]]);
266 selectedClasses.push_back(c);
267 }
268 }
269
270 // Select max detection numbers of the highest score across all classes
Matthew Sloyan171214c2020-09-09 09:07:37 +0100271 unsigned int numSelected = armnn::numeric_cast<unsigned int>(selectedBoxesAfterNms.size());
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000272 unsigned int numOutput = std::min(desc.m_MaxDetections, numSelected);
273
274 // Sort the max scores among the selected indices.
275 std::vector<unsigned int> outputIndices = GenerateRangeK(numSelected);
276 TopKSort(numOutput, outputIndices.data(), selectedScoresAfterNms.data(), numSelected);
277
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000278 AllocateOutputData(detectionBoxesInfo.GetShape()[1], numOutput, boxCorners, outputIndices,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000279 selectedBoxesAfterNms, selectedClasses, selectedScoresAfterNms,
280 detectionBoxes, detectionScores, detectionClasses, numDetections);
281 }
282 else
283 {
284 // Perform Fast NMS.
285 // Select max scores of boxes and perform NMS on max scores,
286 // select max detection numbers of the highest score
287 unsigned int numClassesPerBox = std::min(desc.m_MaxClassesPerDetection, desc.m_NumClasses);
288 std::vector<float> maxScores;
289 std::vector<unsigned int>boxIndices;
290 std::vector<unsigned int>maxScoreClasses;
291
292 for (unsigned int box = 0; box < numBoxes; ++box)
293 {
294 unsigned int scoreIndex = box * numClassesWithBg + 1;
295
296 // Get the max scores of the box.
297 std::vector<unsigned int> maxScoreIndices = GenerateRangeK(desc.m_NumClasses);
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100298 TopKSort(numClassesPerBox, maxScoreIndices.data(),
299 decodedScores.data() + scoreIndex, desc.m_NumClasses);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000300
301 for (unsigned int i = 0; i < numClassesPerBox; ++i)
302 {
Aron Virginas-Tar6331f912019-06-03 17:10:02 +0100303 maxScores.push_back(decodedScores[scoreIndex + maxScoreIndices[i]]);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000304 maxScoreClasses.push_back(maxScoreIndices[i]);
305 boxIndices.push_back(box);
306 }
307 }
308
309 // Perform NMS on max scores
310 std::vector<unsigned int> selectedIndices = NonMaxSuppression(numBoxes, boxCorners, maxScores,
311 desc.m_NmsScoreThreshold,
312 desc.m_MaxDetections,
313 desc.m_NmsIouThreshold);
314
Matthew Sloyan171214c2020-09-09 09:07:37 +0100315 unsigned int numSelected = armnn::numeric_cast<unsigned int>(selectedIndices.size());
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000316 unsigned int numOutput = std::min(desc.m_MaxDetections, numSelected);
317
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000318 AllocateOutputData(detectionBoxesInfo.GetShape()[1], numOutput, boxCorners, selectedIndices,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000319 boxIndices, maxScoreClasses, maxScores,
320 detectionBoxes, detectionScores, detectionClasses, numDetections);
321 }
322}
323
324} // namespace armnn