Ver Greeneyes wrote:
Hi
I noticed recently that my calibration + 3DLUT was causing clipping on very
bright colors, and I've been fiddling around with calibration since then.
I'm getting much better results now, and wanted to share my findings.
First a question: what is the reasoning behind the call to reset_targ_w()
on line 4536 (just before the verify & refine loop)?
Moving on, I've been having difficulty maintaining a good black level
during calibration, and so I decided to enable the black point hack and aim
for natural white. However, I noticed the resulting calibration gave me
clipping near white. I think this is because my display suffers both from
black crush and "white crush", where one or more channels stop getting
brighter well before their maximum value.
I think removing black crush and this "white crush" is one of the most
important roles of calibration, and I realized that by enabling the black
point hack and shooting for natural white, it actually bends the
calibration curve toward (0,0) and (1,1) and destroys its careful work
determining where black ends and white begins.
But disabling the black
point hack, the calibration raised my black levels again; and setting
chromaticity target coordinates for white, I still saw some clipping. I
think there are two reasons for this:
1) Test points are weighted perceptually.
However, it also means that near white, far fewer points are measured. This
is fine for most of the range, but it means dispcal doesn't do a very good
job of eliminating white crush.
2) After each pass, a curve is fitted to the measurements to ensure
monotonicity and smoothness. However, this smoothing can significantly
change the values near black and white, where the curve is most non-linear.
This undermines the careful measurements near black and can significantly
raise the black point (or lower it, presumably, reintroducing black crush).
I'd like to suggest the following changes to address these problems. I've
implemented them locally, and they've significantly improved my results:
1) Weight points in a sigmoidal way, emphasizing points near black *and*
near white. Actual sigmoidal functions don't naturally go through 0 or 1,
so instead I grafted two power functions together. In pseudo-code:
if (x < ½)
y = 2^(p-1) * x^p
else
y = 1 - 2^(p-1) * (1-x)^p
Here x is the input, y is the output, and p is the power used to weight the
points (such as REFN_DIST_POW). A possible C implementation:
#define DEFINE_APPLY(POW)\
static double APPLY_##POW(const double v) {\
if (v < 0.5)\
return exp2(POW - 1.0) * pow(v, POW);\
return 1.0 - exp2(POW - 1.0) * pow(1.0 - v, POW);\
}
DEFINE_APPLY(MOD_DIST_POW)
DEFINE_APPLY(REFN_DIST_POW)
DEFINE_APPLY(CHECK_DIST_POW)
#undef DEFINE_APPLY
2) Don't smooth the curve after the final pass (e.g. pass 0.0 instead of
RDAC_SMOOTH), or apply the weighting used for x.nat != 0 and x.bkhack != 0
after the final pass. I opted for the former, as I'm pretty confident in
the measurements and I haven't seen any evidence of non-monotonicity. This
solved my black point woes.
With those two changes, I get good calibration results on all my monitors.
In addition, since I'm using the madVR TPG, I added a pass to 'quantize'
the measurements into the 8-bit values that my GPU will actually apply
(since I'm using the calibration for more than just madVR), to avoid
possible rounding issues.
It simply measures the 8 possible ways to round each value (2 ways per
color channel) and chooses the one with the lowest hde. It's a fairly long
process, since it measures 2048 patches, and I don't think it makes much of
a difference if any, but I thought I'd mention it. If this is worth doing,
it probably makes more sense to limit it to values near black and near
white.
In any case, those were my findings. I realize there are trade-offs to
consider here, so I don't know if these changes can be integrated into
dispcal, but they worked well for me :)