blob: d3790f22abc307d6d418a57d8e76247fce61686b [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
Aron Virginas-Tard4f0fea2019-04-09 14:08:06 +010010#include <boost/assert.hpp>
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +000011#include <boost/numeric/conversion/cast.hpp>
12
13#include <algorithm>
14#include <numeric>
15
16namespace
17{
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
51std::vector<unsigned int> NonMaxSuppression(unsigned int numBoxes, const std::vector<float>& boxCorners,
52 const std::vector<float>& scores, float nmsScoreThreshold,
53 unsigned int maxDetection, float nmsIouThreshold)
54{
55 // Select boxes that have scores above a given threshold.
56 std::vector<float> scoresAboveThreshold;
57 std::vector<unsigned int> indicesAboveThreshold;
58 for (unsigned int i = 0; i < numBoxes; ++i)
59 {
60 if (scores[i] >= nmsScoreThreshold)
61 {
62 scoresAboveThreshold.push_back(scores[i]);
63 indicesAboveThreshold.push_back(i);
64 }
65 }
66
67 // Sort the indices based on scores.
68 unsigned int numAboveThreshold = boost::numeric_cast<unsigned int>(scoresAboveThreshold.size());
69 std::vector<unsigned int> sortedIndices = GenerateRangeK(numAboveThreshold);
70 TopKSort(numAboveThreshold,sortedIndices.data(), scoresAboveThreshold.data(), numAboveThreshold);
71
72 // Number of output cannot be more than max detections specified in the option.
73 unsigned int numOutput = std::min(maxDetection, numAboveThreshold);
74 std::vector<unsigned int> outputIndices;
75 std::vector<bool> visited(numAboveThreshold, false);
76
77 // Prune out the boxes with high intersection over union by keeping the box with higher score.
78 for (unsigned int i = 0; i < numAboveThreshold; ++i)
79 {
80 if (outputIndices.size() >= numOutput)
81 {
82 break;
83 }
84 if (!visited[sortedIndices[i]])
85 {
86 outputIndices.push_back(indicesAboveThreshold[sortedIndices[i]]);
87 }
88 for (unsigned int j = i + 1; j < numAboveThreshold; ++j)
89 {
90 unsigned int iIndex = indicesAboveThreshold[sortedIndices[i]] * 4;
91 unsigned int jIndex = indicesAboveThreshold[sortedIndices[j]] * 4;
92 if (IntersectionOverUnion(&boxCorners[iIndex], &boxCorners[jIndex]) > nmsIouThreshold)
93 {
94 visited[sortedIndices[j]] = true;
95 }
96 }
97 }
98 return outputIndices;
99}
100
101void AllocateOutputData(unsigned int numOutput, unsigned int numSelected, const std::vector<float>& boxCorners,
102 const std::vector<unsigned int>& outputIndices, const std::vector<unsigned int>& selectedBoxes,
103 const std::vector<unsigned int>& selectedClasses, const std::vector<float>& selectedScores,
104 float* detectionBoxes, float* detectionScores, float* detectionClasses, float* numDetections)
105{
106 for (unsigned int i = 0; i < numOutput; ++i)
107 {
108 unsigned int boxIndex = i * 4;
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000109 if (i < numSelected)
110 {
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000111 unsigned int boxCornorIndex = selectedBoxes[outputIndices[i]] * 4;
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000112 detectionScores[i] = selectedScores[outputIndices[i]];
113 detectionClasses[i] = boost::numeric_cast<float>(selectedClasses[outputIndices[i]]);
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000114 detectionBoxes[boxIndex] = boxCorners[boxCornorIndex];
115 detectionBoxes[boxIndex + 1] = boxCorners[boxCornorIndex + 1];
116 detectionBoxes[boxIndex + 2] = boxCorners[boxCornorIndex + 2];
117 detectionBoxes[boxIndex + 3] = boxCorners[boxCornorIndex + 3];
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000118 }
119 else
120 {
121 detectionScores[i] = 0.0f;
122 detectionClasses[i] = 0.0f;
123 detectionBoxes[boxIndex] = 0.0f;
124 detectionBoxes[boxIndex + 1] = 0.0f;
125 detectionBoxes[boxIndex + 2] = 0.0f;
126 detectionBoxes[boxIndex + 3] = 0.0f;
127 }
128 }
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000129 numDetections[0] = boost::numeric_cast<float>(numSelected);
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000130}
131
132} // anonymous namespace
133
134namespace armnn
135{
136
137void DetectionPostProcess(const TensorInfo& boxEncodingsInfo,
138 const TensorInfo& scoresInfo,
139 const TensorInfo& anchorsInfo,
140 const TensorInfo& detectionBoxesInfo,
141 const TensorInfo& detectionClassesInfo,
142 const TensorInfo& detectionScoresInfo,
143 const TensorInfo& numDetectionsInfo,
144 const DetectionPostProcessDescriptor& desc,
145 const float* boxEncodings,
146 const float* scores,
147 const float* anchors,
148 float* detectionBoxes,
149 float* detectionClasses,
150 float* detectionScores,
151 float* numDetections)
152{
153 // Transform center-size format which is (ycenter, xcenter, height, width) to box-corner format,
154 // which represents the lower left corner and the upper right corner (ymin, xmin, ymax, xmax)
155 std::vector<float> boxCorners(boxEncodingsInfo.GetNumElements());
156 unsigned int numBoxes = boxEncodingsInfo.GetShape()[1];
157 for (unsigned int i = 0; i < numBoxes; ++i)
158 {
159 unsigned int indexY = i * 4;
160 unsigned int indexX = indexY + 1;
161 unsigned int indexH = indexX + 1;
162 unsigned int indexW = indexH + 1;
163 float yCentre = boxEncodings[indexY] / desc.m_ScaleY * anchors[indexH] + anchors[indexY];
164 float xCentre = boxEncodings[indexX] / desc.m_ScaleX * anchors[indexW] + anchors[indexX];
165 float halfH = 0.5f * expf(boxEncodings[indexH] / desc.m_ScaleH) * anchors[indexH];
166 float halfW = 0.5f * expf(boxEncodings[indexW] / desc.m_ScaleW) * anchors[indexW];
167 // ymin
168 boxCorners[indexY] = yCentre - halfH;
169 // xmin
170 boxCorners[indexX] = xCentre - halfW;
171 // ymax
172 boxCorners[indexH] = yCentre + halfH;
173 // xmax
174 boxCorners[indexW] = xCentre + halfW;
175
176 BOOST_ASSERT(boxCorners[indexY] < boxCorners[indexH]);
177 BOOST_ASSERT(boxCorners[indexX] < boxCorners[indexW]);
178 }
179
180 unsigned int numClassesWithBg = desc.m_NumClasses + 1;
181
182 // Perform Non Max Suppression.
183 if (desc.m_UseRegularNms)
184 {
185 // Perform Regular NMS.
186 // For each class, perform NMS and select max detection numbers of the highest score across all classes.
187 std::vector<float> classScores(numBoxes);
188 std::vector<unsigned int>selectedBoxesAfterNms;
189 std::vector<float> selectedScoresAfterNms;
190 std::vector<unsigned int> selectedClasses;
191
192 for (unsigned int c = 0; c < desc.m_NumClasses; ++c)
193 {
194 // For each boxes, get scores of the boxes for the class c.
195 for (unsigned int i = 0; i < numBoxes; ++i)
196 {
197 classScores[i] = scores[i * numClassesWithBg + c + 1];
198 }
199 std::vector<unsigned int> selectedIndices = NonMaxSuppression(numBoxes, boxCorners, classScores,
200 desc.m_NmsScoreThreshold,
Narumol Prangnawarat4628d052019-02-25 17:26:05 +0000201 desc.m_DetectionsPerClass,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000202 desc.m_NmsIouThreshold);
203
204 for (unsigned int i = 0; i < selectedIndices.size(); ++i)
205 {
206 selectedBoxesAfterNms.push_back(selectedIndices[i]);
207 selectedScoresAfterNms.push_back(classScores[selectedIndices[i]]);
208 selectedClasses.push_back(c);
209 }
210 }
211
212 // Select max detection numbers of the highest score across all classes
213 unsigned int numSelected = boost::numeric_cast<unsigned int>(selectedBoxesAfterNms.size());
214 unsigned int numOutput = std::min(desc.m_MaxDetections, numSelected);
215
216 // Sort the max scores among the selected indices.
217 std::vector<unsigned int> outputIndices = GenerateRangeK(numSelected);
218 TopKSort(numOutput, outputIndices.data(), selectedScoresAfterNms.data(), numSelected);
219
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000220 AllocateOutputData(detectionBoxesInfo.GetShape()[1], numOutput, boxCorners, outputIndices,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000221 selectedBoxesAfterNms, selectedClasses, selectedScoresAfterNms,
222 detectionBoxes, detectionScores, detectionClasses, numDetections);
223 }
224 else
225 {
226 // Perform Fast NMS.
227 // Select max scores of boxes and perform NMS on max scores,
228 // select max detection numbers of the highest score
229 unsigned int numClassesPerBox = std::min(desc.m_MaxClassesPerDetection, desc.m_NumClasses);
230 std::vector<float> maxScores;
231 std::vector<unsigned int>boxIndices;
232 std::vector<unsigned int>maxScoreClasses;
233
234 for (unsigned int box = 0; box < numBoxes; ++box)
235 {
236 unsigned int scoreIndex = box * numClassesWithBg + 1;
237
238 // Get the max scores of the box.
239 std::vector<unsigned int> maxScoreIndices = GenerateRangeK(desc.m_NumClasses);
240 TopKSort(numClassesPerBox, maxScoreIndices.data(), scores + scoreIndex, desc.m_NumClasses);
241
242 for (unsigned int i = 0; i < numClassesPerBox; ++i)
243 {
244 maxScores.push_back(scores[scoreIndex + maxScoreIndices[i]]);
245 maxScoreClasses.push_back(maxScoreIndices[i]);
246 boxIndices.push_back(box);
247 }
248 }
249
250 // Perform NMS on max scores
251 std::vector<unsigned int> selectedIndices = NonMaxSuppression(numBoxes, boxCorners, maxScores,
252 desc.m_NmsScoreThreshold,
253 desc.m_MaxDetections,
254 desc.m_NmsIouThreshold);
255
256 unsigned int numSelected = boost::numeric_cast<unsigned int>(selectedIndices.size());
257 unsigned int numOutput = std::min(desc.m_MaxDetections, numSelected);
258
Narumol Prangnawarat6d302bf2019-02-04 11:46:26 +0000259 AllocateOutputData(detectionBoxesInfo.GetShape()[1], numOutput, boxCorners, selectedIndices,
Narumol Prangnawaratbc67cef2019-01-31 15:31:54 +0000260 boxIndices, maxScoreClasses, maxScores,
261 detectionBoxes, detectionScores, detectionClasses, numDetections);
262 }
263}
264
265} // namespace armnn