Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionLast revisionBoth sides next revision | ||
isp:sample_ae_implementation_in_c [2018/06/15 01:17] – [AE::_analyzeRH()] Igor Yefmov | isp:sample_ae_implementation_in_c [2018/07/03 22:22] – [Initial values] Igor Yefmov | ||
---|---|---|---|
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 115: | Line 142: | ||
===== AE:: | ===== AE:: | ||
- | Decide what to do with the "left side of the histogram", | + | Decide what to do with the "left side of the histogram", |
<code c++>void AE:: | <code c++>void AE:: | ||
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:: |
}</ | }</ | ||
+ | ===== AE:: | ||
+ | |||
+ | "Right hand side" correction is a bit more involved and is thus split into to distinct functions, one for increasing the brightness and another for decreasing it: | ||
+ | |||
+ | <code c++>void AE:: | ||
+ | { | ||
+ | switch(m_vars.actionRH){ | ||
+ | case Action:: | ||
+ | case Action:: | ||
+ | } | ||
+ | }</ | ||
+ | |||
+ | ===== AE:: | ||
+ | |||
+ | Increasing the brightness of the image is a 3-step process where first we max out the '' | ||
+ | - We have determined that to reach the target luminosity we need to increase the brightness at least 4-fold | ||
+ | - AWB is **on**, since the color representation is going to be severely affected by this change | ||
+ | - We have sufficient buffer for Red and Blue channels' | ||
+ | |||
+ | <code c++>void AE:: | ||
+ | { | ||
+ | const auto limitExp = m_ov.getLimit(Val:: | ||
+ | const int setExp = m_ov[Val:: | ||
+ | if(setExp < limitExp){ | ||
+ | m_ov[Val:: | ||
+ | m_vars.op = CorrOp:: | ||
+ | return; | ||
+ | } | ||
+ | | ||
+ | const auto limitGg = m_ov.getLimit(Val:: | ||
+ | const int setGg = m_ov[Val:: | ||
+ | if(setGg < limitGg){ | ||
+ | m_ov[Val:: | ||
+ | m_vars.op = CorrOp:: | ||
+ | return; | ||
+ | } | ||
+ | | ||
+ | const auto limitRgb = m_ov.getLimit(Val:: | ||
+ | const int setGreen = m_ov[Val:: | ||
+ | if(m_vars.adjY > 4 && setGreen < limitRgb * 0.95){ | ||
+ | const auto safetyBuffer = 0.95; | ||
+ | const auto limitRGain = 1180; | ||
+ | const int highestRgb = max(m_ov[Val:: | ||
+ | const double topMultiplier = std:: | ||
+ | const auto gainG = static_cast< | ||
+ | if(gainG > setGreen){ | ||
+ | m_ov[Val:: | ||
+ | m_vars.op = CorrOp:: | ||
+ | return; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | 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 '' | ||
+ | | '' | ||
+ | | '' | ||
+ | | '' | ||
+ | | " |