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:36] – [Helper one-liners] Igor Yefmov | isp: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& | 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 27: | Line 27: | ||
int _triggeredRight(const ImgStats& | 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 103: | Line 130: | ||
A bulk of (quite simplistic) calculations is performed with data in '' | 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 242: | Line 269: | ||
<code c++>void AE:: | <code c++>void AE:: | ||
{ | { | ||
- | 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< | const auto nextGreen = static_cast< | ||
assert(nextGreen >= 1024); | assert(nextGreen >= 1024); | ||
- | m_ov[Val::GAIN_G] = nextGreen; | + | m_ov[Val::gain_g] = nextGreen; |
m_vars.op = CorrOp:: | m_vars.op = CorrOp:: | ||
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< | + | m_ov[Val::gain_global] = static_cast< |
m_vars.op = CorrOp:: | m_vars.op = CorrOp:: | ||
}else{ | }else{ | ||
- | m_ov[Val::EXPOSURE] *= m_vars.adjY; | + | m_ov[Val::exposure] *= m_vars.adjY; |
m_vars.op = CorrOp:: | m_vars.op = CorrOp:: | ||
- | if(m_ov[Val:: | + | if(m_ov[Val:: |
- | m_ov[Val::EXPOSURE] = 10; | + | m_ov[Val::exposure] = 10; |
m_vars.op = CorrOp:: | m_vars.op = CorrOp:: | ||
} | } | ||
Line 265: | Line 292: | ||
}</ | }</ | ||
+ | ====== 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 '' | ||
+ | | '' | ||
+ | | '' | ||
+ | | '' | ||
+ | | " |