Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| isp:sample_ae_implementation_in_c [2018/06/15 01:27] – [AE::_analyzeLH()] Igor Yefmov | isp:sample_ae_implementation_in_c [2022/04/04 23:32] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 8: | Line 8: | ||
| The code below conforms to C++17 standard and it is recommended to use the C++17 toolchain as this is the version the [[code:: | The code below conforms to C++17 standard and it is recommended to use the C++17 toolchain as this is the version the [[code:: | ||
| - | ====== | + | ====== |
| A few short functions for code readability, | A few short functions for code readability, | ||
| Line 15: | Line 15: | ||
| constexpr int defectivePixels = 100; // assume there are at least that many DP that will screw our stats | constexpr int defectivePixels = 100; // assume there are at least that many DP that will screw our stats | ||
| - | static | + | int _triggeredLeft(const ImgStats& |
| - | auto count = max(defectivePixels, | + | auto count = max(defectivePixels, |
| for(int i = 0; i < 256; ++i){ | for(int i = 0; i < 256; ++i){ | ||
| - | count -= _stats.hist[HIST_Y][i]; | + | count -= _stats.ae.hist[i]; |
| if(count < 0){ | if(count < 0){ | ||
| return i; | return i; | ||
| Line 26: | Line 26: | ||
| } | } | ||
| - | static | + | int _triggeredRight(const ImgStats& |
| - | auto count = max(defectivePixels, | + | auto count = max(defectivePixels, |
| for(int i = 255; i >= 0; --i){ | for(int i = 255; i >= 0; --i){ | ||
| - | count -= _stats.hist[HIST_Y][i]; | + | count -= _stats.ae.hist[i]; |
| if(count < 0){ | if(count < 0){ | ||
| return i; | return i; | ||
| Line 36: | Line 36: | ||
| return 0; | return 0; | ||
| } | } | ||
| + | |||
| + | }</ | ||
| + | |||
| + | ====== PerPart ====== | ||
| + | This helper class is to avoid stupid arithmetic errors when mixing various ratios, like absolute ratios, percentages (%), per-milles(‰), | ||
| + | |||
| + | In addition a few helpful user-defined literals are provided for better code readability when it comes to constants. | ||
| + | |||
| + | This is a super-minimalistic class that doesn' | ||
| + | |||
| + | <code c++> | ||
| + | |||
| + | struct PerPart{ | ||
| + | PerPart(long double _val = 0.f) : m_val(_val) {} // 1.0f == 100_pcnt | ||
| + | PerPart(unsigned long long int _val, unsigned long long int _div) | ||
| + | : PerPart(_div == 0 ? 0.f : static_cast< | ||
| + | {} | ||
| + | operator long double() const { return m_val; } | ||
| + | int pcnt() const { return static_cast< | ||
| + | |||
| + | private: | ||
| + | long double m_val = 0.f; | ||
| + | }; | ||
| + | |||
| + | PerPart operator"" | ||
| + | PerPart operator"" | ||
| + | PerPart operator"" | ||
| }</ | }</ | ||
| Line 85: | Line 112: | ||
| using Val = IImgSensor:: | using Val = IImgSensor:: | ||
| IImgSensor & m_ov; // OmniVision sensor interface | IImgSensor & m_ov; // OmniVision sensor interface | ||
| - | const ImgStats & m_stats; | + | const PerPart m_hysteresis; |
| struct{ | struct{ | ||
| Line 101: | Line 128: | ||
| ===== Constructor AE::AE ===== | ===== Constructor AE::AE ===== | ||
| - | A bulk of (quite simplistic) calculations is performed with data in _stats and the results are stored in the m_vars struct: | + | A bulk of (quite simplistic) calculations is performed with data in '' |
| - | <code c++> | + | <code c++> |
| : m_ov(*_ov) | : m_ov(*_ov) | ||
| - | , m_stats(_stats) | + | , m_hysteresis(_hysteresis) |
| { | { | ||
| - | m_vars.underExposed = _triggeredLeft(m_stats, _pp); | + | m_vars.underExposed = _triggeredLeft(_stats, _pp); |
| - | m_vars.overExposed = _triggeredRight(m_stats, _pp); | + | m_vars.overExposed = _triggeredRight(_stats, _pp); |
| - | m_vars.avgY = static_cast< | + | m_vars.avgY = static_cast< |
| m_vars.adjY = m_vars.avgY == 0 ? 0 : 1. * _tgtLuma / m_vars.avgY; | m_vars.adjY = m_vars.avgY == 0 ? 0 : 1. * _tgtLuma / m_vars.avgY; | ||
| }</ | }</ | ||
| Line 124: | Line 151: | ||
| const auto diffRight = 256 - m_vars.overExposed; | const auto diffRight = 256 - m_vars.overExposed; | ||
| if(diffLeft > diffRight * 2){ | if(diffLeft > diffRight * 2){ | ||
| - | const int currBL = m_ov[Val::BLACK_LEVEL]; | + | const int currBL = m_ov[Val::black_level]; |
| if(currBL > 0){ | if(currBL > 0){ | ||
| m_vars.adjBL = - max(1, currBL / 10); | m_vars.adjBL = - max(1, currBL / 10); | ||
| Line 160: | Line 187: | ||
| m_vars.adjY += (1 - m_vars.adjY) * 5 / 8; // be 62.5% less aggressive than requested to avoid oscillations | m_vars.adjY += (1 - m_vars.adjY) * 5 / 8; // be 62.5% less aggressive than requested to avoid oscillations | ||
| - | if(m_vars.overExposed > 60 && m_vars.adjY < 1){ | + | if(m_vars.overExposed > 60 && m_vars.adjY < 1 - m_hysteresis){ |
| m_vars.actionRH = Action:: | m_vars.actionRH = Action:: | ||
| - | }else if(m_vars.overExposed >= 250 && m_vars.adjY <= 1){ | + | }else if(m_vars.overExposed > 250 && m_vars.adjY <= 1 - m_hysteresis){ |
| m_vars.actionRH = Action:: | m_vars.actionRH = Action:: | ||
| - | }else if(m_vars.overExposed < 240 && m_vars.adjY > 1){ | + | }else if(m_vars.overExposed < 250 && m_vars.adjY > 1 + m_hysteresis){ |
| m_vars.actionRH = Action:: | m_vars.actionRH = Action:: | ||
| }else{ | }else{ | ||
| Line 177: | Line 204: | ||
| <code c++>void AE:: | <code c++>void AE:: | ||
| { | { | ||
| - | auto bl = m_ov[Val::BLACK_LEVEL]; | + | auto bl = m_ov[Val::black_level]; |
| - | bl = std:: | + | bl = std:: |
| }</ | }</ | ||
| Line 202: | Line 229: | ||
| <code c++>void AE:: | <code c++>void AE:: | ||
| { | { | ||
| - | const auto limitExp = m_ov.getLimit(Val:: | + | const auto limitExp = m_ov.getLimit(Val:: |
| - | const int setExp = m_ov[Val::EXPOSURE]; | + | const int setExp = m_ov[Val::exposure]; |
| if(setExp < limitExp){ | if(setExp < limitExp){ | ||
| - | m_ov[Val::EXPOSURE] = min(limitExp, | + | m_ov[Val::exposure] = min(limitExp, |
| m_vars.op = CorrOp:: | m_vars.op = CorrOp:: | ||
| return; | return; | ||
| } | } | ||
| | | ||
| - | const auto limitGg = m_ov.getLimit(Val:: | + | const auto limitGg = m_ov.getLimit(Val:: |
| - | const int setGg = m_ov[Val::GAIN_GLOBAL]; | + | const int setGg = m_ov[Val::gain_global]; |
| if(setGg < limitGg){ | if(setGg < limitGg){ | ||
| - | m_ov[Val::GAIN_GLOBAL] = min(limitGg, | + | m_ov[Val::gain_global] = min(limitGg, |
| m_vars.op = CorrOp:: | m_vars.op = CorrOp:: | ||
| return; | return; | ||
| } | } | ||
| | | ||
| - | const auto limitRgb = m_ov.getLimit(Val:: | + | const auto limitRgb = m_ov.getLimit(Val:: |
| - | const int setGreen = m_ov[Val::GAIN_G]; | + | const int setGreen = m_ov[Val::gain_g]; |
| if(m_vars.adjY > 4 && setGreen < limitRgb * 0.95){ | if(m_vars.adjY > 4 && setGreen < limitRgb * 0.95){ | ||
| const auto safetyBuffer = 0.95; | const auto safetyBuffer = 0.95; | ||
| const auto limitRGain = 1180; | const auto limitRGain = 1180; | ||
| - | const int highestRgb = max(m_ov[Val:: | + | const int highestRgb = max(m_ov[Val:: |
| const double topMultiplier = std:: | const double topMultiplier = std:: | ||
| const auto gainG = static_cast< | const auto gainG = static_cast< | ||
| if(gainG > setGreen){ | if(gainG > setGreen){ | ||
| - | m_ov[Val::GAIN_G] = gainG; | + | m_ov[Val::gain_g] = gainG; |
| m_vars.op = CorrOp:: | m_vars.op = CorrOp:: | ||
| return; | return; | ||
| Line 235: | Line 262: | ||
| m_vars.op = CorrOp:: | m_vars.op = CorrOp:: | ||
| }</ | }</ | ||
| + | |||
| + | ===== AE:: | ||
| + | |||
| + | Quite expectedly this function mirrors '' | ||
| + | |||
| + | <code c++>void AE:: | ||
| + | { | ||
| + | const int setGreen = m_ov[Val:: | ||
| + | if(setGreen > 1024){ | ||
| + | const auto nextGreen = static_cast< | ||
| + | assert(nextGreen >= 1024); | ||
| + | m_ov[Val:: | ||
| + | m_vars.op = CorrOp:: | ||
| + | return; | ||
| + | } | ||
| + | const int setGg = m_ov[Val:: | ||
| + | if(setGg > 0){ | ||
| + | m_ov[Val:: | ||
| + | m_vars.op = CorrOp:: | ||
| + | }else{ | ||
| + | m_ov[Val:: | ||
| + | m_vars.op = CorrOp:: | ||
| + | |||
| + | if(m_ov[Val:: | ||
| + | m_ov[Val:: | ||
| + | m_vars.op = CorrOp:: | ||
| + | } | ||
| + | } | ||
| + | }</ | ||
| + | |||
| + | ====== Sample usage ====== | ||
| + | |||
| + | This is a copy-paste of the code used in [[manual: | ||
| + | |||
| + | <code c++> | ||
| + | |||
| + | static CString _corrOp2Txt(AE:: | ||
| + | using op = AE::CorrOp; | ||
| + | switch(_op){ | ||
| + | case op:: | ||
| + | case op:: | ||
| + | case op:: | ||
| + | case op:: | ||
| + | case op:: | ||
| + | case op:: | ||
| + | case op:: | ||
| + | } | ||
| + | return {}; | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | |||
| + | void ImgAnalyzerDlg:: | ||
| + | { | ||
| + | if(((CButton *)(GetDlgItem(IDC_CHECK_ISA_AE)))-> | ||
| + | return; | ||
| + | } | ||
| + | const auto & stats = m_analyzer.getStats(); | ||
| + | const auto correct = ((CButton*)(GetDlgItem(IDC_CHECK_ISA_AE_CORRECT)))-> | ||
| + | const auto aeTolerance = PerPart(GetDlgItemInt(IDC_EDIT_ISA_AE_RIGHT, | ||
| + | const int aeTgtLuma = GetDlgItemInt(IDC_EDIT_ISA_AE_BRIGHTNESS, | ||
| + | const PerPart aeHysteresis{GetDlgItemInt(IDC_EDIT_ISA_AE_HYSTERESIS, | ||
| + | AE ae{m_fx3.sensor(), | ||
| + | const auto rez = ae(correct); | ||
| + | |||
| + | // display the information | ||
| + | { | ||
| + | const PerPart pxUsed{stats.ae.pixels, | ||
| + | CString msg; | ||
| + | msg.AppendFormat(L" | ||
| + | msg.AppendFormat(L" | ||
| + | msg.AppendFormat(L" | ||
| + | msg.AppendFormat(L" | ||
| + | msg.AppendFormat(L" | ||
| + | msg += _corrOp2Txt(ae.details().op); | ||
| + | SetDlgItemTextW(IDC_STATIC_ISA_AE, | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ====== Initial values ====== | ||
| + | |||
| + | Following are the initial defaults for the variables that affect the process of automatic image brightness (Black Level, Exposure, Gains) adjustment: | ||
| + | ^ Name (as seen in '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | " | ||