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/16 03:44] – [AE::_aeInc()] Igor Yefmovisp:sample_ae_implementation_in_c [2022/04/04 23:32] (current) – external edit 127.0.0.1
Line 16: Line 16:
  
 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 27: Line 27:
  
 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 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 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