Skip to content

Commit 7330f29

Browse files
committed
ipa: rpi: Add support for the Sony IMX500 camera sensor
Add a Sony IMX500 camera helper to the IPA. This also includes support for the on-chip CNN hardware accelerator and parsing of the neural network data stream returned in the metadata buffer. Add tuning files for both VC4 and PiSP platforms. Signed-off-by: Naushir Patuck <[email protected]>
1 parent 549f80a commit 7330f29

24 files changed

+12784
-0
lines changed

Diff for: src/ipa/rpi/cam_helper/cam_helper_imx500.cpp

+341
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
/* SPDX-License-Identifier: BSD-2-Clause */
2+
/*
3+
* Copyright (C) 2024, Raspberry Pi Ltd
4+
*
5+
* cam_helper_imx500.cpp - camera helper for imx500 sensor
6+
*/
7+
8+
#include <algorithm>
9+
#include <assert.h>
10+
#include <cmath>
11+
#include <fstream>
12+
#include <memory>
13+
#include <stddef.h>
14+
#include <stdio.h>
15+
#include <stdlib.h>
16+
#include <string.h>
17+
18+
#include <libcamera/base/log.h>
19+
#include <libcamera/base/span.h>
20+
21+
#include <libcamera/control_ids.h>
22+
23+
#include "imx500_tensor_parser/imx500_tensor_parser.h"
24+
25+
#include "cam_helper.h"
26+
#include "md_parser.h"
27+
28+
using namespace RPiController;
29+
using namespace libcamera;
30+
using libcamera::utils::Duration;
31+
32+
namespace libcamera {
33+
LOG_DECLARE_CATEGORY(IPARPI)
34+
}
35+
36+
/*
37+
* We care about two gain registers and a pair of exposure registers. Their
38+
* I2C addresses from the Sony IMX500 datasheet:
39+
*/
40+
constexpr uint32_t expHiReg = 0x0202;
41+
constexpr uint32_t expLoReg = 0x0203;
42+
constexpr uint32_t gainHiReg = 0x0204;
43+
constexpr uint32_t gainLoReg = 0x0205;
44+
constexpr uint32_t frameLengthHiReg = 0x0340;
45+
constexpr uint32_t frameLengthLoReg = 0x0341;
46+
constexpr uint32_t lineLengthHiReg = 0x0342;
47+
constexpr uint32_t lineLengthLoReg = 0x0343;
48+
constexpr uint32_t temperatureReg = 0x013a;
49+
constexpr std::initializer_list<uint32_t> registerList = { expHiReg, expLoReg, gainHiReg, gainLoReg, frameLengthHiReg, frameLengthLoReg,
50+
lineLengthHiReg, lineLengthLoReg, temperatureReg };
51+
52+
class CamHelperImx500 : public CamHelper
53+
{
54+
public:
55+
CamHelperImx500();
56+
uint32_t gainCode(double gain) const override;
57+
double gain(uint32_t gainCode) const override;
58+
void prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata) override;
59+
std::pair<uint32_t, uint32_t> getBlanking(Duration &exposure, Duration minFrameDuration,
60+
Duration maxFrameDuration) const override;
61+
void getDelays(int &exposureDelay, int &gainDelay,
62+
int &vblankDelay, int &hblankDelay) const override;
63+
bool sensorEmbeddedDataPresent() const override;
64+
65+
private:
66+
/*
67+
* Smallest difference between the frame length and integration time,
68+
* in units of lines.
69+
*/
70+
static constexpr int frameIntegrationDiff = 22;
71+
/* Maximum frame length allowable for long exposure calculations. */
72+
static constexpr int frameLengthMax = 0xffdc;
73+
/* Largest long exposure scale factor given as a left shift on the frame length. */
74+
static constexpr int longExposureShiftMax = 7;
75+
76+
void parseInferenceData(libcamera::Span<const uint8_t> buffer, Metadata &metadata);
77+
void populateMetadata(const MdParser::RegisterMap &registers,
78+
Metadata &metadata) const override;
79+
80+
std::unique_ptr<uint8_t[]> savedInputTensor_;
81+
};
82+
83+
CamHelperImx500::CamHelperImx500()
84+
: CamHelper(std::make_unique<MdParserSmia>(registerList), frameIntegrationDiff)
85+
{
86+
}
87+
88+
uint32_t CamHelperImx500::gainCode(double gain) const
89+
{
90+
return static_cast<uint32_t>(1024 - 1024 / gain);
91+
}
92+
93+
double CamHelperImx500::gain(uint32_t gainCode) const
94+
{
95+
return 1024.0 / (1024 - gainCode);
96+
}
97+
98+
void CamHelperImx500::prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata)
99+
{
100+
MdParser::RegisterMap registers;
101+
DeviceStatus deviceStatus;
102+
103+
if (metadata.get("device.status", deviceStatus)) {
104+
LOG(IPARPI, Error) << "DeviceStatus not found from DelayedControls";
105+
return;
106+
}
107+
108+
parseEmbeddedData(buffer, metadata);
109+
110+
/*
111+
* The DeviceStatus struct is first populated with values obtained from
112+
* DelayedControls. If this reports frame length is > frameLengthMax,
113+
* it means we are using a long exposure mode. Since the long exposure
114+
* scale factor is not returned back through embedded data, we must rely
115+
* on the existing exposure lines and frame length values returned by
116+
* DelayedControls.
117+
*
118+
* Otherwise, all values are updated with what is reported in the
119+
* embedded data.
120+
*/
121+
if (deviceStatus.frameLength > frameLengthMax) {
122+
DeviceStatus parsedDeviceStatus;
123+
124+
metadata.get("device.status", parsedDeviceStatus);
125+
parsedDeviceStatus.shutterSpeed = deviceStatus.shutterSpeed;
126+
parsedDeviceStatus.frameLength = deviceStatus.frameLength;
127+
metadata.set("device.status", parsedDeviceStatus);
128+
129+
LOG(IPARPI, Debug) << "Metadata updated for long exposure: "
130+
<< parsedDeviceStatus;
131+
}
132+
133+
parseInferenceData(buffer, metadata);
134+
}
135+
136+
std::pair<uint32_t, uint32_t> CamHelperImx500::getBlanking(Duration &exposure,
137+
Duration minFrameDuration,
138+
Duration maxFrameDuration) const
139+
{
140+
uint32_t frameLength, exposureLines;
141+
unsigned int shift = 0;
142+
143+
auto [vblank, hblank] = CamHelper::getBlanking(exposure, minFrameDuration,
144+
maxFrameDuration);
145+
146+
frameLength = mode_.height + vblank;
147+
Duration lineLength = hblankToLineLength(hblank);
148+
149+
/*
150+
* Check if the frame length calculated needs to be setup for long
151+
* exposure mode. This will require us to use a long exposure scale
152+
* factor provided by a shift operation in the sensor.
153+
*/
154+
while (frameLength > frameLengthMax) {
155+
if (++shift > longExposureShiftMax) {
156+
shift = longExposureShiftMax;
157+
frameLength = frameLengthMax;
158+
break;
159+
}
160+
frameLength >>= 1;
161+
}
162+
163+
if (shift) {
164+
/* Account for any rounding in the scaled frame length value. */
165+
frameLength <<= shift;
166+
exposureLines = CamHelperImx500::exposureLines(exposure, lineLength);
167+
exposureLines = std::min(exposureLines, frameLength - frameIntegrationDiff);
168+
exposure = CamHelperImx500::exposure(exposureLines, lineLength);
169+
}
170+
171+
return { frameLength - mode_.height, hblank };
172+
}
173+
174+
void CamHelperImx500::getDelays(int &exposureDelay, int &gainDelay,
175+
int &vblankDelay, int &hblankDelay) const
176+
{
177+
exposureDelay = 2;
178+
gainDelay = 2;
179+
vblankDelay = 3;
180+
hblankDelay = 3;
181+
}
182+
183+
bool CamHelperImx500::sensorEmbeddedDataPresent() const
184+
{
185+
return true;
186+
}
187+
188+
void CamHelperImx500::parseInferenceData(libcamera::Span<const uint8_t> buffer,
189+
Metadata &metadata)
190+
{
191+
/* Inference data comes after 2 lines of embedded data. */
192+
constexpr unsigned int StartLine = 2;
193+
size_t bytesPerLine = (mode_.width * mode_.bitdepth) >> 3;
194+
if (hwConfig_.cfeDataBufferStrided)
195+
bytesPerLine = (bytesPerLine + 15) & ~15;
196+
197+
if (buffer.size() <= StartLine * bytesPerLine)
198+
return;
199+
200+
/* Check if an input tensor is needed - this is sticky! */
201+
bool enableInputTensor = false;
202+
metadata.get("cnn.enable_input_tensor", enableInputTensor);
203+
204+
/* Cache the DNN metadata for fast parsing. */
205+
unsigned int tensorBufferSize = buffer.size() - (StartLine * bytesPerLine);
206+
std::unique_ptr<uint8_t[]> cache = std::make_unique<uint8_t[]>(tensorBufferSize);
207+
memcpy(cache.get(), buffer.data() + StartLine * bytesPerLine, tensorBufferSize);
208+
Span<const uint8_t> tensors(cache.get(), tensorBufferSize);
209+
210+
std::unordered_map<TensorType, IMX500Tensors> offsets = RPiController::imx500SplitTensors(tensors);
211+
auto itIn = offsets.find(TensorType::InputTensor);
212+
auto itOut = offsets.find(TensorType::OutputTensor);
213+
214+
if (itIn != offsets.end() && itOut != offsets.end()) {
215+
const unsigned int inputTensorOffset = itIn->second.offset;
216+
const unsigned int outputTensorOffset = itOut->second.offset;
217+
const unsigned int inputTensorSize = outputTensorOffset - inputTensorOffset;
218+
Span<const uint8_t> inputTensor;
219+
220+
if (itIn->second.valid) {
221+
if (itOut->second.valid) {
222+
/* Valid input and output tensor, get the span directly from the current cache. */
223+
inputTensor = Span<const uint8_t>(cache.get() + inputTensorOffset,
224+
inputTensorSize);
225+
} else {
226+
/*
227+
* Invalid output tensor with valid input tensor.
228+
* This is likely because the DNN takes longer than
229+
* a frame time to generate the output tensor.
230+
*
231+
* In such cases, we don't process the input tensor,
232+
* but simply save it for when the next output
233+
* tensor is valid. This way, we ensure that both
234+
* valid input and output tensors are in lock-step.
235+
*/
236+
savedInputTensor_ = std::make_unique<uint8_t[]>(inputTensorSize);
237+
memcpy(savedInputTensor_.get(), cache.get() + inputTensorOffset,
238+
inputTensorSize);
239+
}
240+
} else if (itOut->second.valid && savedInputTensor_) {
241+
/*
242+
* Invalid input tensor with valid output tensor. This is
243+
* likely because the DNN takes longer than a frame time
244+
* to generate the output tensor.
245+
*
246+
* In such cases, use the previously saved input tensor
247+
* if possible.
248+
*/
249+
inputTensor = Span<const uint8_t>(savedInputTensor_.get(), inputTensorSize);
250+
}
251+
252+
if (inputTensor.size()) {
253+
IMX500InputTensorInfo inputTensorInfo;
254+
if (!imx500ParseInputTensor(inputTensorInfo, inputTensor)) {
255+
CnnInputTensorInfo exported{};
256+
exported.width = inputTensorInfo.width;
257+
exported.height = inputTensorInfo.height;
258+
exported.numChannels = inputTensorInfo.channels;
259+
strncpy(exported.networkName, inputTensorInfo.networkName.c_str(),
260+
sizeof(exported.networkName));
261+
exported.networkName[sizeof(exported.networkName) - 1] = '\0';
262+
metadata.set("cnn.input_tensor_info", exported);
263+
metadata.set("cnn.input_tensor", std::move(inputTensorInfo.data));
264+
metadata.set("cnn.input_tensor_size", inputTensorInfo.size);
265+
}
266+
267+
/* We can now safely clear the saved input tensor. */
268+
savedInputTensor_.reset();
269+
}
270+
}
271+
272+
if (itOut != offsets.end() && itOut->second.valid) {
273+
unsigned int outputTensorOffset = itOut->second.offset;
274+
Span<const uint8_t> outputTensor(cache.get() + outputTensorOffset,
275+
tensorBufferSize - outputTensorOffset);
276+
277+
IMX500OutputTensorInfo outputTensorInfo;
278+
if (!imx500ParseOutputTensor(outputTensorInfo, outputTensor)) {
279+
CnnOutputTensorInfo exported{};
280+
if (outputTensorInfo.numTensors < MaxNumTensors) {
281+
exported.numTensors = outputTensorInfo.numTensors;
282+
for (unsigned int i = 0; i < exported.numTensors; i++) {
283+
exported.info[i].tensorDataNum = outputTensorInfo.tensorDataNum[i];
284+
exported.info[i].numDimensions = outputTensorInfo.numDimensions[i];
285+
for (unsigned int j = 0; j < exported.info[i].numDimensions; j++)
286+
exported.info[i].size[j] = outputTensorInfo.vecDim[i][j].size;
287+
}
288+
} else {
289+
LOG(IPARPI, Debug)
290+
<< "IMX500 output tensor info export failed, numTensors > MaxNumTensors";
291+
}
292+
strncpy(exported.networkName, outputTensorInfo.networkName.c_str(),
293+
sizeof(exported.networkName));
294+
exported.networkName[sizeof(exported.networkName) - 1] = '\0';
295+
metadata.set("cnn.output_tensor_info", exported);
296+
metadata.set("cnn.output_tensor", std::move(outputTensorInfo.data));
297+
metadata.set("cnn.output_tensor_size", outputTensorInfo.totalSize);
298+
299+
auto itKpi = offsets.find(TensorType::Kpi);
300+
if (itKpi != offsets.end()) {
301+
constexpr unsigned int DnnRuntimeOffset = 9;
302+
constexpr unsigned int DspRuntimeOffset = 10;
303+
CnnKpiInfo kpi;
304+
305+
uint8_t *k = cache.get() + itKpi->second.offset;
306+
kpi.dnnRuntime = k[4 * DnnRuntimeOffset + 3] << 24 |
307+
k[4 * DnnRuntimeOffset + 2] << 16 |
308+
k[4 * DnnRuntimeOffset + 1] << 8 |
309+
k[4 * DnnRuntimeOffset];
310+
kpi.dspRuntime = k[4 * DspRuntimeOffset + 3] << 24 |
311+
k[4 * DspRuntimeOffset + 2] << 16 |
312+
k[4 * DspRuntimeOffset + 1] << 8 |
313+
k[4 * DspRuntimeOffset];
314+
metadata.set("cnn.kpi_info", kpi);
315+
}
316+
}
317+
}
318+
}
319+
320+
void CamHelperImx500::populateMetadata(const MdParser::RegisterMap &registers,
321+
Metadata &metadata) const
322+
{
323+
DeviceStatus deviceStatus;
324+
325+
deviceStatus.lineLength = lineLengthPckToDuration(registers.at(lineLengthHiReg) * 256 +
326+
registers.at(lineLengthLoReg));
327+
deviceStatus.shutterSpeed = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg),
328+
deviceStatus.lineLength);
329+
deviceStatus.analogueGain = gain(registers.at(gainHiReg) * 256 + registers.at(gainLoReg));
330+
deviceStatus.frameLength = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg);
331+
deviceStatus.sensorTemperature = std::clamp<int8_t>(registers.at(temperatureReg), -20, 80);
332+
333+
metadata.set("device.status", deviceStatus);
334+
}
335+
336+
static CamHelper *create()
337+
{
338+
return new CamHelperImx500();
339+
}
340+
341+
static RegisterCamHelper reg_imx500("imx500", &create);

0 commit comments

Comments
 (0)