source/isp/CorrectVignetting.cpp (141 lines of code) (raw):
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "source/util/CvUtil.h"
#include "source/util/SystemUtil.h"
using namespace fb360_dep;
const std::string kUsageMessage = R"(
- Correct vignetting in a single image.
- Example:
./CorrectVignetting \
--out=/path/to/output/image \
--principal_x=1680 \
--principal_y=1080 \
--raw=/path/to/raw/image
--vignetting_x="1.5,1.0,1.0,1.0,1.0,1.5" \
--vignetting_y="1.5,1.0,1.0,1.0,1.0,1.5"
)";
DEFINE_string(out, "", "path to output image");
DEFINE_double(principal_x, -1, "principal x-coord (< 0 = width / 2)");
DEFINE_double(principal_y, -1, "principal y-coord (< 0 = height / 2)");
DEFINE_string(raw, "", "path to raw image");
DEFINE_string(vignetting_x, "", "x-axis comma-separated vignetting values");
DEFINE_string(vignetting_y, "", "y-axis comma-separated vignetting values");
float* vignettingTableX = 0;
float* vignettingTableY = 0;
template <typename T>
inline T clamp(const T& x, const T& a, const T& b) {
return x < a ? a : x > b ? b : x;
}
template <typename V, typename T>
inline V lerp(const V x0, const V x1, const T alpha) {
return x0 * (T(1) - alpha) + x1 * alpha;
}
template <typename T>
inline T lerp(const T x0, const T x1, const T alpha) {
return x0 * (T(1) - alpha) + x1 * alpha;
}
template <typename T, typename V>
class BezierCurve {
protected:
std::vector<V> points_;
public:
BezierCurve() {}
BezierCurve(std::vector<V> points) {
for (auto p : points) {
points_.push_back(p);
}
}
void addPoint(const V p) {
points_.push_back(p);
}
void clearPoints() {
points_.clear();
}
inline V operator()(const int i, const int j, const T t) const {
return (i == j) ? points_[i] : lerp((*this)(i, j - 1, t), (*this)(i + 1, j, t), t);
}
inline V operator()(const T t) const {
return (*this)(0, points_.size() - 1, t);
}
};
void initGflags(int& argc, char**& argv) {
static const bool kRemoveFlags = true;
gflags::ParseCommandLineNonHelpFlags(&argc, &argv, kRemoveFlags);
gflags::HandleCommandLineHelpFlags();
}
std::vector<float> splitString(const std::string& csv) {
std::vector<float> values;
std::stringstream ss(csv);
float val;
while (ss >> val) {
values.push_back(val);
if (ss.peek() == ',') {
ss.ignore();
}
}
return values;
}
void getBezierCenterShift(int& bezierShiftX, int& bezierShiftY, const int width, const int height) {
int principalX = FLAGS_principal_x;
int principalY = FLAGS_principal_y;
if (principalX < 0) {
principalX = width / 2;
}
if (principalY < 0) {
principalY = height / 2;
}
CHECK_LT(principalX, width) << "principal_x out of bounds";
CHECK_LT(principalY, height) << "principal_y out of bounds";
bezierShiftX = principalX - width / 2;
bezierShiftY = principalY - height / 2;
}
void buildVignettingTables(const int width, const int height) {
LOG(INFO) << "Pre-computing vignetting tables...";
// NOTE: These tables only need to be computed once, and can then be applied
// to all input images
vignettingTableX = new float[width];
vignettingTableY = new float[height];
// Build Bezier X and Y curves
// NOTE: The more anchor points the Bezier curve has the slower it will be,
// since it has to interpolate between anchor points
BezierCurve<float, float> vignetteCurveX(splitString(FLAGS_vignetting_x));
BezierCurve<float, float> vignetteCurveY(splitString(FLAGS_vignetting_y));
// Bezier template is circular and centered at the center of the image, so we
// need to shift smallest dimension by (max dimension - min dimension) / 2
int dX = 0;
int dY = 0;
int maxDimension;
if (width > height) {
dY = (width - height) / 2;
maxDimension = width;
} else {
dX = (height - width) / 2;
maxDimension = height;
}
for (int y = 0; y < height; ++y) {
vignettingTableY[y] = vignetteCurveY((y + dY) / float(maxDimension));
for (int x = 0; x < width; ++x) {
vignettingTableX[x] = vignetteCurveX((x + dX) / float(maxDimension));
}
}
}
// Load raw image
// NOTE: loading grayscale to simulate one of the color channels
// NOTE: the exact same process would be applied to all the channels (same
// vignetting curve)
cv::Mat_<float> loadImage(const std::string& fn) {
cv::Mat raw = cv::imread(fn, cv::IMREAD_GRAYSCALE | cv::IMREAD_ANYDEPTH);
CHECK(!raw.empty()) << "Failed to load image";
// Convert 16-bit to 32-bit floating point in range [0..1]
cv::Mat_<float> output;
raw.convertTo(output, CV_32F, 1.0f / 65535.0f);
return output;
}
int main(int argc, char** argv) {
system_util::initDep(argc, argv, kUsageMessage);
cv::Mat_<float> image = loadImage(FLAGS_raw);
const int width = image.cols;
const int height = image.rows;
// Pre-compute vignetting tables
buildVignettingTables(width, height);
// Center of Bezier is shifted by the distance of the principal to the image
// center
int bezierShiftX;
int bezierShiftY;
getBezierCenterShift(bezierShiftX, bezierShiftY, width, height);
// Apply vignetting correction
LOG(INFO) << "Applying vignetting correction...";
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
const int xx = clamp(x + bezierShiftX, 0, width - 1);
const int yy = clamp(y + bezierShiftY, 0, height - 1);
image(y, x) *= vignettingTableX[xx] * vignettingTableY[yy];
}
}
// Save corrected image
cv::imwrite(FLAGS_out, 255.0f * image);
return EXIT_SUCCESS;
}