User Tools

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
isp:sample_ae_implementation_in_c [2018/06/15 01:30] – [AE::_aeInc()] Igor Yefmovisp: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::SUB2r-lib]] is targetting. 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::SUB2r-lib]] is targetting.
  
-====== Helper one-liners ======+====== Helpers ======
 A few short functions for code readability, put into a local anonymous namespace (effectively making these function ''static'' and making sure we don't pollute/collide with the global namespace). A few short functions for code readability, put into a local anonymous namespace (effectively making these function ''static'' and making sure we don't pollute/collide with the global namespace).
  
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& _stats, PerPart _pp){ +int _triggeredLeft(const ImgStats& _stats, PerPart _pp){ 
-    auto count = max(defectivePixels, _stats.total.pixels * _pp);+    auto count = max(defectivePixels, _stats.ae.pixels * _pp);
     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& _stats, PerPart _pp){ +int _triggeredRight(const ImgStats& _stats, PerPart _pp){ 
-    auto count = max(defectivePixels, _stats.total.pixels * _pp);+    auto count = max(defectivePixels, _stats.ae.pixels * _pp);
     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;
 } }
 +
 +}</code>
 +
 +====== PerPart ======
 +This helper class is to avoid stupid arithmetic errors when mixing various ratios, like absolute ratios, percentages (%), per-milles(‰), parts-per-million (ppm), etc. It also helps with gracefully handling division by zero (which results in defaulting the result of such ratio to ''0'').
 +
 +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't pretend to be production-quality.
 +
 +<code c++>namespace{
 +
 +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<long double>(_val) / _div)
 +    {}
 +    operator long double() const { return m_val; }
 +    int pcnt() const { return static_cast<int>(round(m_val * 100)); }
 +
 +private:
 +    long double m_val = 0.f;
 +};
 +
 +PerPart operator"" _pcnt(unsigned long long int _val){return PerPart(_val, 100);}
 +PerPart operator"" _pmille(unsigned long long int _val){return PerPart(_val, 1000);}
 +PerPart operator"" _ppm(unsigned long long int _val){return PerPart(_val, 1000 * 1000);}
  
 }</code> }</code>
Line 85: Line 112:
     using Val = IImgSensor::Value;  // shortcut for typing out the full type qualifier     using Val = IImgSensor::Value;  // shortcut for typing out the full type qualifier
     IImgSensor & m_ov;              // OmniVision sensor interface     IImgSensor & m_ov;              // OmniVision sensor interface
-    const ImgStats & m_stats      // frame's statistical data+    const PerPart m_hysteresis    // tolerance +/- for how accurate/sensitive the correction should be
  
     struct{     struct{
Line 103: Line 130:
 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 ''_stats'' and the results are stored in the ''m_vars'' struct:
  
-<code c++>AE::AE(IImgSensor * _ov, const ImgStats & _stats, const PerPart & _pp, int _tgtLuma)+<code c++>AE::AE(IImgSensor * _ov, const ImgStats & _stats, const PerPart & _pp, int _tgtLuma, const PerPart & _hysteresis)
     : 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<int>(m_stats.total.pixels == 0 ? 0 : m_stats.total.y / m_stats.total.pixels);+    m_vars.avgY = static_cast<int>(_stats.ae.pixels == 0 ? 0 : _stats.ae.y / _stats.ae.pixels);
     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;
 }</code> }</code>
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::dec;  // plenty of pixels at level 60+ and we were asked to dim the image         m_vars.actionRH = Action::dec;  // plenty of pixels at level 60+ and we were asked to dim the image
-    }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::dec;  // too many overexposed pixels and NOT increasing brightness         m_vars.actionRH = Action::dec;  // too many overexposed pixels and NOT increasing brightness
-    }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::inc;  // there's room to grow and asked to increase brightness         m_vars.actionRH = Action::inc;  // there's room to grow and asked to increase brightness
     }else{     }else{
Line 177: Line 204:
 <code c++>void AE::_correctLH() <code c++>void AE::_correctLH()
 { {
-    auto bl = m_ov[Val::BLACK_LEVEL]; +    auto bl = m_ov[Val::black_level]; 
-    bl = std::clamp(bl + m_vars.adjBL, 0, m_ov.getLimit(Val::BLACK_LEVEL));+    bl = std::clamp(bl + m_vars.adjBL, 0, m_ov.getLimit(Val::black_level));
 }</code> }</code>
  
Line 202: Line 229:
 <code c++>void AE::_aeInc() <code c++>void AE::_aeInc()
 { {
-    const auto limitExp = m_ov.getLimit(Val::EXPOSURE); +    const auto limitExp = m_ov.getLimit(Val::exposure); 
-    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, static_cast<int>((setExp ? setExp : 2000) * m_vars.adjY));+        m_ov[Val::exposure] = min(limitExp, static_cast<int>((setExp ? setExp : 2000) * m_vars.adjY));
         m_vars.op = CorrOp::inc_exp;         m_vars.op = CorrOp::inc_exp;
         return;         return;
     }     }
          
-    const auto limitGg = m_ov.getLimit(Val::GAIN_GLOBAL); +    const auto limitGg = m_ov.getLimit(Val::gain_global); 
-    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, static_cast<int>((setGg ? setGg : 10) * m_vars.adjY));+        m_ov[Val::gain_global] = min(limitGg, static_cast<int>((setGg ? setGg : 10) * m_vars.adjY));
         m_vars.op = CorrOp::inc_gl;         m_vars.op = CorrOp::inc_gl;
         return;         return;
     }     }
          
-    const auto limitRgb = m_ov.getLimit(Val::GAIN_G); +    const auto limitRgb = m_ov.getLimit(Val::gain_g); 
-    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::GAIN_R], m_ov[Val::GAIN_B]);+        const int highestRgb = max(m_ov[Val::gain_r], m_ov[Val::gain_b]);
         const double topMultiplier = std::clamp(m_vars.adjY, 1., safetyBuffer * limitRgb / highestRgb);         const double topMultiplier = std::clamp(m_vars.adjY, 1., safetyBuffer * limitRgb / highestRgb);
         const auto gainG = static_cast<int>(min(limitRGain, setGreen * topMultiplier));         const auto gainG = static_cast<int>(min(limitRGain, setGreen * topMultiplier));
         if(gainG > setGreen){         if(gainG > setGreen){
-            m_ov[Val::GAIN_G] = gainG;+            m_ov[Val::gain_g] = gainG;
             m_vars.op = CorrOp::inc_green;             m_vars.op = CorrOp::inc_green;
             return;             return;
Line 242: Line 269:
 <code c++>void AE::_aeDec() <code c++>void AE::_aeDec()
 { {
-    const int setGreen = m_ov[Val::GAIN_G];+    const int setGreen = m_ov[Val::gain_g];
     if(setGreen > 1024){     if(setGreen > 1024){
         const auto nextGreen = static_cast<int>(setGreen - (setGreen - 1024) * 10_pcnt);         const auto nextGreen = static_cast<int>(setGreen - (setGreen - 1024) * 10_pcnt);
         assert(nextGreen >= 1024);         assert(nextGreen >= 1024);
-        m_ov[Val::GAIN_G] = nextGreen;+        m_ov[Val::gain_g] = nextGreen;
         m_vars.op = CorrOp::dec_green;         m_vars.op = CorrOp::dec_green;
         return;         return;
     }     }
-    const int setGg = m_ov[Val::GAIN_GLOBAL];+    const int setGg = m_ov[Val::gain_global];
     if(setGg > 0){     if(setGg > 0){
-        m_ov[Val::GAIN_GLOBAL] = static_cast<int>(setGg * m_vars.adjY);+        m_ov[Val::gain_global] = static_cast<int>(setGg * m_vars.adjY);
         m_vars.op = CorrOp::dec_gl;         m_vars.op = CorrOp::dec_gl;
     }else{     }else{
-        m_ov[Val::EXPOSURE] *= m_vars.adjY;+        m_ov[Val::exposure] *= m_vars.adjY;
         m_vars.op = CorrOp::dec_exp;         m_vars.op = CorrOp::dec_exp;
  
-        if(m_ov[Val::EXPOSURE] < 10){   // safety net to make sure we never get to corner case +        if(m_ov[Val::exposure] < 10){   // safety net to make sure we never get to corner case 
-            m_ov[Val::EXPOSURE] = 10;+            m_ov[Val::exposure] = 10;
             m_vars.op = CorrOp::noop;             m_vars.op = CorrOp::noop;
         }         }
Line 265: Line 292:
 }</code> }</code>
  
 +====== Sample usage ======
 +
 +This is a copy-paste of the code used in [[manual:Aria]] utility for the POC. This code has minimal comments but should be fairly easy to follow:
 +
 +<code c++>namespace{
 +
 +static CString _corrOp2Txt(AE::CorrOp _op){
 +    using op = AE::CorrOp;
 +    switch(_op){
 +    case op::noop:      return {};
 +    case op::inc_exp:   return L"+++ Increasing Exposure";
 +    case op::inc_gl:    return L"+++ Increasing GLOBAL gain";
 +    case op::inc_green: return L"+++ PANIC! Increasing GREEN gain";
 +    case op::dec_exp:   return L"--- Reducing Exposure";
 +    case op::dec_gl:    return L"--- Reducing GLOBAL gain";
 +    case op::dec_green: return L"--- Reducing GREEN gain";
 +    }
 +    return {};
 +}
 +
 +}
 +
 +
 +void ImgAnalyzerDlg::_aeInfo()
 +{
 +    if(((CButton *)(GetDlgItem(IDC_CHECK_ISA_AE)))->GetCheck() != BST_CHECKED){
 +        return;
 +    }
 +    const auto & stats = m_analyzer.getStats();
 +    const auto correct = ((CButton*)(GetDlgItem(IDC_CHECK_ISA_AE_CORRECT)))->GetCheck() == BST_CHECKED;
 +    const auto aeTolerance = PerPart(GetDlgItemInt(IDC_EDIT_ISA_AE_RIGHT, NULL, FALSE), 1000);
 +    const int aeTgtLuma = GetDlgItemInt(IDC_EDIT_ISA_AE_BRIGHTNESS, NULL, FALSE);
 +    const PerPart aeHysteresis{GetDlgItemInt(IDC_EDIT_ISA_AE_HYSTERESIS, NULL, FALSE), 100};
 +    AE ae{m_fx3.sensor(), stats, aeTolerance, aeTgtLuma, aeHysteresis};
 +    const auto rez = ae(correct);
 +
 +    // display the information
 +    {
 +        const PerPart pxUsed{stats.ae.pixels, stats.total.pixels};
 +        CString msg;
 +        msg.AppendFormat(L"Pixels used: %d (%.1f%%)\n", stats.ae.pixels, pxUsed * 100);
 +        msg.AppendFormat(L"Under/over-exposed: %d/%d\n", rez.underExposed, rez.overExposed);
 +        msg.AppendFormat(L"BL adj.: %d\n", rez.adjBL);
 +        msg.AppendFormat(L"Avg Y.: %d\n", rez.avgY);
 +        msg.AppendFormat(L"Brightness adj.: %.2f\n", rez.adjY);
 +        msg += _corrOp2Txt(ae.details().op);
 +        SetDlgItemTextW(IDC_STATIC_ISA_AE, msg);
 +    }
 +}
 +</code>
 +
 +====== 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 ''%%ImgAnalyzerDlg%%::_aeInfo()'' above) ^ Value ^
 +| ''aeTolerance'' | ''1/1000'' |
 +| ''aeTgtLuma'' | ''90'' |
 +| ''aeHysteresis'' | ''5/100'' |
 +| "center window" dimensions | ''50%'' vertically and horizontally, centered at image's center |

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also, you acknowledge that you have read and understand our Privacy Policy. If you do not agree, please leave the website.

More information