Browse Source

Add lens correction with lensfun

Martijn Braam 8 months ago
parent
commit
621632a43f
9 changed files with 332 additions and 192 deletions
  1. 7 6
      CMakeLists.txt
  2. 10 3
      postprocess.c
  3. 7 0
      postprocess.h
  4. 32 31
      stacker.cpp
  5. 1 1
      stacker.h
  6. 268 150
      stackercpp.cpp
  7. 7 1
      stackercpp.h
  8. BIN
      test.dng
  9. BIN
      test5.dng

+ 7 - 6
CMakeLists.txt

@@ -11,15 +11,16 @@ pkg_check_modules(DNG REQUIRED libdng)
 pkg_check_modules(JPEG REQUIRED libjpeg)
 pkg_check_modules(EXIF REQUIRED libexif)
 pkg_check_modules(CV REQUIRED opencv4)
+pkg_check_modules(LENSFUN REQUIRED lensfun)
 
 add_compile_options(-Wall -Wextra -pedantic)
 
 add_executable(postprocessd main.c postprocess.c postprocess.h util.c util.h stacker.cpp stacker.h stackercpp.cpp stackercpp.h)
-target_link_libraries(postprocessd ${RAW_LIBRARIES} ${DNG_LIBRARIES} ${JPEG_LIBRARIES} ${EXIF_LIBRARIES} ${CV_LIBRARIES})
-target_include_directories(postprocessd PUBLIC ${RAW_INCLUDE_DIRS} ${DNG_INCLUDE_DIRS} ${JPEG_INCUDE_DIRS} ${EXIF_INCUDE_DIRS} ${CV_INCLUDE_DIRS})
-target_compile_options(postprocessd PUBLIC ${RAW_CFLAGS_OTHER} ${DNG_CFLAGS_OTHER} ${JPEG_CFLAGS_OTHER} ${EXIF_CFLAGS_OTHER} ${CV_CFLAGS_OTHER})
+target_link_libraries(postprocessd ${RAW_LIBRARIES} ${DNG_LIBRARIES} ${LENSFUN_LIBRARIES} ${JPEG_LIBRARIES} ${EXIF_LIBRARIES} ${CV_LIBRARIES})
+target_include_directories(postprocessd PUBLIC ${RAW_INCLUDE_DIRS} ${DNG_INCLUDE_DIRS} ${LENSFUN_INCLUDE_DIRS} ${JPEG_INCUDE_DIRS} ${EXIF_INCUDE_DIRS} ${CV_INCLUDE_DIRS})
+target_compile_options(postprocessd PUBLIC ${RAW_CFLAGS_OTHER} ${DNG_CFLAGS_OTHER} ${LENSFUN_CFLAGS_OTHER} ${JPEG_CFLAGS_OTHER} ${EXIF_CFLAGS_OTHER} ${CV_CFLAGS_OTHER})
 
 add_executable(postprocess-single single.c postprocess.c postprocess.h util.c util.h stacker.cpp stacker.h stackercpp.cpp stackercpp.h)
-target_link_libraries(postprocess-single ${RAW_LIBRARIES} ${DNG_LIBRARIES} ${JPEG_LIBRARIES} ${EXIF_LIBRARIES} ${CV_LIBRARIES})
-target_include_directories(postprocess-single PUBLIC ${RAW_INCLUDE_DIRS} ${DNG_INCLUDE_DIRS} ${JPEG_INCUDE_DIRS} ${EXIF_INCUDE_DIRS} ${CV_INCLUDE_DIRS})
-target_compile_options(postprocess-single PUBLIC ${RAW_CFLAGS_OTHER} ${DNG_CFLAGS_OTHER} ${JPEG_CFLAGS_OTHER} ${EXIF_CFLAGS_OTHER} ${CV_CFLAGS_OTHER})
+target_link_libraries(postprocess-single ${RAW_LIBRARIES} ${DNG_LIBRARIES} ${LENSFUN_LIBRARIES} ${JPEG_LIBRARIES} ${EXIF_LIBRARIES} ${CV_LIBRARIES})
+target_include_directories(postprocess-single PUBLIC ${RAW_INCLUDE_DIRS} ${DNG_INCLUDE_DIRS} ${LENSFUN_INCLUDE_DIRS} ${JPEG_INCUDE_DIRS} ${EXIF_INCUDE_DIRS} ${CV_INCLUDE_DIRS})
+target_compile_options(postprocess-single PUBLIC ${RAW_CFLAGS_OTHER} ${DNG_CFLAGS_OTHER} ${LENSFUN_CFLAGS_OTHER} ${JPEG_CFLAGS_OTHER} ${EXIF_CFLAGS_OTHER} ${CV_CFLAGS_OTHER})

+ 10 - 3
postprocess.c

@@ -248,7 +248,14 @@ read_exif(char *filename)
 	imagedata.isospeed = (uint16_t) dng.iso;
 	imagedata.focal_length = dng.focal_length;
 	imagedata.focal_length_35mm = (uint16_t) (dng.focal_length * dng.crop_factor);
+	imagedata.dist_a = dng.distortion_a;
+	imagedata.dist_b = dng.distortion_b;
+	imagedata.dist_c = dng.distortion_c;
+	imagedata.vignette_k1 = dng.vignette_k1;
+	imagedata.vignette_k2 = dng.vignette_k2;
+	imagedata.vignette_k3 = dng.vignette_k3;
 	imagedata.flash = 0;
+	return imagedata;
 }
 
 static int
@@ -300,7 +307,7 @@ postprocess_internal(char *burst_dir, char *target_dir, int keep)
 		printf("No DNG files found\n");
 		exit(1);
 	}
-	stacker_t *stacker = stacker_create(1);
+	stacker_t *stacker = stacker_create(1, imagedata);
 	while (burst_size--) {
 		fprintf(stderr, "DEBAYER %s\n", namelist[burst_size]->d_name);
 		snprintf(path, sizeof(path), "%s/%s", burst_dir, namelist[burst_size]->d_name);
@@ -362,14 +369,14 @@ postprocess_single(char *in_path, char *out_path, int quality, int verbose)
 	int width, height, result_size;
 	char *result;
 	clock_t timer;
-	stacker_t *stacker = stacker_create(verbose);
+	imagedata = read_exif(in_path);
+	stacker_t *stacker = stacker_create(verbose, imagedata);
 
 	// Give the operating system more cpu time
 	nice(19);
 
 	// Parse exif data from original file
 	timer = clock();
-	imagedata = read_exif(in_path);
 	exif = create_exif(imagedata);
 	if (verbose) {
 		printf("[%.1fms] %s\n", (float) (clock() - timer) / CLOCKS_PER_SEC * 1000, "exif read");

+ 7 - 0
postprocess.h

@@ -30,6 +30,13 @@ struct Imagedata {
 		float fnumber;
 		float focal_length;
 		uint16_t focal_length_35mm;
+
+		float dist_a;
+		float dist_b;
+		float dist_c;
+		float vignette_k1;
+		float vignette_k2;
+		float vignette_k3;
 };
 
 void

+ 32 - 31
stacker.cpp

@@ -1,65 +1,66 @@
 #include <stdlib.h>
 #include "stacker.h"
 #include "stackercpp.h"
+#include "postprocess.h"
 
 stacker_t *
-stacker_create(int verbose)
+stacker_create(int verbose, struct Imagedata imagedata)
 {
-    stacker_t *st;
-    Stacker *obj;
+	stacker_t *st;
+	Stacker *obj;
 
-    st = (__typeof__(st)) malloc(sizeof(*st));
-    obj = new Stacker(verbose != 0);
-    st->obj = obj;
-    return st;
+	st = (__typeof__(st)) malloc(sizeof(*st));
+	obj = new Stacker(verbose != 0, imagedata);
+	st->obj = obj;
+	return st;
 }
 
 void
 stacker_add_image(stacker_t *st, unsigned char *data, int width, int height)
 {
-    Stacker *obj;
-    if (st == NULL) {
-        return;
-    }
+	Stacker *obj;
+	if (st == NULL) {
+		return;
+	}
 
-    obj = static_cast<Stacker * >(st->obj);
-    obj->add_frame(data, width, height);
+	obj = static_cast<Stacker * >(st->obj);
+	obj->add_frame(data, width, height);
 }
 
 char *
 stacker_get_result(stacker_t *st)
 {
-    Stacker *obj;
-    if (st == NULL) {
-        return NULL;
-    }
-    obj = static_cast<Stacker * >(st->obj);
-    return obj->get_result();
+	Stacker *obj;
+	if (st == NULL) {
+		return NULL;
+	}
+	obj = static_cast<Stacker * >(st->obj);
+	return obj->get_result();
 }
 
 char *
 stacker_postprocess(stacker_t *st, unsigned char *data, int width, int height)
 {
-    Stacker *obj;
-    if (st == NULL) {
-        return NULL;
-    }
-    obj = static_cast<Stacker * >(st->obj);
-    return obj->postprocess(data, width, height);
+	Stacker *obj;
+	if (st == NULL) {
+		return NULL;
+	}
+	obj = static_cast<Stacker * >(st->obj);
+	return obj->postprocess(data, width, height);
 }
 
 int
 stacker_get_width(stacker_t *st)
 {
-    Stacker *obj;
-    obj = static_cast<Stacker * >(st->obj);
-    return obj->get_width();
+	Stacker *obj;
+	obj = static_cast<Stacker * >(st->obj);
+	return obj->get_width();
 }
 
 int
 stacker_get_height(stacker_t *st)
 {
-    Stacker *obj;
-    obj = static_cast<Stacker * >(st->obj);
-    return obj->get_height();
+	Stacker *obj;
+	obj = static_cast<Stacker * >(st->obj);
+	return obj->get_height();
 }

+ 1 - 1
stacker.h

@@ -11,7 +11,7 @@ struct stacker {
 typedef struct stacker stacker_t;
 
 stacker_t *
-stacker_create(int verbose);
+stacker_create(int verbose, struct Imagedata imagedata);
 
 void
 stacker_add_image(stacker_t *st, unsigned char *data, int width, int height);

+ 268 - 150
stackercpp.cpp

@@ -1,193 +1,311 @@
 #include "stackercpp.h"
 
-Stacker::Stacker(bool verbose)
+Stacker::Stacker(bool verbose, struct Imagedata imagedata)
 {
-    Stacker::verbose = verbose;
-    Stacker::export_width = 0;
-    Stacker::export_height = 0;
-    Stacker::layers = 0;
-    Stacker::trimratio = 0;
-    cv::setNumThreads(0);
-    Stacker::stopwatch = clock();
+	Stacker::verbose = verbose;
+	Stacker::export_width = 0;
+	Stacker::export_height = 0;
+	Stacker::layers = 0;
+	Stacker::trimratio = 0;
+
+	Stacker::imagedata = imagedata;
+
+	Stacker::ldb = lf_db_new();
+	lf_db_load(Stacker::ldb);
+
+	cv::setNumThreads(0);
+	Stacker::stopwatch = clock();
+
+	/*
+	Stacker::imagedata.dist_a = 0.025898;
+	Stacker::imagedata.dist_b = -0.092305;
+	Stacker::imagedata.dist_c = 0.105513;
+
+	Stacker::imagedata.vignette_k1 = -2.4268;
+	Stacker::imagedata.vignette_k2 = 3.0906;
+	Stacker::imagedata.vignette_k3 = -1.4546;
+	 */
 }
 
 void
 Stacker::add_frame(unsigned char *data, int width, int height)
 {
-    stopwatch_start();
-    Mat warp_matrix = Mat::eye(3, 3, CV_32F);
-    Mat mat = Mat(height, width, CV_8UC3, data);
-    Mat grayscale = Mat(height, width, CV_8UC1);
-    cv::cvtColor(mat, grayscale, cv::COLOR_BGR2GRAY);
-    stopwatch_mark("grayscale input");
-
-    stopwatch_start();
-    Scalar mean, stddev;
-    meanStdDev(grayscale, mean, stddev);
-    printf("mean: %f, dev: %f\n", mean[0], stddev[0]);
-    if (mean[0] < 10) {
-        return;
-    }
-    stopwatch_mark("filter");
-
-    int number_of_iterations = 5;
-    double termination_eps = 1e-10;
-    TermCriteria criteria(TermCriteria::COUNT + TermCriteria::EPS, number_of_iterations, termination_eps);
-    if (layers == 0) {
-        // First image in the stack is used as the reference to align the next frames to
-        Stacker::reference = grayscale;
-
-        // Create black image to accumulate the average
-        Stacker::stacked = Mat(height, width, CV_64FC3);
-        Stacker::stacked.setTo(Scalar(0, 0, 0, 0));
-
-        // Add first frame to the stack
-        Stacker::stacked += mat;
-        Stacker::layers += 1;
-    } else {
-        // All frames after the initial one are stacked
-        Mat warped = Mat(Stacker::stacked.rows, Stacker::stacked.cols, CV_32FC1);
-
-        stopwatch_start();
-        cv::findTransformECC(grayscale, Stacker::reference, warp_matrix, MOTION_HOMOGRAPHY, criteria);
-        stopwatch_mark("find alignment");
-
-        stopwatch_start();
-        warpPerspective(mat, warped, warp_matrix, warped.size(), INTER_LINEAR);
-        stopwatch_mark("warp image");
-
-        // Check how much the image should be cropped to hide the warped edges
-        float current_trimratio = cv::videostab::estimateOptimalTrimRatio(warp_matrix, mat.size());
-        Stacker::trimratio = std::max(Stacker::trimratio, current_trimratio);
-
-        // Add the warped image to the stack
-        Stacker::stacked += warped;
-        Stacker::layers += 1;
-    }
+	stopwatch_start();
+	Mat warp_matrix = Mat::eye(3, 3, CV_32F);
+	Mat mat = Mat(height, width, CV_8UC3, data);
+	Mat grayscale = Mat(height, width, CV_8UC1);
+	cv::cvtColor(mat, grayscale, cv::COLOR_BGR2GRAY);
+	stopwatch_mark("grayscale input");
+
+	stopwatch_start();
+	Scalar mean, stddev;
+	meanStdDev(grayscale, mean, stddev);
+	printf("mean: %f, dev: %f\n", mean[0], stddev[0]);
+	if (mean[0] < 10) {
+		return;
+	}
+	stopwatch_mark("filter");
+
+	int number_of_iterations = 5;
+	double termination_eps = 1e-10;
+	TermCriteria criteria(TermCriteria::COUNT + TermCriteria::EPS, number_of_iterations, termination_eps);
+	if (layers == 0) {
+		// First image in the stack is used as the reference to align the next frames to
+		Stacker::reference = grayscale;
+
+		// Create black image to accumulate the average
+		Stacker::stacked = Mat(height, width, CV_64FC3);
+		Stacker::stacked.setTo(Scalar(0, 0, 0, 0));
+
+		// Add first frame to the stack
+		Stacker::stacked += mat;
+		Stacker::layers += 1;
+	} else {
+		// All frames after the initial one are stacked
+		Mat warped = Mat(Stacker::stacked.rows, Stacker::stacked.cols, CV_32FC1);
+
+		stopwatch_start();
+		cv::findTransformECC(grayscale, Stacker::reference, warp_matrix, MOTION_HOMOGRAPHY, criteria);
+		stopwatch_mark("find alignment");
+
+		stopwatch_start();
+		warpPerspective(mat, warped, warp_matrix, warped.size(), INTER_LINEAR);
+		stopwatch_mark("warp image");
+
+		// Check how much the image should be cropped to hide the warped edges
+		float current_trimratio = cv::videostab::estimateOptimalTrimRatio(warp_matrix, mat.size());
+		Stacker::trimratio = std::max(Stacker::trimratio, current_trimratio);
+
+		// Add the warped image to the stack
+		Stacker::stacked += warped;
+		Stacker::layers += 1;
+	}
 }
 
 Mat
-Stacker::postprocess_mat(Mat input)
+Stacker::postprocess_mat(Mat
+input)
 {
-    stopwatch_start();
-    int h_crop = (int) ((float) input.cols * Stacker::trimratio);
-    int v_crop = (int) ((float) input.rows * Stacker::trimratio);
-    Mat cropped;
-    input(Rect(h_crop, v_crop, input.cols - h_crop - h_crop, input.rows - v_crop - v_crop)).copyTo(input);
-    stopwatch_mark("trim");
-
-    stopwatch_start();
-    Mat blur;
-    GaussianBlur(input, blur, Size(0, 0), 1.8);
-    std::vector<cv::Mat> rgb_planes(3);
-    cv::split(blur, rgb_planes);
-    double min_r, max_r, min_g, max_g, min_b, max_b;
-    minMaxIdx(rgb_planes[0], &min_b, &max_b);
-    minMaxIdx(rgb_planes[1], &min_g, &max_g);
-    minMaxIdx(rgb_planes[2], &min_r, &max_r);
-    input = input - Scalar(min_b + 5, min_g + 5, min_r + 5);
-    double scale_r, scale_g, scale_b;
-    scale_r = 255 / (max_r - min_r + 5);
-    scale_g = (255 / (max_g - min_g + 5));
-    scale_b = 255 / (max_b - min_b + 5);
-    multiply(input, Scalar(scale_b, scale_g, scale_r), input);
-    stopwatch_mark("levels");
-
-    stopwatch_start();
-    Mat sharpened;
-    GaussianBlur(input, sharpened, Size(0, 0), 1.7);
-    addWeighted(input, 2.5, sharpened, -1.5, 0, sharpened);
-    stopwatch_mark("sharpen");
-
-    /* Disabled CLAHE local contrast, it's a bit to overpronounced and doesn't
-     * seem really necessary at this point
-
-    stopwatch_start();
-    Mat lab;
-    cvtColor(sharpened, lab, COLOR_BGR2Lab);
-    std::vector<cv::Mat> lab_planes(3);
-    cv::split(lab, lab_planes);
-    stopwatch_mark("to Lab");
-
-    stopwatch_start();
-    cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
-    clahe->setClipLimit(1);
-    clahe->setTilesGridSize(Size(8, 8));
-    cv::Mat dst;
-    clahe->apply(lab_planes[0], dst);
-    dst.copyTo(lab_planes[0]);
-    stopwatch_mark("clahe");
-
-    stopwatch_start();
-    Mat result;
-    cv::merge(lab_planes, lab);
-    cvtColor(lab, result, COLOR_Lab2BGR);
-    stopwatch_mark("to RGB");
-    */
-
-    return sharpened;
+	stopwatch_start();
+
+	int h_crop = (int) ((float) input.cols * Stacker::trimratio);
+	int v_crop = (int) ((float) input.rows * Stacker::trimratio);
+	Mat cropped;
+	input(Rect(h_crop, v_crop, input.cols - h_crop - h_crop, input.rows - v_crop - v_crop)
+	).
+		copyTo(input);
+	stopwatch_mark("trim");
+
+	stopwatch_start();
+
+	Mat blur;
+	GaussianBlur(input, blur, Size(0, 0),
+		1.8);
+	std::vector<cv::Mat> rgb_planes(3);
+	cv::split(blur, rgb_planes
+	);
+	double min_r, max_r, min_g, max_g, min_b, max_b;
+	minMaxIdx(rgb_planes[0], &min_b, &max_b
+	);
+	minMaxIdx(rgb_planes[1], &min_g, &max_g
+	);
+	minMaxIdx(rgb_planes[2], &min_r, &max_r
+	);
+	input = input - Scalar(min_b + 5, min_g + 5, min_r + 5);
+	double scale_r, scale_g, scale_b;
+	scale_r = 255 / (max_r - min_r + 5);
+	scale_g = (255 / (max_g - min_g + 5));
+	scale_b = 255 / (max_b - min_b + 5);
+	multiply(input, Scalar(scale_b, scale_g, scale_r), input
+	);
+	stopwatch_mark("levels");
+
+	stopwatch_start();
+
+	Mat sharpened;
+	GaussianBlur(input, sharpened, Size(0, 0),
+		1.7);
+	addWeighted(input,
+		2.5, sharpened, -1.5, 0, sharpened);
+	stopwatch_mark("sharpen");
+
+/* Disabled CLAHE local contrast, it's a bit to overpronounced and doesn't
+ * seem really necessary at this point
+
+stopwatch_start();
+Mat lab;
+cvtColor(sharpened, lab, COLOR_BGR2Lab);
+std::vector<cv::Mat> lab_planes(3);
+cv::split(lab, lab_planes);
+stopwatch_mark("to Lab");
+
+stopwatch_start();
+cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
+clahe->setClipLimit(1);
+clahe->setTilesGridSize(Size(8, 8));
+cv::Mat dst;
+clahe->apply(lab_planes[0], dst);
+dst.copyTo(lab_planes[0]);
+stopwatch_mark("clahe");
+
+stopwatch_start();
+Mat result;
+cv::merge(lab_planes, lab);
+cvtColor(lab, result, COLOR_Lab2BGR);
+stopwatch_mark("to RGB");
+*/
+
+	return
+		sharpened;
 }
 
 char *
 Stacker::get_result()
 {
-    // Complete the averaging and go back to an 8-bit image
-    stopwatch_start();
-    Stacker::stacked.convertTo(Stacker::stacked, CV_8U, 1. / Stacker::layers);
-    stopwatch_mark("average");
-
-    // Run the final-frame postprocessing
-    Mat result = postprocess_mat(Stacker::stacked);
-    Stacker::export_width = result.cols;
-    Stacker::export_height = result.rows;
-
-    // Convert mat to bytes
-    size_t size = result.total() * result.elemSize();
-    char *data = (char *) malloc(size);
-    std::memcpy(data, result.data, size * sizeof(char));
-    return data;
+	// Complete the averaging and go back to an 8-bit image
+	stopwatch_start();
+	Stacker::stacked.convertTo(Stacker::stacked, CV_8U, 1. / Stacker::layers);
+	stopwatch_mark("average");
+
+	// Run the final-frame postprocessing
+	Mat result = postprocess_mat(Stacker::stacked);
+	Stacker::export_width = result.cols;
+	Stacker::export_height = result.rows;
+
+	// Convert mat to bytes
+	size_t size = result.total() * result.elemSize();
+	char *data = (char *) malloc(size);
+	std::memcpy(data, result.data, size * sizeof(char));
+	return data;
 }
 
 char *
 Stacker::postprocess(unsigned char *data, int width, int height)
 {
-    // Convert bytes to mat
-    Mat mat = Mat(height, width, CV_8UC3, data);
-
-    // Run the final-frame postprocessing
-    Mat result = postprocess_mat(mat);
-    Stacker::export_width = result.cols;
-    Stacker::export_height = result.rows;
-
-    // Convert mat to bytes
-    size_t size = result.total() * result.elemSize();
-    char *outdata = (char *) malloc(size);
-    std::memcpy(outdata, result.data, size * sizeof(char));
-    return outdata;
+	// Run lensfun
+	lfCamera *cam = nullptr;
+	const lfCamera **cameras = ldb->FindCamerasExt(nullptr, imagedata.model);
+	if (cameras) {
+		cam = (lfCamera *) cameras[0];
+		fprintf(stderr, "Using camera %s\n", cam->Model);
+	} else {
+		fprintf(stderr, "No camera found in LensFun database\n");
+	}
+	lfLens *lens = nullptr;
+	const lfLens **lenses = ldb->FindLenses(cam, nullptr, imagedata.model);
+	if (lenses) {
+		lens = (lfLens *) lenses[0];
+		fprintf(stderr, "Using lens %s\n", lens->Model);
+	} else {
+		fprintf(stderr, "No lens found in LensFun database\n");
+	}
+	float cropfactor = (float) imagedata.focal_length_35mm / imagedata.focal_length;
+
+	if (!lens && !cam) {
+		cam = new lfCamera;
+		cam->SetModel(imagedata.model);
+		lens = new lfLens;
+		lens->Type = LF_RECTILINEAR;
+		lens->GuessParameters();
+		lens->AddMount("fixed");
+		lens->SetModel(imagedata.model);
+		lens->MinFocal = imagedata.focal_length;
+		lens->MaxFocal = imagedata.focal_length;
+		lens->MinAperture = imagedata.fnumber;
+		lens->MaxAperture = imagedata.fnumber;
+		lens->AspectRatio = 1.5;
+		lens->CropFactor = cropfactor;
+
+		if (imagedata.dist_a != 0.0f) {
+			auto *distortion = new lfLensCalibDistortion();
+			distortion->Model = lfDistortionModel::LF_DIST_MODEL_PTLENS;
+			distortion->Focal = imagedata.focal_length;
+			distortion->Terms[0] = imagedata.dist_a;
+			distortion->Terms[1] = imagedata.dist_b;
+			distortion->Terms[2] = imagedata.dist_c;
+			lens->AddCalibDistortion(distortion);
+		}
+
+		if (imagedata.vignette_k1 != 0.0f) {
+			auto *vignette = new lfLensCalibVignetting();
+			vignette->Model = LF_VIGNETTING_MODEL_PA;
+			vignette->Focal = imagedata.focal_length;
+			vignette->Aperture = imagedata.fnumber;
+			vignette->Distance = 10.0f;
+			vignette->Terms[0] = imagedata.vignette_k1;
+			vignette->Terms[1] = imagedata.vignette_k2;
+			vignette->Terms[2] = imagedata.vignette_k3;
+			lens->AddCalibVignetting(vignette);
+		}
+
+		if (!lens->Check()) {
+			fprintf(stderr, "Lens check failed\n");
+		}
+	}
+
+	auto *mod = new lfModifier(lens, cropfactor, width, height);
+	mod->Initialize(lens, LF_PF_U8, imagedata.focal_length, imagedata.fnumber, 10.0f, 1.0f, lfLensType::LF_RECTILINEAR,
+		LF_MODIFY_ALL, false);
+
+	auto *pos = new float[width * height * 2];
+	bool do_remap = mod->ApplyGeometryDistortion(0.0f, 0.0f, width, height, pos);
+
+	// Convert bytes to mat
+	Mat mat = Mat(height, width, CV_8UC3, data);
+	stopwatch_start();
+	mod->ApplyColorModification(mat.data, 0, 0, width, height, LF_CR_3(RED, GREEN, BLUE), width * 3);
+	stopwatch_mark("vignette");
+	if (do_remap) {
+		stopwatch_start();
+		Mat dst = Mat(mat.size(), mat.type());
+		Mat map_x(mat.size(), CV_32FC1);
+		Mat map_y(mat.size(), CV_32FC1);
+		for (int x = 0; x < mat.rows; x++) {
+			for (int y = 0; y < mat.cols; y++) {
+				size_t offset = ((x * mat.cols) + y) * 2;
+				map_x.at<float>(x, y) = pos[offset];
+				map_y.at<float>(x, y) = pos[offset + 1];
+			}
+		}
+		remap(mat, dst, map_x, map_y, INTER_LANCZOS4, BORDER_CONSTANT, Scalar(0, 0, 0));
+		mat = dst;
+		stopwatch_mark("distortion");
+	}
+
+	// Run the final-frame postprocessing
+	Mat result = postprocess_mat(mat);
+	Stacker::export_width = result.cols;
+	Stacker::export_height = result.rows;
+
+	// Convert mat to bytes
+	size_t size = result.total() * result.elemSize();
+	char *outdata = (char *) malloc(size);
+	std::memcpy(outdata, result.data, size * sizeof(char));
+	return outdata;
 }
 
 void
 Stacker::stopwatch_start()
 {
-    Stacker::stopwatch = clock();
+	Stacker::stopwatch = clock();
 }
 
 void
 Stacker::stopwatch_mark(const char *name)
 {
-    if (Stacker::verbose) {
-        printf("[%.1fms] %s\n", float(clock() - Stacker::stopwatch) / CLOCKS_PER_SEC * 1000, name);
-    }
+	if (Stacker::verbose) {
+		printf("[%.1fms] %s\n", float(clock() - Stacker::stopwatch) / CLOCKS_PER_SEC * 1000, name);
+	}
 }
 
 int
 Stacker::get_width()
 {
-    return Stacker::export_width;
+	return Stacker::export_width;
 }
 
 int
 Stacker::get_height()
 {
-    return Stacker::export_height;
+	return Stacker::export_height;
 }

+ 7 - 1
stackercpp.h

@@ -3,6 +3,8 @@
 #include <opencv2/imgcodecs.hpp>
 #include <opencv2/video/tracking.hpp>
 #include <opencv2/videostab/motion_stabilizing.hpp>
+#include <lensfun.h>
+#include "postprocess.h"
 
 
 using namespace std;
@@ -13,7 +15,7 @@ using namespace cv;
 
 class Stacker {
 public:
-        Stacker(bool verbose);
+        Stacker(bool verbose, struct Imagedata imagedata);
 
         void
         add_frame(unsigned char *data, int width, int height);
@@ -40,6 +42,10 @@ private:
         int export_height;
         float trimratio;
 
+		// Lensfun
+		struct Imagedata imagedata;
+		struct lfDatabase *ldb;
+
         Mat
         postprocess_mat(Mat input);
 

BIN
test.dng


BIN
test5.dng