summaryrefslogtreecommitdiff
path: root/utils/raspberrypi/ctt/ctt_noise.py
blob: 3270bf341c870398d432d0ee0771f507edb46410 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (C) 2019, Raspberry Pi Ltd
#
# ctt_noise.py - camera tuning tool noise calibration

from ctt_image_load import *
import matplotlib.pyplot as plt


"""
Find noise standard deviation and fit to model:

    noise std = a + b*sqrt(pixel mean)
"""
def noise(Cam, Img, plot):
    Cam.log += '\nProcessing image: {}'.format(Img.name)
    stds = []
    means = []
    """
    iterate through macbeth square patches
    """
    for ch_patches in Img.patches:
        for patch in ch_patches:
            """
            renormalise patch
            """
            patch = np.array(patch)
            patch = (patch-Img.blacklevel_16)/Img.againQ8_norm
            std = np.std(patch)
            mean = np.mean(patch)
            stds.append(std)
            means.append(mean)

    """
    clean data and ensure all means are above 0
    """
    stds = np.array(stds)
    means = np.array(means)
    means = np.clip(np.array(means), 0, None)
    sq_means = np.sqrt(means)

    """
    least squares fit model
    """
    fit = np.polyfit(sq_means, stds, 1)
    Cam.log += '\nBlack level = {}'.format(Img.blacklevel_16)
    Cam.log += '\nNoise profile: offset = {}'.format(int(fit[1]))
    Cam.log += ' slope = {:.3f}'.format(fit[0])
    """
    remove any values further than std from the fit

    anomalies most likely caused by:
    > ucharacteristically noisy white patch
    > saturation in the white patch
    """
    fit_score = np.abs(stds - fit[0]*sq_means - fit[1])
    fit_std = np.std(stds)
    fit_score_norm = fit_score - fit_std
    anom_ind = np.where(fit_score_norm > 1)
    fit_score_norm.sort()
    sq_means_clean = np.delete(sq_means, anom_ind)
    stds_clean = np.delete(stds, anom_ind)
    removed = len(stds) - len(stds_clean)
    if removed != 0:
        Cam.log += '\nIdentified and removed {} anomalies.'.format(removed)
        Cam.log += '\nRecalculating fit'
        """
        recalculate fit with outliers removed
        """
        fit = np.polyfit(sq_means_clean, stds_clean, 1)
        Cam.log += '\nNoise profile: offset = {}'.format(int(fit[1]))
        Cam.log += ' slope = {:.3f}'.format(fit[0])

    """
    if fit const is < 0 then force through 0 by
    dividing by sq_means and fitting poly order 0
    """
    corrected = 0
    if fit[1] < 0:
        corrected = 1
        ones = np.ones(len(means))
        y_data = stds/sq_means
        fit2 = np.polyfit(ones, y_data, 0)
        Cam.log += '\nOffset below zero. Fit recalculated with zero offset'
        Cam.log += '\nNoise profile: offset = 0'
        Cam.log += ' slope = {:.3f}'.format(fit2[0])
        # print('new fit')
        # print(fit2)

    """
    plot fit for debug
    """
    if plot:
        x = np.arange(sq_means.max()//0.88)
        fit_plot = x*fit[0] + fit[1]
        plt.scatter(sq_means, stds, label='data', color='blue')
        plt.scatter(sq_means[anom_ind], stds[anom_ind], color='orange', label='anomalies')
        plt.plot(x, fit_plot, label='fit', color='red', ls=':')
        if fit[1] < 0:
            fit_plot_2 = x*fit2[0]
            plt.plot(x, fit_plot_2, label='fit 0 intercept', color='green', ls='--')
        plt.plot(0, 0)
        plt.title('Noise Plot\nImg: {}'.format(Img.str))
        plt.legend(loc='upper left')
        plt.xlabel('Sqrt Pixel Value')
        plt.ylabel('Noise Standard Deviation')
        plt.grid()
        plt.show()
    """
    End of plotting code
    """

    """
    format output to include forced 0 constant
    """
    Cam.log += '\n'
    if corrected:
        fit = [fit2[0], 0]
        return fit

    else:
        return fit
> * \brief Compute gain code from the analogue gain absolute value * \param[in] gain The real gain to pass * * This function aims to abstract the calculation of the gain letting the IPA * use the real gain for its estimations. * * \return The gain code to pass to V4L2 */ uint32_t CameraSensorHelper::gainCode(double gain) const { const AnalogueGainConstants &k = gainConstants_; switch (gainType_) { case AnalogueGainLinear: ASSERT(k.linear.m0 == 0 || k.linear.m1 == 0); return (k.linear.c0 - k.linear.c1 * gain) / (k.linear.m1 * gain - k.linear.m0); case AnalogueGainExponential: ASSERT(k.exp.a != 0 && k.exp.m != 0); return std::log2(gain / k.exp.a) / k.exp.m; default: ASSERT(false); return 0; } } /** * \brief Compute the real gain from the V4L2 subdev control gain code * \param[in] gainCode The V4L2 subdev control gain * * This function aims to abstract the calculation of the gain letting the IPA * use the real gain for its estimations. It is the counterpart of the function * CameraSensorHelper::gainCode. * * \return The real gain */ double CameraSensorHelper::gain(uint32_t gainCode) const { const AnalogueGainConstants &k = gainConstants_; double gain = static_cast<double>(gainCode); switch (gainType_) { case AnalogueGainLinear: ASSERT(k.linear.m0 == 0 || k.linear.m1 == 0); return (k.linear.m0 * gain + k.linear.c0) / (k.linear.m1 * gain + k.linear.c1); case AnalogueGainExponential: ASSERT(k.exp.a != 0 && k.exp.m != 0); return k.exp.a * std::exp2(k.exp.m * gain); default: ASSERT(false); return 0.0; } } /** * \enum CameraSensorHelper::AnalogueGainType * \brief The gain calculation modes as defined by the MIPI CCS * * Describes the image sensor analogue gain capabilities. * Two modes are possible, depending on the sensor: Linear and Exponential. */ /** * \var CameraSensorHelper::AnalogueGainLinear * \brief Gain is computed using linear gain estimation * * The relationship between the integer gain parameter and the resulting gain * multiplier is given by the following equation: * * \f$gain=\frac{m0x+c0}{m1x+c1}\f$ * * Where 'x' is the gain control parameter, and m0, m1, c0 and c1 are * image-sensor-specific constants of the sensor. * These constants are static parameters, and for any given image sensor either * m0 or m1 shall be zero. * * The full Gain equation therefore reduces to either: * * \f$gain=\frac{c0}{m1x+c1}\f$ or \f$\frac{m0x+c0}{c1}\f$ */ /** * \var CameraSensorHelper::AnalogueGainExponential * \brief Gain is expressed using an exponential model * * The relationship between the integer gain parameter and the resulting gain * multiplier is given by the following equation: * * \f$gain = a \cdot 2^{m \cdot x}\f$ * * Where 'x' is the gain control parameter, and 'a' and 'm' are image * sensor-specific constants. * * This is a subset of the MIPI CCS exponential gain model with the linear * factor 'a' being a constant, but with the exponent being configurable * through the 'm' coefficient. * * When the gain is expressed in dB, 'a' is equal to 1 and 'm' to * \f$log_{2}{10^{\frac{1}{20}}}\f$. */ /** * \struct CameraSensorHelper::AnalogueGainLinearConstants * \brief Analogue gain constants for the linear gain model * * \var CameraSensorHelper::AnalogueGainLinearConstants::m0 * \brief Constant used in the linear gain coding/decoding * * \note Either m0 or m1 shall be zero. * * \var CameraSensorHelper::AnalogueGainLinearConstants::c0 * \brief Constant used in the linear gain coding/decoding * * \var CameraSensorHelper::AnalogueGainLinearConstants::m1 * \brief Constant used in the linear gain coding/decoding * * \note Either m0 or m1 shall be zero. * * \var CameraSensorHelper::AnalogueGainLinearConstants::c1 * \brief Constant used in the linear gain coding/decoding */ /** * \struct CameraSensorHelper::AnalogueGainExpConstants * \brief Analogue gain constants for the exponential gain model * * \var CameraSensorHelper::AnalogueGainExpConstants::a * \brief Constant used in the exponential gain coding/decoding * * \var CameraSensorHelper::AnalogueGainExpConstants::m * \brief Constant used in the exponential gain coding/decoding */ /** * \struct CameraSensorHelper::AnalogueGainConstants * \brief Analogue gain model constants * * This union stores the constants used to calculate the analogue gain. The * CameraSensorHelper::gainType_ variable selects which union member is valid. * * \var CameraSensorHelper::AnalogueGainConstants::linear * \brief Constants for the linear gain model * * \var CameraSensorHelper::AnalogueGainConstants::exp * \brief Constants for the exponential gain model */ /** * \var CameraSensorHelper::gainType_ * \brief The analogue gain model type */ /** * \var CameraSensorHelper::gainConstants_ * \brief The analogue gain parameters used for calculation * * The analogue gain is calculated through a formula, and its parameters are * sensor specific. Use this variable to store the values at init time. */ /** * \class CameraSensorHelperFactory * \brief Registration of CameraSensorHelperFactory classes and creation of instances * * To facilitate discovery and instantiation of CameraSensorHelper classes, the * CameraSensorHelperFactory class maintains a registry of camera sensor helper * sub-classes. Each CameraSensorHelper subclass shall register itself using the * REGISTER_CAMERA_SENSOR_HELPER() macro, which will create a corresponding * instance of a CameraSensorHelperFactory subclass and register it with the * static list of factories. */ /** * \brief Construct a camera sensor helper factory * \param[in] name Name of the camera sensor helper class * * Creating an instance of the factory registers it with the global list of * factories, accessible through the factories() function. * * The factory \a name is used for debug purpose and shall be unique. */ CameraSensorHelperFactory::CameraSensorHelperFactory(const std::string name) : name_(name) { registerType(this); } /** * \brief Create an instance of the CameraSensorHelper corresponding to * a named factory * \param[in] name Name of the factory * * \return A unique pointer to a new instance of the CameraSensorHelper subclass * corresponding to the named factory or a null pointer if no such factory * exists */ std::unique_ptr<CameraSensorHelper> CameraSensorHelperFactory::create(const std::string &name) { std::vector<CameraSensorHelperFactory *> &factories = CameraSensorHelperFactory::factories(); for (CameraSensorHelperFactory *factory : factories) { if (name != factory->name_) continue; CameraSensorHelper *helper = factory->createInstance(); return std::unique_ptr<CameraSensorHelper>(helper); } return nullptr; } /** * \brief Add a camera sensor helper class to the registry * \param[in] factory Factory to use to construct the camera sensor helper * * The caller is responsible to guarantee the uniqueness of the camera sensor * helper name. */ void CameraSensorHelperFactory::registerType(CameraSensorHelperFactory *factory) { std::vector<CameraSensorHelperFactory *> &factories = CameraSensorHelperFactory::factories(); factories.push_back(factory); } /** * \brief Retrieve the list of all camera sensor helper factories * \return The list of camera sensor helper factories */ std::vector<CameraSensorHelperFactory *> &CameraSensorHelperFactory::factories() { /* * The static factories map is defined inside the function to ensure * it gets initialized on first use, without any dependency on link * order. */ static std::vector<CameraSensorHelperFactory *> factories; return factories; } /** * \fn CameraSensorHelperFactory::createInstance() * \brief Create an instance of the CameraSensorHelper corresponding to the * factory * * This virtual function is implemented by the REGISTER_CAMERA_SENSOR_HELPER() * macro. It creates a camera sensor helper instance associated with the camera * sensor model. * * \return A pointer to a newly constructed instance of the CameraSensorHelper * subclass corresponding to the factory */ /** * \var CameraSensorHelperFactory::name_ * \brief The name of the factory */ /** * \def REGISTER_CAMERA_SENSOR_HELPER * \brief Register a camera sensor helper with the camera sensor helper factory * \param[in] name Sensor model name used to register the class * \param[in] helper Class name of CameraSensorHelper derived class to register * * Register a CameraSensorHelper subclass with the factory and make it available * to try and match sensors. */ /* ----------------------------------------------------------------------------- * Sensor-specific subclasses */ #ifndef __DOXYGEN__ /* * Helper function to compute the m parameter of the exponential gain model * when the gain code is expressed in dB. */ static constexpr double expGainDb(double step) { constexpr double log2_10 = 3.321928094887362; /* * The gain code is expressed in step * dB (e.g. in 0.1 dB steps): * * G_code = G_dB/step = 20/step*log10(G_linear) * * Inverting the formula, we get * * G_linear = 10^(step/20*G_code) = 2^(log2(10)*step/20*G_code) */ return log2_10 * step / 20; } class CameraSensorHelperImx219 : public CameraSensorHelper { public: CameraSensorHelperImx219() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 0, 256, -1, 256 }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx219", CameraSensorHelperImx219) class CameraSensorHelperImx258 : public CameraSensorHelper { public: CameraSensorHelperImx258() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 0, 512, -1, 512 }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx258", CameraSensorHelperImx258) class CameraSensorHelperImx290 : public CameraSensorHelper { public: CameraSensorHelperImx290() { gainType_ = AnalogueGainExponential; gainConstants_.exp = { 1.0, expGainDb(0.3) }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx290", CameraSensorHelperImx290) class CameraSensorHelperImx296 : public CameraSensorHelper { public: CameraSensorHelperImx296() { gainType_ = AnalogueGainExponential; gainConstants_.exp = { 1.0, expGainDb(0.1) }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx296", CameraSensorHelperImx296) class CameraSensorHelperImx477 : public CameraSensorHelper { public: CameraSensorHelperImx477() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 0, 1024, -1, 1024 }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx477", CameraSensorHelperImx477) class CameraSensorHelperOv2740 : public CameraSensorHelper { public: CameraSensorHelperOv2740() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov2740", CameraSensorHelperOv2740) class CameraSensorHelperOv5640 : public CameraSensorHelper { public: CameraSensorHelperOv5640() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 1, 0, 0, 16 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5640", CameraSensorHelperOv5640) class CameraSensorHelperOv5670 : public CameraSensorHelper { public: CameraSensorHelperOv5670() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5670", CameraSensorHelperOv5670) class CameraSensorHelperOv5675 : public CameraSensorHelper { public: CameraSensorHelperOv5675() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5675", CameraSensorHelperOv5675) class CameraSensorHelperOv5693 : public CameraSensorHelper { public: CameraSensorHelperOv5693() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 1, 0, 0, 16 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5693", CameraSensorHelperOv5693) class CameraSensorHelperOv8865 : public CameraSensorHelper { public: CameraSensorHelperOv8865() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov8865", CameraSensorHelperOv8865) class CameraSensorHelperOv13858 : public CameraSensorHelper { public: CameraSensorHelperOv13858() { gainType_ = AnalogueGainLinear; gainConstants_.linear = { 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov13858", CameraSensorHelperOv13858) #endif /* __DOXYGEN__ */ } /* namespace ipa */ } /* namespace libcamera */