Files
rawTherapee/rtengine/perspectivecorrection.cc
Lawrence Lee 9a40c14858 Make proper use of minimum control line count
When using control lines for perspective correction, set the number of
vertical and horizontal lines to the proper values instead of
hard-coding them to 4. The minimum line count is set to 2 when using
control lines, and defaults to 4 when using fully-automatic correction.
2020-06-23 21:57:10 -07:00

410 lines
12 KiB
C++

/* -*- C++ -*-
*
* This file is part of RawTherapee.
*
* Copyright (c) 2019 Alberto Griggio <alberto.griggio@gmail.com>
*
* RawTherapee is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RawTherapee is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RawTherapee. If not, see <http://www.gnu.org/licenses/>.
*/
// taken from darktable (src/iop/ashift.c)
/*
This file is part of darktable,
copyright (c) 2016 Ulrich Pegelow.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see <http://www.gnu.org/licenses/>.
*/
// Inspiration to this module comes from the program ShiftN (http://www.shiftn.de) by
// Marcus Hebel.
// Thanks to Marcus for his support when implementing part of the ShiftN functionality
// to darktable.
#include "perspectivecorrection.h"
#include "improcfun.h"
#include "procparams.h"
#include "rt_math.h"
#include <string.h>
#include <math.h>
#include <assert.h>
#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "../rtgui/threadutils.h"
#include "colortemp.h"
#include "imagefloat.h"
#include "settings.h"
namespace rtengine { extern const Settings *settings; }
#define _(msg) (msg)
#define dt_control_log(msg) \
if (settings->verbose) { \
printf("%s\n", msg); \
fflush(stdout); \
}
namespace rtengine {
namespace {
inline int mat3inv(float *const dst, const float *const src)
{
std::array<std::array<float, 3>, 3> tmpsrc;
std::array<std::array<float, 3>, 3> tmpdst;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
tmpsrc[i][j] = src[3 * i + j];
}
}
if (invertMatrix(tmpsrc, tmpdst)) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
dst[3 * i + j] = tmpdst[i][j];
}
}
return 0;
} else {
return 1;
}
}
// the darktable ashift iop (adapted to RT), which does most of the work
#include "ashift_dt.c"
} // namespace
namespace {
/*
std::vector<Coord2D> get_corners(int w, int h)
{
int x1 = 0, y1 = 0;
int x2 = w, y2 = h;
std::vector<Coord2D> corners = {
Coord2D(x1, y1),
Coord2D(x1, y2),
Coord2D(x2, y2),
Coord2D(x2, y1)
};
return corners;
}
*/
void init_dt_structures(dt_iop_ashift_params_t *p, dt_iop_ashift_gui_data_t *g,
const procparams::PerspectiveParams *params)
{
dt_iop_ashift_params_t dp = {
0.0f,
0.0f,
0.0f,
0.0f,
DEFAULT_F_LENGTH,
1.f,
0.0f,
1.0f,
ASHIFT_MODE_SPECIFIC,
0,
ASHIFT_CROP_OFF,
0.0f,
1.0f,
0.0f,
1.0f,
0.0f,
0.0f
};
*p = dp;
g->buf = NULL;
g->buf_width = 0;
g->buf_height = 0;
g->buf_x_off = 0;
g->buf_y_off = 0;
g->buf_scale = 1.0f;
g->buf_hash = 0;
g->isflipped = 0;
g->lastfit = ASHIFT_FIT_NONE;
g->fitting = 0;
g->lines = NULL;
g->lines_count =0;
g->horizontal_count = 0;
g->vertical_count = 0;
g->grid_hash = 0;
g->lines_hash = 0;
g->rotation_range = ROTATION_RANGE_SOFT;
g->lensshift_v_range = LENSSHIFT_RANGE_SOFT;
g->lensshift_h_range = LENSSHIFT_RANGE_SOFT;
g->shear_range = SHEAR_RANGE_SOFT;
g->camera_pitch_range = CAMERA_ANGLE_RANGE_SOFT;
g->camera_yaw_range = CAMERA_ANGLE_RANGE_SOFT;
g->lines_suppressed = 0;
g->lines_version = 0;
g->show_guides = 0;
g->isselecting = 0;
g->isdeselecting = 0;
g->isbounding = ASHIFT_BOUNDING_OFF;
g->near_delta = 0;
g->selecting_lines_version = 0;
g->points = NULL;
g->points_idx = NULL;
g->points_lines_count = 0;
g->points_version = 0;
g->jobcode = ASHIFT_JOBCODE_NONE;
g->jobparams = 0;
g->adjust_crop = FALSE;
g->lastx = g->lasty = -1.0f;
g->crop_cx = g->crop_cy = 1.0f;
if (params) {
p->rotation = params->camera_roll;
p->lensshift_v = params->camera_shift_vert;
p->lensshift_h = params->camera_shift_horiz;
p->f_length = params->camera_focal_length;
p->crop_factor = params->camera_crop_factor;
p->camera_pitch = params->camera_pitch;
p->camera_yaw = params->camera_yaw;
}
}
/*
void get_view_size(int w, int h, const procparams::PerspectiveParams &params, double &cw, double &ch)
{
double min_x = RT_INFINITY, max_x = -RT_INFINITY;
double min_y = RT_INFINITY, max_y = -RT_INFINITY;
auto corners = get_corners(w, h);
float homo[3][3];
homography((float *)homo, params.angle, params.vertical / 100.0, -params.horizontal / 100.0, params.shear / 100.0, params.flength * params.cropfactor, 100.f, params.aspect, w, h, ASHIFT_HOMOGRAPH_FORWARD);
for (auto &c : corners) {
float pin[3] = { float(c.x), float(c.y), 1.f };
float pout[3];
mat3mulv(pout, (float *)homo, pin);
double x = pout[0] / pout[2];
double y = pout[1] / pout[2];
min_x = min(min_x, x);
max_x = max(max_x, x);
min_y = min(min_y, y);
max_y = max(max_y, y);
}
cw = max_x - min_x;
ch = max_y - min_y;
}
*/
/**
* Allocates a new array and populates it with ashift lines corresponding to the
* provided control lines.
*/
dt_iop_ashift_line_t* toAshiftLines(const ControlLine *lines, size_t count)
{
auto retval = (dt_iop_ashift_line_t*)malloc(count * sizeof(dt_iop_ashift_line_t));
for (size_t i = 0; i < count; i++) {
const float x1 = lines[i].x1;
const float y1 = lines[i].y1;
const float x2 = lines[i].x2;
const float y2 = lines[i].y2;
retval[i].p1[0] = x1;
retval[i].p1[1] = y1;
retval[i].p1[2] = 1.0f;
retval[i].p2[0] = x2;
retval[i].p2[1] = y2;
retval[i].p2[2] = 1.0f;
retval[i].length = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
retval[i].width = 1.0f;
retval[i].weight = retval[i].length;
if (lines[i].type == ControlLine::HORIZONTAL) {
retval[i].type = ASHIFT_LINE_HORIZONTAL_SELECTED;
} else if (lines[i].type == ControlLine::VERTICAL) {
retval[i].type = ASHIFT_LINE_VERTICAL_SELECTED;
} else {
retval[i].type = ASHIFT_LINE_IRRELEVANT;
}
}
return retval;
}
} // namespace
PerspectiveCorrection::Params PerspectiveCorrection::autocompute(ImageSource *src, bool corr_pitch, bool corr_yaw, const procparams::ProcParams *pparams, const FramesMetaData *metadata, const ControlLine *control_lines, size_t control_lines_count)
{
auto pcp = procparams::PerspectiveParams(pparams->perspective);
procparams::PerspectiveParams dflt;
/*
pcp.horizontal = dflt.horizontal;
pcp.vertical = dflt.vertical;
pcp.angle = dflt.angle;
pcp.shear = dflt.shear;
*/
pcp.camera_pitch = dflt.camera_pitch;
pcp.camera_roll = dflt.camera_roll;
pcp.camera_yaw = dflt.camera_yaw;
dt_iop_ashift_params_t p;
dt_iop_ashift_gui_data_t g;
init_dt_structures(&p, &g, &pparams->perspective);
dt_iop_module_t module;
module.gui_data = &g;
module.is_raw = src->isRAW();
int tr = getCoarseBitMask(pparams->coarse);
int fw, fh;
src->getFullSize(fw, fh, tr);
if (control_lines == nullptr) {
int skip = max(float(max(fw, fh)) / 900.f + 0.5f, 1.f);
PreviewProps pp(0, 0, fw, fh, skip);
int w, h;
src->getSize(pp, w, h);
std::unique_ptr<Imagefloat> img(new Imagefloat(w, h));
ProcParams neutral;
neutral.raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST);
neutral.raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST);
neutral.icm.outputProfile = ColorManagementParams::NoICMString;
src->getImage(src->getWB(), tr, img.get(), pp, neutral.toneCurve, neutral.raw);
src->convertColorSpace(img.get(), pparams->icm, src->getWB());
neutral.commonTrans.autofill = false; // Ensures crop factor is correct.
// TODO: Ensure image borders of rotated image do not get detected as lines.
neutral.rotate = pparams->rotate;
neutral.distortion = pparams->distortion;
neutral.lensProf = pparams->lensProf;
ImProcFunctions ipf(&neutral, true);
if (ipf.needsTransform(w, h, src->getRotateDegree(), src->getMetaData())) {
Imagefloat *tmp = new Imagefloat(w, h);
ipf.transform(img.get(), tmp, 0, 0, 0, 0, w, h, w, h,
src->getMetaData(), src->getRotateDegree(), false);
img.reset(tmp);
}
// allocate the gui buffer
g.buf = static_cast<float *>(malloc(sizeof(float) * w * h * 4));
g.buf_width = w;
g.buf_height = h;
img->normalizeFloatTo1();
#ifdef _OPENMP
# pragma omp parallel for
#endif
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int i = (y * w + x) * 4;
g.buf[i] = img->r(y, x);
g.buf[i+1] = img->g(y, x);
g.buf[i+2] = img->b(y, x);
g.buf[i+3] = 1.f;
}
}
}
dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE;
if (corr_pitch && corr_yaw) {
fitaxis = ASHIFT_FIT_BOTH_SHEAR;
} else if (corr_pitch) {
fitaxis = ASHIFT_FIT_VERTICALLY;
} else if (corr_yaw) {
fitaxis = ASHIFT_FIT_HORIZONTALLY;
}
// reset the pseudo-random seed for repeatability -- ashift_dt uses rand()
// internally!
srand(1);
bool res;
if (control_lines == nullptr) {
res = do_get_structure(&module, &p, ASHIFT_ENHANCE_EDGES) && do_fit(&module, &p, fitaxis);
} else {
dt_iop_ashift_gui_data_t *g = module.gui_data;
g->lines_count = control_lines_count;
g->lines = toAshiftLines(control_lines, control_lines_count);
g->lines_in_height = fh;
g->lines_in_width = fw;
update_lines_count(g->lines, g->lines_count, &(g->vertical_count), &(g->horizontal_count));
res = do_fit(&module, &p, fitaxis, 2);
}
Params retval = {
.angle = p.rotation,
.pitch = p.camera_pitch,
.yaw = p.camera_yaw
};
// cleanup the gui
if (g.lines) free(g.lines);
if (g.points) free(g.points);
if (g.points_idx) free(g.points_idx);
if (g.buf) free(g.buf);
if (!res) {
retval.angle = pparams->perspective.camera_roll;
retval.pitch = pparams->perspective.camera_pitch;
retval.yaw = pparams->perspective.camera_yaw;
}
return retval;
}
/*
void PerspectiveCorrection::autocrop(int width, int height, bool fixratio, const procparams::PerspectiveParams &params, const FramesMetaData *metadata, int &x, int &y, int &w, int &h)
{
auto pp = import_meta(params, metadata);
double cw, ch;
get_view_size(width, height, params, cw, ch);
double s = min(double(width)/cw, double(height)/ch);
dt_iop_ashift_params_t p;
dt_iop_ashift_gui_data_t g;
init_dt_structures(&p, &g, &pp);
dt_iop_module_t module = { &g, false };
g.buf_width = width;
g.buf_height = height;
p.cropmode = fixratio ? ASHIFT_CROP_ASPECT : ASHIFT_CROP_LARGEST;
do_crop(&module, &p);
cw *= s;
ch *= s;
double ox = p.cl * cw;
double oy = p.ct * ch;
x = ox - (cw - width)/2.0 + 0.5;
y = oy - (ch - height)/2.0 + 0.5;
w = (p.cr - p.cl) * cw;
h = (p.cb - p.ct) * ch;
}
*/
} // namespace rtengine