Generalize perspective correction

Revise perspective transformation to remove hard-coded angular field of
view and horizontal perspective axis of rotation. Add vertical bias
parameter to retain ability to perform vertical perspective
transformation independent of the horizontal perspective axis of
rotation. Add field of view parameter as a tentative method for
specifying angular field of view.

The current implementation of perspective transformation applies
horizontal perspective transformation in such a way that preserves the
orientation of a horizontal line going through the center of the image.
In common use cases, horizontal lines such as the horizon do not go
through the center of the image. In such cases, the horizontal
perspective axis of rotation should not be parallel to the image's
y-axis. This commit makes the axis of rotation dependent on the vertical
parameter.

The two axes of rotation should be placed at the appropriate distance
from the image in order to prevent stretched or compressed proportions.
In the current implementation, the axes are at a fixed relative distance
from the image. This commit adds the ability to specify the distance in
the form of the diagonal angular field of view.
This commit is contained in:
Lawrence
2019-12-18 10:22:05 -08:00
parent 3a207dace7
commit f4c37598ee
9 changed files with 145 additions and 53 deletions

View File

@@ -1803,9 +1803,11 @@ TP_PCVIGNETTE_ROUNDNESS_TOOLTIP;Roundness:\n0 = rectangle,\n50 = fitted ellipse,
TP_PCVIGNETTE_STRENGTH;Strength
TP_PCVIGNETTE_STRENGTH_TOOLTIP;Filter strength in stops (reached in corners).
TP_PDSHARPENING_LABEL;Capture Sharpening
TP_PERSPECTIVE_FOV;Field of view
TP_PERSPECTIVE_HORIZONTAL;Horizontal
TP_PERSPECTIVE_LABEL;Perspective
TP_PERSPECTIVE_VERTICAL;Vertical
TP_PERSPECTIVE_VERTICAL_BIAS;Vertical bias
TP_PFCURVE_CURVEEDITOR_CH;Hue
TP_PFCURVE_CURVEEDITOR_CH_TOOLTIP;Controls defringe strength by color.\nHigher = more,\nLower = less.
TP_PREPROCESS_DEADPIXFILT;Dead pixel filter

View File

@@ -236,20 +236,38 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector<Coord2D> &src,
double cost = cos (params->rotate.degree * rtengine::RT_PI / 180.0);
double sint = sin (params->rotate.degree * rtengine::RT_PI / 180.0);
// auxiliary variables for vertical perspective correction
double vpdeg = params->perspective.vertical / 100.0 * 45.0;
double vpalpha = (90.0 - vpdeg) / 180.0 * rtengine::RT_PI;
double vpteta = fabs (vpalpha - rtengine::RT_PI / 2) < 3e-4 ? 0.0 : acos ((vpdeg > 0 ? 1.0 : -1.0) * sqrt ((-oW * oW * tan (vpalpha) * tan (vpalpha) + (vpdeg > 0 ? 1.0 : -1.0) * oW * tan (vpalpha) * sqrt (16 * maxRadius * maxRadius + oW * oW * tan (vpalpha) * tan (vpalpha))) / (maxRadius * maxRadius * 8)));
double vpcospt = (vpdeg >= 0 ? 1.0 : -1.0) * cos (vpteta), vptanpt = tan (vpteta);
// auxiliary variables for horizontal perspective correction
double hpdeg = params->perspective.horizontal / 100.0 * 45.0;
double hpalpha = (90.0 - hpdeg) / 180.0 * rtengine::RT_PI;
double hpteta = fabs (hpalpha - rtengine::RT_PI / 2) < 3e-4 ? 0.0 : acos ((hpdeg > 0 ? 1.0 : -1.0) * sqrt ((-oH * oH * tan (hpalpha) * tan (hpalpha) + (hpdeg > 0 ? 1.0 : -1.0) * oH * tan (hpalpha) * sqrt (16 * maxRadius * maxRadius + oH * oH * tan (hpalpha) * tan (hpalpha))) / (maxRadius * maxRadius * 8)));
double hpcospt = (hpdeg >= 0 ? 1.0 : -1.0) * cos (hpteta), hptanpt = tan (hpteta);
double ascale = ascaleDef > 0 ? ascaleDef : (params->commonTrans.autofill ? getTransformAutoFill (oW, oH, pLCPMap) : 1.0);
// auxiliary variables for perspective correction
const double f = maxRadius / tan(params->perspective.fov / 360.0 * rtengine::RT_PI);
const double phtheta = params->perspective.horizontal / -180.0 * rtengine::RT_PI;
const double pvtheta = params->perspective.vertical / -180.0 * rtengine::RT_PI;
const double pbtheta = params->perspective.vBias / -180.0 * rtengine::RT_PI;
const double phcos = cos(phtheta);
const double pvcos = cos(pvtheta);
const double pbcos = cos(pbtheta);
const double phsin = sin(phtheta);
const double pvsin = sin(pvtheta);
const double pbsin = sin(pbtheta);
// Coordinates of distorted image center.
const double pxoffset = f * phsin * pvcos;
const double pyoffset = -f * (phcos * pvcos * pbsin + pvsin * pbcos);
const double pz = f * (phcos * pvcos * pbcos - pvsin * pbsin);
// Inverse transformation matrix.
const double p_xx = f * phcos;
const double p_xy = f * phsin * pbsin;
const double p_xz = f * phsin * pbcos;
const double p_yx = f * phsin * pvsin;
const double p_yy = f * (pvcos * pbcos - phcos * pvsin * pbsin);
const double p_yz = f * (-pvcos * pbsin - phcos * pvsin * pbcos);
const double p_zx = -phsin * pvcos;
const double p_zy = phcos * pvcos * pbsin + pvsin * pbcos;
const double p_zz = phcos * pvcos * pbcos - pvsin * pbsin;
// z is known, can calculate these in advance.
const double pz_xz = pz * p_xz;
const double pz_yz = pz * p_yz;
const double pz_zz = pz * p_zz;
for (size_t i = 0; i < src.size(); i++) {
double x_d = src[i].x, y_d = src[i].y;
@@ -264,13 +282,13 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector<Coord2D> &src,
y_d += ascale * (0 - h2); // centering y coord & scale
if (needsPerspective()) {
// horizontal perspective transformation
y_d *= maxRadius / (maxRadius + x_d * hptanpt);
x_d *= maxRadius * hpcospt / (maxRadius + x_d * hptanpt);
// vertical perspective transformation
x_d *= maxRadius / (maxRadius - y_d * vptanpt);
y_d *= maxRadius * vpcospt / (maxRadius - y_d * vptanpt);
x_d -= pxoffset;
y_d -= pyoffset;
const double normalizer = p_zx * x_d + p_zy * y_d + pz_zz;
const double x_d_new = p_xx * x_d + p_xy * y_d + pz_xz;
y_d = p_yx * x_d + p_yy * y_d + pz_yz;
x_d = x_d_new / normalizer;
y_d /= normalizer;
}
// rotate
@@ -903,28 +921,42 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I
const double cost = cos(params->rotate.degree * rtengine::RT_PI / 180.0);
const double sint = sin(params->rotate.degree * rtengine::RT_PI / 180.0);
// auxiliary variables for vertical perspective correction
const double vpdeg = params->perspective.vertical / 100.0 * 45.0;
const double vpalpha = (90.0 - vpdeg) / 180.0 * rtengine::RT_PI;
const double vpteta = fabs(vpalpha - rtengine::RT_PI / 2) < 3e-4 ? 0.0 : acos((vpdeg > 0 ? 1.0 : -1.0) * sqrt((-SQR(oW * tan(vpalpha)) + (vpdeg > 0 ? 1.0 : -1.0) *
oW * tan(vpalpha) * sqrt(SQR(4 * maxRadius) + SQR(oW * tan(vpalpha)))) / (SQR(maxRadius) * 8)));
const double vpcospt = (vpdeg >= 0 ? 1.0 : -1.0) * cos(vpteta);
const double vptanpt = tan(vpteta);
// auxiliary variables for horizontal perspective correction
const double hpdeg = params->perspective.horizontal / 100.0 * 45.0;
const double hpalpha = (90.0 - hpdeg) / 180.0 * rtengine::RT_PI;
const double hpteta = fabs(hpalpha - rtengine::RT_PI / 2) < 3e-4 ? 0.0 : acos((hpdeg > 0 ? 1.0 : -1.0) * sqrt((-SQR(oH * tan(hpalpha)) + (hpdeg > 0 ? 1.0 : -1.0) *
oH * tan(hpalpha) * sqrt(SQR(4 * maxRadius) + SQR(oH * tan(hpalpha)))) / (SQR(maxRadius) * 8)));
const double hpcospt = (hpdeg >= 0 ? 1.0 : -1.0) * cos(hpteta);
const double hptanpt = tan(hpteta);
const double ascale = params->commonTrans.autofill ? getTransformAutoFill(oW, oH, pLCPMap) : 1.0;
const bool darkening = (params->vignetting.amount <= 0.0);
const double centerFactorx = cx - w2;
const double centerFactory = cy - h2;
// auxiliary variables for perspective correction
const double f = maxRadius / tan(params->perspective.fov / 360.0 * rtengine::RT_PI);
const double phtheta = params->perspective.horizontal / -180.0 * rtengine::RT_PI;
const double pvtheta = params->perspective.vertical / -180.0 * rtengine::RT_PI;
const double pbtheta = params->perspective.vBias / -180.0 * rtengine::RT_PI;
const double phcos = cos(phtheta);
const double pvcos = cos(pvtheta);
const double pbcos = cos(pbtheta);
const double phsin = sin(phtheta);
const double pvsin = sin(pvtheta);
const double pbsin = sin(pbtheta);
// Coordinates of distorted image center.
const double pxoffset = f * phsin * pvcos;
const double pyoffset = -f * (phcos * pvcos * pbsin + pvsin * pbcos);
const double pz = f * (phcos * pvcos * pbcos - pvsin * pbsin);
// Inverse transformation matrix.
const double p_xx = f * phcos;
const double p_xy = f * phsin * pbsin;
const double p_xz = f * phsin * pbcos;
const double p_yx = f * phsin * pvsin;
const double p_yy = f * (pvcos * pbcos - phcos * pvsin * pbsin);
const double p_yz = f * (-pvcos * pbsin - phcos * pvsin * pbcos);
const double p_zx = -phsin * pvcos;
const double p_zy = phcos * pvcos * pbsin + pvsin * pbcos;
const double p_zz = phcos * pvcos * pbcos - pvsin * pbsin;
// z is known, can calculate these in advance.
const double pz_xz = pz * p_xz;
const double pz_yz = pz * p_yz;
const double pz_zz = pz * p_zz;
// main cycle
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic, 16) if(multiThread)
@@ -946,13 +978,13 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I
y_d += ascale * centerFactory; // centering y coord & scale
if (enablePerspective) {
// horizontal perspective transformation
y_d *= maxRadius / (maxRadius + x_d * hptanpt);
x_d *= maxRadius * hpcospt / (maxRadius + x_d * hptanpt);
// vertical perspective transformation
x_d *= maxRadius / (maxRadius - y_d * vptanpt);
y_d *= maxRadius * vpcospt / (maxRadius - y_d * vptanpt);
x_d -= pxoffset;
y_d -= pyoffset;
const double normalizer = p_zx * x_d + p_zy * y_d + pz_zz;
const double x_d_new = p_xx * x_d + p_xy * y_d + pz_xz;
y_d = p_yx * x_d + p_yy * y_d + pz_yz;
x_d = x_d_new / normalizer;
y_d /= normalizer;
}
// rotate
@@ -1150,7 +1182,7 @@ bool ImProcFunctions::needsRotation () const
bool ImProcFunctions::needsPerspective () const
{
return params->perspective.horizontal || params->perspective.vertical;
return params->perspective.horizontal || params->perspective.vertical || params->perspective.vBias;
}
bool ImProcFunctions::needsGradient () const

View File

@@ -1851,7 +1851,9 @@ LensProfParams::LcMode LensProfParams::getMethodNumber(const Glib::ustring& mode
PerspectiveParams::PerspectiveParams() :
horizontal(0.0),
vertical(0.0)
vertical(0.0),
vBias(0.0),
fov(65.0)
{
}
@@ -1859,7 +1861,9 @@ bool PerspectiveParams::operator ==(const PerspectiveParams& other) const
{
return
horizontal == other.horizontal
&& vertical == other.vertical;
&& vertical == other.vertical
&& vBias == other.vBias
&& fov == other.fov;
}
bool PerspectiveParams::operator !=(const PerspectiveParams& other) const
@@ -3343,6 +3347,8 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo
// Perspective correction
saveToKeyfile(!pedited || pedited->perspective.horizontal, "Perspective", "Horizontal", perspective.horizontal, keyFile);
saveToKeyfile(!pedited || pedited->perspective.vertical, "Perspective", "Vertical", perspective.vertical, keyFile);
saveToKeyfile(!pedited || pedited->perspective.vBias, "Perspective", "VerticalBias", perspective.vBias, keyFile);
saveToKeyfile(!pedited || pedited->perspective.fov, "Perspective", "FOV", perspective.fov, keyFile);
// Gradient
saveToKeyfile(!pedited || pedited->gradient.enabled, "Gradient", "Enabled", gradient.enabled, keyFile);
@@ -4420,6 +4426,8 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited)
if (keyFile.has_group("Perspective")) {
assignFromKeyfile(keyFile, "Perspective", "Horizontal", pedited, perspective.horizontal, pedited->perspective.horizontal);
assignFromKeyfile(keyFile, "Perspective", "Vertical", pedited, perspective.vertical, pedited->perspective.vertical);
assignFromKeyfile(keyFile, "Perspective", "VerticalBias", pedited, perspective.vBias, pedited->perspective.vBias);
assignFromKeyfile(keyFile, "Perspective", "FOV", pedited, perspective.fov, pedited->perspective.fov);
}
if (keyFile.has_group("Gradient")) {

View File

@@ -907,6 +907,8 @@ struct LensProfParams {
struct PerspectiveParams {
double horizontal;
double vertical;
double vBias;
double fov;
PerspectiveParams();

View File

@@ -336,6 +336,8 @@ void ParamsEdited::set(bool v)
lensProf.lfLens = v;
perspective.horizontal = v;
perspective.vertical = v;
perspective.vBias = v;
perspective.fov = v;
gradient.enabled = v;
gradient.degree = v;
gradient.feather = v;
@@ -919,6 +921,8 @@ void ParamsEdited::initFrom(const std::vector<rtengine::procparams::ProcParams>&
lensProf.lfLens = lensProf.lfLens && p.lensProf.lfLens == other.lensProf.lfLens;
perspective.horizontal = perspective.horizontal && p.perspective.horizontal == other.perspective.horizontal;
perspective.vertical = perspective.vertical && p.perspective.vertical == other.perspective.vertical;
perspective.vBias = perspective.vBias && p.perspective.vBias == other.perspective.vBias;
perspective.fov = perspective.fov && p.perspective.fov == other.perspective.fov;
gradient.enabled = gradient.enabled && p.gradient.enabled == other.gradient.enabled;
gradient.degree = gradient.degree && p.gradient.degree == other.gradient.degree;
gradient.feather = gradient.feather && p.gradient.feather == other.gradient.feather;
@@ -2317,6 +2321,14 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng
toEdit.perspective.vertical = dontforceSet && options.baBehav[ADDSET_PERSPECTIVE] ? toEdit.perspective.vertical + mods.perspective.vertical : mods.perspective.vertical;
}
if (perspective.vBias) {
toEdit.perspective.vBias = dontforceSet && options.baBehav[ADDSET_PERSPECTIVE] ? toEdit.perspective.vBias + mods.perspective.vBias : mods.perspective.vBias;
}
if (perspective.fov) {
toEdit.perspective.fov = dontforceSet && options.baBehav[ADDSET_PERSPECTIVE] ? toEdit.perspective.fov + mods.perspective.fov : mods.perspective.fov;
}
if (gradient.enabled) {
toEdit.gradient.enabled = mods.gradient.enabled;
}

View File

@@ -396,6 +396,8 @@ struct LensProfParamsEdited {
struct PerspectiveParamsEdited {
bool horizontal;
bool vertical;
bool vBias;
bool fov;
};
struct GradientParamsEdited {

View File

@@ -32,18 +32,30 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M("
Gtk::Image* ipersHR = Gtk::manage (new RTImage ("perspective-horizontal-right-small.png"));
Gtk::Image* ipersVL = Gtk::manage (new RTImage ("perspective-vertical-bottom-small.png"));
Gtk::Image* ipersVR = Gtk::manage (new RTImage ("perspective-vertical-top-small.png"));
Gtk::Image* ipersBL = Gtk::manage (new RTImage ("perspective-vertical-bottom-small.png"));
Gtk::Image* ipersBR = Gtk::manage (new RTImage ("perspective-vertical-top-small.png"));
horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_HORIZONTAL"), -100, 100, 0.1, 0, ipersHL, ipersHR));
horiz->setAdjusterListener (this);
vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL"), -100, 100, 0.1, 0, ipersVL, ipersVR));
vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL"), -85, 85, 0.1, 0, ipersVL, ipersVR));
vert->setAdjusterListener (this);
pack_start (*horiz);
horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_HORIZONTAL"), -85, 85, 0.1, 0, ipersHL, ipersHR));
horiz->setAdjusterListener (this);
vBias = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL_BIAS"), -85, 85, 0.1, 0, ipersBL, ipersBR));
vBias->setAdjusterListener (this);
fov = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_FOV"), 0.1, 150, 0.1, 65));
fov->setAdjusterListener (this);
pack_start (*vert);
pack_start (*horiz);
pack_start (*vBias);
pack_start (*fov);
horiz->setLogScale(2, 0);
vert->setLogScale(2, 0);
vBias->setLogScale(2, 0);
fov->setLogScale(2, 0);
show_all();
}
@@ -56,10 +68,14 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited)
if (pedited) {
horiz->setEditedState (pedited->perspective.horizontal ? Edited : UnEdited);
vert->setEditedState (pedited->perspective.vertical ? Edited : UnEdited);
vBias->setEditedState (pedited->perspective.vBias ? Edited : UnEdited);
fov->setEditedState (pedited->perspective.fov ? Edited : UnEdited);
}
horiz->setValue (pp->perspective.horizontal);
vert->setValue (pp->perspective.vertical);
vBias->setValue (pp->perspective.vBias);
fov->setValue (pp->perspective.fov);
enableListener ();
}
@@ -69,10 +85,14 @@ void PerspCorrection::write (ProcParams* pp, ParamsEdited* pedited)
pp->perspective.horizontal = horiz->getValue ();
pp->perspective.vertical = vert->getValue ();
pp->perspective.vBias = vBias->getValue ();
pp->perspective.fov = fov->getValue ();
if (pedited) {
pedited->perspective.horizontal = horiz->getEditedState ();
pedited->perspective.vertical = vert->getEditedState ();
pedited->perspective.vBias = vBias->getEditedState ();
pedited->perspective.fov = fov->getEditedState ();
}
}
@@ -81,20 +101,26 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit
horiz->setDefault (defParams->perspective.horizontal);
vert->setDefault (defParams->perspective.vertical);
vBias->setDefault (defParams->perspective.vBias);
fov->setDefault (defParams->perspective.fov);
if (pedited) {
horiz->setDefaultEditedState (pedited->perspective.horizontal ? Edited : UnEdited);
vert->setDefaultEditedState (pedited->perspective.vertical ? Edited : UnEdited);
vBias->setDefaultEditedState (pedited->perspective.vBias ? Edited : UnEdited);
fov->setDefaultEditedState (pedited->perspective.fov ? Edited : UnEdited);
} else {
horiz->setDefaultEditedState (Irrelevant);
vert->setDefaultEditedState (Irrelevant);
vBias->setDefaultEditedState (Irrelevant);
fov->setDefaultEditedState (Irrelevant);
}
}
void PerspCorrection::adjusterChanged(Adjuster* a, double newval)
{
if (listener) {
listener->panelChanged (EvPerspCorr, Glib::ustring::compose ("%1=%3\n%2=%4", M("TP_PERSPECTIVE_HORIZONTAL"), M("TP_PERSPECTIVE_VERTICAL"), horiz->getValue(), vert->getValue()));
listener->panelChanged (EvPerspCorr, Glib::ustring::compose ("%1=%5\n%2=%6\n%3=%7\n%4=%8", M("TP_PERSPECTIVE_HORIZONTAL"), M("TP_PERSPECTIVE_VERTICAL"), M("TP_PERSPECTIVE_VERTICAL_BIAS"), M("TP_PERSPECTIVE_FOV"), horiz->getValue(), vert->getValue(), vBias->getValue(), fov->getValue()));
}
}
@@ -103,6 +129,8 @@ void PerspCorrection::setAdjusterBehavior (bool badd)
horiz->setAddMode(badd);
vert->setAddMode(badd);
vBias->setAddMode(badd);
fov->setAddMode(badd);
}
void PerspCorrection::trimValues (rtengine::procparams::ProcParams* pp)
@@ -110,6 +138,8 @@ void PerspCorrection::trimValues (rtengine::procparams::ProcParams* pp)
horiz->trimValue(pp->perspective.horizontal);
vert->trimValue(pp->perspective.vertical);
vBias->trimValue(pp->perspective.vBias);
fov->trimValue(pp->perspective.fov);
}
void PerspCorrection::setBatchMode (bool batchMode)
@@ -118,4 +148,6 @@ void PerspCorrection::setBatchMode (bool batchMode)
ToolPanel::setBatchMode (batchMode);
horiz->showEditedCB ();
vert->showEditedCB ();
vBias->showEditedCB ();
fov->showEditedCB ();
}

View File

@@ -32,6 +32,8 @@ class PerspCorrection final :
protected:
Adjuster* horiz;
Adjuster* vert;
Adjuster* vBias;
Adjuster* fov;
public:

View File

@@ -343,7 +343,7 @@ Gtk::Widget* Preferences::getBatchProcPanel ()
mi = behModel->append ();
mi->set_value (behavColumns.label, M ("TP_PERSPECTIVE_LABEL"));
appendBehavList (mi, M ("TP_PERSPECTIVE_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_VERTICAL"), ADDSET_PERSPECTIVE, false);
appendBehavList (mi, M ("TP_PERSPECTIVE_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_VERTICAL") + ", " + M ("TP_PERSPECTIVE_VERTICAL_BIAS") + ", " + M ("TP_PERSPECTIVE_FOV"), ADDSET_PERSPECTIVE, false);
mi = behModel->append ();
mi->set_value (behavColumns.label, M ("TP_GRADIENT_LABEL"));