实现RawBurstCapture
parent
9c9a122034
commit
72f595b7a3
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <utility> // std::pair
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include "hdrplus/burst.h"
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
class align
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
align() = default;
|
||||||
|
~align() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Run alignment on burst of images
|
||||||
|
*
|
||||||
|
* @param burst_images collection of burst images
|
||||||
|
* @param aligements alignment in pixel value pair.
|
||||||
|
* Outer most vector is per alternative image.
|
||||||
|
* Inner most two vector is for horizontle & verticle tiles
|
||||||
|
*/
|
||||||
|
void process( const hdrplus::burst& burst_images, \
|
||||||
|
std::vector<std::vector<std::vector<std::pair<int, int>>>>& aligements );
|
||||||
|
|
||||||
|
private:
|
||||||
|
// From original image to coarse image
|
||||||
|
const std::vector<int> inv_scale_factors = { 1, 2, 4, 4 };
|
||||||
|
const std::vector<int> distances = { 1, 2, 2, 2 }; // L1 / L2 distance
|
||||||
|
const std::vector<int> grayimg_search_radious = { 1, 4, 4, 4 };
|
||||||
|
const std::vector<int> grayimg_tile_sizes = { 16, 16, 16, 8 };
|
||||||
|
const int num_levels = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility> // std::pair
|
||||||
|
#include <memory> // std::shared_ptr
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include <libraw/libraw.h>
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
class bayer_image
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit bayer_image( const std::string& bayer_image_path );
|
||||||
|
~bayer_image() = default;
|
||||||
|
|
||||||
|
std::pair<double, double> get_noise_params() const;
|
||||||
|
|
||||||
|
std::shared_ptr<LibRaw> libraw_processor;
|
||||||
|
cv::Mat raw_image;
|
||||||
|
cv::Mat grayscale_image;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int white_level;
|
||||||
|
std::vector<int> black_level_per_channel;
|
||||||
|
float iso;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float baseline_lambda_shot = 3.24 * pow( 10, -4 );
|
||||||
|
float baseline_lambda_read = 4.3 * pow( 10, -6 );
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include "hdrplus/bayer_image.h"
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
class burst
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit burst( const std::string& burst_path, const std::string& reference_image_path );
|
||||||
|
explicit burst(const std::vector<std::string>& burst_paths, int reference_image_index);
|
||||||
|
|
||||||
|
~burst() = default;
|
||||||
|
|
||||||
|
// Reference image index in the array
|
||||||
|
int reference_image_idx;
|
||||||
|
|
||||||
|
// Source bayer images & grayscale unpadded image
|
||||||
|
std::vector<hdrplus::bayer_image> bayer_images;
|
||||||
|
|
||||||
|
// Image padded to upper level tile size (16*2)
|
||||||
|
// Use for alignment, merging, and finishing
|
||||||
|
std::vector<cv::Mat> bayer_images_pad;
|
||||||
|
|
||||||
|
// Padding information
|
||||||
|
std::vector<int> padding_info_bayer;
|
||||||
|
|
||||||
|
// Image padded to upper level tile size (16)
|
||||||
|
// Use for alignment, merging, and finishing
|
||||||
|
std::vector<cv::Mat> grayscale_images_pad;
|
||||||
|
|
||||||
|
// number of image (including reference) in burst
|
||||||
|
int num_images;
|
||||||
|
|
||||||
|
// Bayer image after merging, stored as cv::Mat
|
||||||
|
cv::Mat merged_bayer_image;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,240 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <hdrplus/bayer_image.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <hdrplus/params.h>
|
||||||
|
#include <hdrplus/burst.h>
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
uint16_t uGammaCompress_1pix(float x, float threshold,float gainMin,float gainMax,float exponent);
|
||||||
|
uint16_t uGammaDecompress_1pix(float x, float threshold,float gainMin,float gainMax,float exponent);
|
||||||
|
cv::Mat uGammaCompress_(cv::Mat m,float threshold,float gainMin,float gainMax,float exponent);
|
||||||
|
cv::Mat uGammaDecompress_(cv::Mat m,float threshold,float gainMin,float gainMax,float exponent);
|
||||||
|
cv::Mat gammasRGB(cv::Mat img, bool mode);
|
||||||
|
|
||||||
|
|
||||||
|
class finish
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
cv::Mat mergedBayer; // merged image from Merge Module
|
||||||
|
std::string burstPath; // path to burst images
|
||||||
|
std::vector<std::string> rawPathList; // a list or array of the path to all burst imgs under burst Path
|
||||||
|
int refIdx; // index of the reference img
|
||||||
|
Parameters params;
|
||||||
|
cv::Mat rawReference;
|
||||||
|
// LibRaw libraw_processor_finish;
|
||||||
|
bayer_image* refBayer;
|
||||||
|
|
||||||
|
std::string mergedImgPath;
|
||||||
|
finish() = default;
|
||||||
|
|
||||||
|
// please use this initialization after merging part finish
|
||||||
|
finish(std::string burstPath, cv::Mat mergedBayer,int refIdx){
|
||||||
|
this->refIdx = refIdx;
|
||||||
|
this->burstPath = burstPath;
|
||||||
|
this->mergedBayer = mergedBayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for local testing only
|
||||||
|
finish(std::string burstPath, std::string mergedBayerPath,int refIdx){
|
||||||
|
this->refIdx = refIdx;
|
||||||
|
this->burstPath = burstPath;
|
||||||
|
this->mergedBayer = loadFromCSV(mergedBayerPath, CV_16UC1);//
|
||||||
|
load_rawPathList(burstPath);
|
||||||
|
refBayer= new bayer_image(this->rawPathList[refIdx]);
|
||||||
|
this->rawReference = refBayer->raw_image;//;grayscale_image
|
||||||
|
|
||||||
|
// initialize parameters in libraw_processor_finish
|
||||||
|
setLibRawParams();
|
||||||
|
showParams();
|
||||||
|
|
||||||
|
std::cout<<"Finish init() finished!"<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~finish() = default;
|
||||||
|
|
||||||
|
// finish pipeline func
|
||||||
|
// void process(std::string burstPath, cv::Mat mergedBayer,int refIdx);
|
||||||
|
void process(const hdrplus::burst& burst_images, cv::Mat& finalOutputImage);
|
||||||
|
|
||||||
|
// replace Mat a with Mat b
|
||||||
|
void copy_mat_16U(cv::Mat& A, cv::Mat B);
|
||||||
|
void copy_rawImg2libraw(std::shared_ptr<LibRaw>& libraw_ptr, cv::Mat B);
|
||||||
|
|
||||||
|
// postprocess
|
||||||
|
// cv::Mat postprocess(std::shared_ptr<LibRaw>& libraw_ptr);
|
||||||
|
|
||||||
|
void showImg(cv::Mat img)
|
||||||
|
{
|
||||||
|
int ch = CV_MAT_CN(CV_8UC1);
|
||||||
|
|
||||||
|
// cv::Mat tmp(4208,3120,CV_16UC1);
|
||||||
|
cv::Mat tmp(img);
|
||||||
|
// u_int16_t* ptr_tmp = (u_int16_t*)tmp.data;
|
||||||
|
// u_int16_t* ptr_img = (u_int16_t*)img.data;
|
||||||
|
// // col major to row major
|
||||||
|
// for(int r = 0; r < tmp.rows; r++) {
|
||||||
|
// for(int c = 0; c < tmp.cols; c++) {
|
||||||
|
// *(ptr_tmp+r*tmp.cols+c) = *(ptr_img+c*tmp.rows+r)/2048.0*255.0;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// std::cout<<"height="<<tmp.rows<<std::endl;
|
||||||
|
// std::cout<<"width="<<tmp.cols<<std::endl;
|
||||||
|
// cv::transpose(tmp, tmp);
|
||||||
|
|
||||||
|
u_int16_t* ptr = (u_int16_t*)tmp.data;
|
||||||
|
for(int r = 0; r < tmp.rows; r++) {
|
||||||
|
for(int c = 0; c < tmp.cols; c++) {
|
||||||
|
*(ptr+r*tmp.cols+c) = *(ptr+r*tmp.cols+c)/2048.0*255.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = tmp.reshape(ch);
|
||||||
|
tmp.convertTo(tmp, CV_8UC1);
|
||||||
|
cv::imshow("test",tmp);
|
||||||
|
cv::imwrite("test2.jpg", tmp);
|
||||||
|
cv::waitKey(0);
|
||||||
|
std::cout<< this->mergedBayer.size()<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showMat(cv::Mat img){
|
||||||
|
std::cout<<"size="<<img.size()<<std::endl;
|
||||||
|
std::cout<<"type="<<img.type()<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showParams()
|
||||||
|
{
|
||||||
|
std::cout<<"Parameters:"<<std::endl;
|
||||||
|
std::cout<<"tuning_ltmGain = "<<this->params.tuning.ltmGain<<std::endl;
|
||||||
|
std::cout<<"tuning_gtmContrast = "<<this->params.tuning.gtmContrast<<std::endl;
|
||||||
|
for(auto key_val:this->params.flags){
|
||||||
|
std::cout<<key_val.first<<","<<key_val.second<<std::endl;
|
||||||
|
}
|
||||||
|
std::cout<<"demosaic_algorithm = "<<refBayer->libraw_processor->imgdata.params.user_qual<<std::endl;
|
||||||
|
std::cout<<"half_size = "<<refBayer->libraw_processor->imgdata.params.half_size<<std::endl;
|
||||||
|
std::cout<<"use_camera_wb = "<<refBayer->libraw_processor->imgdata.params.use_camera_wb<<std::endl;
|
||||||
|
std::cout<<"use_auto_wb = "<<refBayer->libraw_processor->imgdata.params.use_auto_wb<<std::endl;
|
||||||
|
std::cout<<"no_auto_bright = "<<refBayer->libraw_processor->imgdata.params.no_auto_bright<<std::endl;
|
||||||
|
std::cout<<"output_color = "<<refBayer->libraw_processor->imgdata.params.output_color <<std::endl;
|
||||||
|
std::cout<<"gamma[0] = "<<refBayer->libraw_processor->imgdata.params.gamm[0]<<std::endl;
|
||||||
|
std::cout<<"gamma[1] = "<<refBayer->libraw_processor->imgdata.params.gamm[1]<<std::endl;
|
||||||
|
std::cout<<"output_bps = "<<refBayer->libraw_processor->imgdata.params.output_bps<<std::endl;
|
||||||
|
|
||||||
|
// std::cout<<"demosaic_algorithm = "<<libraw_processor_finish.imgdata.params.user_qual<<std::endl;
|
||||||
|
// std::cout<<"half_size = "<<libraw_processor_finish.imgdata.params.half_size<<std::endl;
|
||||||
|
// std::cout<<"use_camera_wb = "<<libraw_processor_finish.imgdata.params.use_camera_wb<<std::endl;
|
||||||
|
// std::cout<<"use_auto_wb = "<<libraw_processor_finish.imgdata.params.use_auto_wb<<std::endl;
|
||||||
|
// std::cout<<"no_auto_bright = "<<libraw_processor_finish.imgdata.params.no_auto_bright<<std::endl;
|
||||||
|
// std::cout<<"output_color = "<<libraw_processor_finish.imgdata.params.output_color <<std::endl;
|
||||||
|
// std::cout<<"gamma[0] = "<<libraw_processor_finish.imgdata.params.gamm[0]<<std::endl;
|
||||||
|
// std::cout<<"gamma[1] = "<<libraw_processor_finish.imgdata.params.gamm[1]<<std::endl;
|
||||||
|
// std::cout<<"output_bps = "<<libraw_processor_finish.imgdata.params.output_bps<<std::endl;
|
||||||
|
|
||||||
|
std::cout<<"===================="<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showRawPathList(){
|
||||||
|
std::cout<<"RawPathList:"<<std::endl;
|
||||||
|
for(auto pth:this->rawPathList){
|
||||||
|
std::cout<<pth<<std::endl;
|
||||||
|
}
|
||||||
|
std::cout<<"===================="<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
cv::Mat loadFromCSV(const std::string& path, int opencv_type)
|
||||||
|
{
|
||||||
|
cv::Mat m;
|
||||||
|
std::ifstream csvFile (path);
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (getline(csvFile, line))
|
||||||
|
{
|
||||||
|
std::vector<int> dvals;
|
||||||
|
std::stringstream ss(line);
|
||||||
|
std::string val;
|
||||||
|
// int count=0;
|
||||||
|
while (getline(ss, val, ','))
|
||||||
|
{
|
||||||
|
dvals.push_back(stod(val));//*255.0/2048.0
|
||||||
|
// count++;
|
||||||
|
}
|
||||||
|
// std::cout<<count<<std::endl;
|
||||||
|
cv::Mat mline(dvals, true);
|
||||||
|
cv::transpose(mline, mline);
|
||||||
|
|
||||||
|
m.push_back(mline);
|
||||||
|
}
|
||||||
|
int ch = CV_MAT_CN(opencv_type);
|
||||||
|
|
||||||
|
m = m.reshape(ch);
|
||||||
|
m.convertTo(m, opencv_type);
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_rawPathList(std::string burstPath){
|
||||||
|
DIR *pDir; // pointer to root
|
||||||
|
struct dirent *ptr;
|
||||||
|
if (!(pDir = opendir(burstPath.c_str()))) {
|
||||||
|
std::cout<<"root dir not found!"<<std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while ((ptr = readdir(pDir)) != nullptr) {
|
||||||
|
// ptr will move to the next file automatically
|
||||||
|
std::string sub_file = burstPath + "/" + ptr->d_name; // current filepath that ptr points to
|
||||||
|
if (ptr->d_type != 8 && ptr->d_type != 4) { // not normal file or dir
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// only need normal files
|
||||||
|
if (ptr->d_type == 8) {
|
||||||
|
if (strcmp(ptr->d_name, ".") != 0 && strcmp(ptr->d_name, "..") != 0) {
|
||||||
|
if (strstr(ptr->d_name, ".dng")) {
|
||||||
|
rawPathList.emplace_back(sub_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// close root dir
|
||||||
|
closedir(pDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLibRawParams(){
|
||||||
|
refBayer->libraw_processor->imgdata.params.user_qual = params.rawpyArgs.demosaic_algorithm;
|
||||||
|
refBayer->libraw_processor->imgdata.params.half_size = params.rawpyArgs.half_size;
|
||||||
|
refBayer->libraw_processor->imgdata.params.use_camera_wb = params.rawpyArgs.use_camera_wb;
|
||||||
|
refBayer->libraw_processor->imgdata.params.use_auto_wb = params.rawpyArgs.use_auto_wb;
|
||||||
|
refBayer->libraw_processor->imgdata.params.no_auto_bright = params.rawpyArgs.no_auto_bright;
|
||||||
|
refBayer->libraw_processor->imgdata.params.output_color = params.rawpyArgs.output_color;
|
||||||
|
refBayer->libraw_processor->imgdata.params.gamm[0] = params.rawpyArgs.gamma[0];
|
||||||
|
refBayer->libraw_processor->imgdata.params.gamm[1] = params.rawpyArgs.gamma[1];
|
||||||
|
refBayer->libraw_processor->imgdata.params.output_bps = params.rawpyArgs.output_bps;
|
||||||
|
|
||||||
|
// libraw_processor_finish.imgdata.params.user_qual = params.rawpyArgs.demosaic_algorithm;
|
||||||
|
// libraw_processor_finish.imgdata.params.half_size = params.rawpyArgs.half_size;
|
||||||
|
// libraw_processor_finish.imgdata.params.use_camera_wb = params.rawpyArgs.use_camera_wb;
|
||||||
|
// libraw_processor_finish.imgdata.params.use_auto_wb = params.rawpyArgs.use_auto_wb;
|
||||||
|
// libraw_processor_finish.imgdata.params.no_auto_bright = params.rawpyArgs.no_auto_bright;
|
||||||
|
// libraw_processor_finish.imgdata.params.output_color = params.rawpyArgs.output_color;
|
||||||
|
// libraw_processor_finish.imgdata.params.gamm[0] = params.rawpyArgs.gamma[0];
|
||||||
|
// libraw_processor_finish.imgdata.params.gamm[1] = params.rawpyArgs.gamma[1];
|
||||||
|
// libraw_processor_finish.imgdata.params.output_bps = params.rawpyArgs.output_bps;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include "hdrplus/burst.h"
|
||||||
|
#include "hdrplus/align.h"
|
||||||
|
#include "hdrplus/merge.h"
|
||||||
|
#include "hdrplus/finish.h"
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
class hdrplus_pipeline
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
hdrplus::align align_module;
|
||||||
|
hdrplus::merge merge_module;
|
||||||
|
hdrplus::finish finish_module;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void run_pipeline( const std::string& burst_path, const std::string& reference_image_path );
|
||||||
|
bool run_pipeline( const std::vector<std::string>& burst_paths, int reference_image_index, cv::Mat& finalImg );
|
||||||
|
hdrplus_pipeline() = default;
|
||||||
|
~hdrplus_pipeline() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,184 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include <cmath>
|
||||||
|
#include "hdrplus/burst.h"
|
||||||
|
|
||||||
|
#define TILE_SIZE 16
|
||||||
|
#define TEMPORAL_FACTOR 75
|
||||||
|
#define SPATIAL_FACTOR 0.1
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
class merge
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int offset = TILE_SIZE / 2;
|
||||||
|
float baseline_lambda_shot = 3.24 * pow( 10, -4 );
|
||||||
|
float baseline_lambda_read = 4.3 * pow( 10, -6 );
|
||||||
|
|
||||||
|
merge() = default;
|
||||||
|
~merge() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Run alignment on burst of images
|
||||||
|
*
|
||||||
|
* @param burst_images collection of burst images
|
||||||
|
* @param alignments alignment in pixel value pair.
|
||||||
|
* Outer most vector is per alternative image.
|
||||||
|
* Inner most two vector is for horizontal & vertical tiles
|
||||||
|
*/
|
||||||
|
void process( hdrplus::burst& burst_images, \
|
||||||
|
std::vector<std::vector<std::vector<std::pair<int, int>>>>& alignments);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
std::vector<cv::Mat> get_other_tiles(); //return the other tile list T_1 to T_n
|
||||||
|
|
||||||
|
std::vector<cv::Mat> vector_math(string operation, reference_tile, other_tile_list); //for loop allowing operations across single element and list
|
||||||
|
|
||||||
|
std::vector<cv::Mat> scalar_vector_math(string operation, scalar num, std::vector<cv::Mat> tile_list); //for loop allowing operations across single element and list
|
||||||
|
|
||||||
|
std::vector<cv::Mat> average_vector(std::vector<cv::Mat> tile_list); //take average of vector elements
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
private:
|
||||||
|
float tileRMS(cv::Mat tile) {
|
||||||
|
cv::Mat squared;
|
||||||
|
cv::multiply(tile, tile, squared);
|
||||||
|
return sqrt(cv::mean(squared)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float> getNoiseVariance(std::vector<cv::Mat> tiles, float lambda_shot, float lambda_read) {
|
||||||
|
std::vector<float> noise_variance;
|
||||||
|
for (auto tile : tiles) {
|
||||||
|
noise_variance.push_back(lambda_shot * tileRMS(tile) + lambda_read);
|
||||||
|
}
|
||||||
|
return noise_variance;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat cosineWindow1D(cv::Mat input, int window_size = TILE_SIZE) {
|
||||||
|
cv::Mat output = input.clone();
|
||||||
|
for (int i = 0; i < input.cols; ++i) {
|
||||||
|
output.at<float>(0, i) = 1. / 2. - 1. / 2. * cos(2 * M_PI * (input.at<float>(0, i) + 1 / 2.) / window_size);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat cosineWindow2D(cv::Mat tile) {
|
||||||
|
int window_size = tile.rows; // Assuming square tile
|
||||||
|
cv::Mat output_tile = tile.clone();
|
||||||
|
|
||||||
|
cv::Mat window = cv::Mat::zeros(1, window_size, CV_32F);
|
||||||
|
for(int i = 0; i < window_size; ++i) {
|
||||||
|
window.at<float>(i) = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat window_x = cosineWindow1D(window, window_size);
|
||||||
|
window_x = cv::repeat(window_x, window_size, 1);
|
||||||
|
cv::Mat window_2d = window_x.mul(window_x.t());
|
||||||
|
|
||||||
|
cv::Mat window_applied;
|
||||||
|
cv::multiply(tile, window_2d, window_applied, 1, CV_32F);
|
||||||
|
return window_applied;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat cat2Dtiles(std::vector<std::vector<cv::Mat>> tiles) {
|
||||||
|
std::vector<cv::Mat> rows;
|
||||||
|
for (auto row_tiles : tiles) {
|
||||||
|
cv::Mat row;
|
||||||
|
cv::hconcat(row_tiles, row);
|
||||||
|
rows.push_back(row);
|
||||||
|
}
|
||||||
|
cv::Mat img;
|
||||||
|
cv::vconcat(rows, img);
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
void circshift(cv::Mat &out, const cv::Point &delta)
|
||||||
|
{
|
||||||
|
cv::Size sz = out.size();
|
||||||
|
|
||||||
|
// error checking
|
||||||
|
assert(sz.height > 0 && sz.width > 0);
|
||||||
|
|
||||||
|
// no need to shift
|
||||||
|
if ((sz.height == 1 && sz.width == 1) || (delta.x == 0 && delta.y == 0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// delta transform
|
||||||
|
int x = delta.x;
|
||||||
|
int y = delta.y;
|
||||||
|
if (x > 0) x = x % sz.width;
|
||||||
|
if (y > 0) y = y % sz.height;
|
||||||
|
if (x < 0) x = x % sz.width + sz.width;
|
||||||
|
if (y < 0) y = y % sz.height + sz.height;
|
||||||
|
|
||||||
|
|
||||||
|
// in case of multiple dimensions
|
||||||
|
std::vector<cv::Mat> planes;
|
||||||
|
split(out, planes);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < planes.size(); i++)
|
||||||
|
{
|
||||||
|
// vertical
|
||||||
|
cv::Mat tmp0, tmp1, tmp2, tmp3;
|
||||||
|
cv::Mat q0(planes[i], cv::Rect(0, 0, sz.width, sz.height - y));
|
||||||
|
cv::Mat q1(planes[i], cv::Rect(0, sz.height - y, sz.width, y));
|
||||||
|
q0.copyTo(tmp0);
|
||||||
|
q1.copyTo(tmp1);
|
||||||
|
tmp0.copyTo(planes[i](cv::Rect(0, y, sz.width, sz.height - y)));
|
||||||
|
tmp1.copyTo(planes[i](cv::Rect(0, 0, sz.width, y)));
|
||||||
|
|
||||||
|
// horizontal
|
||||||
|
cv::Mat q2(planes[i], cv::Rect(0, 0, sz.width - x, sz.height));
|
||||||
|
cv::Mat q3(planes[i], cv::Rect(sz.width - x, 0, x, sz.height));
|
||||||
|
q2.copyTo(tmp2);
|
||||||
|
q3.copyTo(tmp3);
|
||||||
|
tmp2.copyTo(planes[i](cv::Rect(x, 0, sz.width - x, sz.height)));
|
||||||
|
tmp3.copyTo(planes[i](cv::Rect(0, 0, x, sz.height)));
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::merge(planes, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fftshift(cv::Mat &out)
|
||||||
|
{
|
||||||
|
cv::Size sz = out.size();
|
||||||
|
cv::Point pt(0, 0);
|
||||||
|
pt.x = (int) floor(sz.width / 2.0);
|
||||||
|
pt.y = (int) floor(sz.height / 2.0);
|
||||||
|
circshift(out, pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ifftshift(cv::Mat &out)
|
||||||
|
{
|
||||||
|
cv::Size sz = out.size();
|
||||||
|
cv::Point pt(0, 0);
|
||||||
|
pt.x = (int) ceil(sz.width / 2.0);
|
||||||
|
pt.y = (int) ceil(sz.height / 2.0);
|
||||||
|
circshift(out, pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<cv::Mat> getReferenceTiles(cv::Mat reference_image);
|
||||||
|
|
||||||
|
cv::Mat mergeTiles(std::vector<cv::Mat> tiles, int rows, int cols);
|
||||||
|
|
||||||
|
cv::Mat processChannel( hdrplus::burst& burst_images, \
|
||||||
|
std::vector<std::vector<std::vector<std::pair<int, int>>>>& alignments, \
|
||||||
|
cv::Mat channel_image, \
|
||||||
|
std::vector<cv::Mat> alternate_channel_i_list,\
|
||||||
|
float lambda_shot, \
|
||||||
|
float lambda_read);
|
||||||
|
|
||||||
|
//temporal denoise
|
||||||
|
std::vector<cv::Mat> temporal_denoise(std::vector<cv::Mat> tiles, std::vector<std::vector<cv::Mat>> alt_tiles, std::vector<float> noise_variance, float temporal_factor);
|
||||||
|
std::vector<cv::Mat> spatial_denoise(std::vector<cv::Mat> tiles, int num_alts, std::vector<float> noise_variance, float spatial_factor);
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory> // std::shared_ptr
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include <libraw/libraw.h>
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
class RawpyArgs{
|
||||||
|
public:
|
||||||
|
int demosaic_algorithm = 3;// 3 - AHD interpolation <->int user_qual
|
||||||
|
bool half_size = false;
|
||||||
|
bool use_camera_wb = true;
|
||||||
|
bool use_auto_wb = false;
|
||||||
|
bool no_auto_bright = true;
|
||||||
|
int output_color = LIBRAW_COLORSPACE_sRGB;
|
||||||
|
int gamma[2] = {1,1}; //# gamma correction not applied by rawpy (not quite understand)
|
||||||
|
int output_bps = 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Options{
|
||||||
|
public:
|
||||||
|
std::string input = "";
|
||||||
|
std::string output = "";
|
||||||
|
std::string mode = "full"; //'full' 'align' 'merge' 'finish'
|
||||||
|
int reference = 0;
|
||||||
|
float temporalfactor=75.0;
|
||||||
|
float spatialfactor = 0.1;
|
||||||
|
int ltmGain=-1;
|
||||||
|
double gtmContrast=0.075;
|
||||||
|
int verbose=2; // (0, 1, 2, 3, 4, 5)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Tuning{
|
||||||
|
public:
|
||||||
|
std::string ltmGain = "auto";
|
||||||
|
double gtmContrast = 0.075;
|
||||||
|
std::vector<float> sharpenAmount{1,0.5,0.5};
|
||||||
|
std::vector<float> sharpenSigma{1,2,4};
|
||||||
|
std::vector<float> sharpenThreshold{0.02,0.04,0.06};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parameters{
|
||||||
|
public:
|
||||||
|
std::unordered_map<std::string,bool> flags;
|
||||||
|
|
||||||
|
RawpyArgs rawpyArgs;
|
||||||
|
Options options;
|
||||||
|
Tuning tuning;
|
||||||
|
|
||||||
|
Parameters()
|
||||||
|
{
|
||||||
|
const char* keys[] = {"writeReferenceImage", "writeGammaReference", "writeMergedImage", "writeGammaMerged",
|
||||||
|
"writeShortExposure", "writeLongExposure", "writeFusedExposure", "writeLTMImage",
|
||||||
|
"writeLTMGamma", "writeGTMImage", "writeReferenceFinal", "writeFinalImage"};
|
||||||
|
for (int idx = 0; idx < sizeof(keys) / sizeof(const char*); idx++) {
|
||||||
|
flags[keys[idx]] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
cv::Mat postprocess(std::shared_ptr<LibRaw>& libraw_ptr, RawpyArgs rawpyArgs);
|
||||||
|
void setParams(std::shared_ptr<LibRaw>& libraw_ptr, RawpyArgs rawpyArgs);
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,326 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept> // std::runtime_error
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include <omp.h>
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/63404539/portable-loop-unrolling-with-template-parameter-in-c-with-gcc-icc
|
||||||
|
/// Helper macros for stringification
|
||||||
|
#define TO_STRING_HELPER(X) #X
|
||||||
|
#define TO_STRING(X) TO_STRING_HELPER(X)
|
||||||
|
|
||||||
|
// Define loop unrolling depending on the compiler
|
||||||
|
#if defined(__ICC) || defined(__ICL)
|
||||||
|
#define UNROLL_LOOP(n) _Pragma(TO_STRING(unroll (n)))
|
||||||
|
#elif defined(__clang__)
|
||||||
|
#define UNROLL_LOOP(n) _Pragma(TO_STRING(unroll (n)))
|
||||||
|
#elif defined(__GNUC__) && !defined(__clang__)
|
||||||
|
#define UNROLL_LOOP(n) _Pragma(TO_STRING(GCC unroll (16)))
|
||||||
|
#elif defined(_MSC_BUILD)
|
||||||
|
#pragma message ("Microsoft Visual C++ (MSVC) detected: Loop unrolling not supported!")
|
||||||
|
#define UNROLL_LOOP(n)
|
||||||
|
#else
|
||||||
|
#warning "Unknown compiler: Loop unrolling not supported!"
|
||||||
|
#define UNROLL_LOOP(n)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T, int kernel>
|
||||||
|
cv::Mat box_filter_kxk( const cv::Mat& src_image )
|
||||||
|
{
|
||||||
|
const T* src_image_ptr = (T*)src_image.data;
|
||||||
|
int src_height = src_image.size().height;
|
||||||
|
int src_width = src_image.size().width;
|
||||||
|
int src_step = src_image.step1();
|
||||||
|
|
||||||
|
if ( kernel <= 0 )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return cv::Mat();
|
||||||
|
#else
|
||||||
|
throw std::runtime_error(std::string( __FILE__ ) + "::" + __func__ + " box filter only support kernel size >= 1");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// int(src_height / kernel) = floor(src_height / kernel)
|
||||||
|
// When input size is not multiplier of kernel, take floor
|
||||||
|
cv::Mat dst_image( src_height / kernel, src_width / kernel, src_image.type() );
|
||||||
|
T* dst_image_ptr = (T*)dst_image.data;
|
||||||
|
int dst_height = dst_image.size().height;
|
||||||
|
int dst_width = dst_image.size().width;
|
||||||
|
int dst_step = dst_image.step1();
|
||||||
|
|
||||||
|
for ( int row_i = 0; row_i < dst_height; ++row_i )
|
||||||
|
{
|
||||||
|
for ( int col_i = 0; col_i < dst_width; col_i++ )
|
||||||
|
{
|
||||||
|
// Take ceiling for rounding
|
||||||
|
T box_sum = T( 0 );
|
||||||
|
|
||||||
|
UNROLL_LOOP( kernel )
|
||||||
|
for ( int kernel_row_i = 0; kernel_row_i < kernel; ++kernel_row_i )
|
||||||
|
{
|
||||||
|
UNROLL_LOOP( kernel )
|
||||||
|
for ( int kernel_col_i = 0; kernel_col_i < kernel; ++kernel_col_i )
|
||||||
|
{
|
||||||
|
box_sum += src_image_ptr[ ( row_i * kernel + kernel_row_i ) * src_step + ( col_i * kernel + kernel_col_i ) ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Average by taking ceiling
|
||||||
|
T box_avg = box_sum / T( kernel * kernel );
|
||||||
|
dst_image_ptr[ row_i * dst_step + col_i ] = box_avg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T, int kernel>
|
||||||
|
cv::Mat downsample_nearest_neighbour( const cv::Mat& src_image )
|
||||||
|
{
|
||||||
|
const T* src_image_ptr = (T*)src_image.data;
|
||||||
|
int src_height = src_image.size().height;
|
||||||
|
int src_width = src_image.size().width;
|
||||||
|
int src_step = src_image.step1();
|
||||||
|
|
||||||
|
// int(src_height / kernel) = floor(src_height / kernel)
|
||||||
|
// When input size is not multiplier of kernel, take floor
|
||||||
|
cv::Mat dst_image = cv::Mat( src_height / kernel, src_width / kernel, src_image.type() );
|
||||||
|
T* dst_image_ptr = (T*)dst_image.data;
|
||||||
|
int dst_height = dst_image.size().height;
|
||||||
|
int dst_width = dst_image.size().width;
|
||||||
|
int dst_step = dst_image.step1();
|
||||||
|
|
||||||
|
// -03 should be enough to optimize below code
|
||||||
|
for ( int row_i = 0; row_i < dst_height; row_i++ )
|
||||||
|
{
|
||||||
|
UNROLL_LOOP( 32 )
|
||||||
|
for ( int col_i = 0; col_i < dst_width; col_i++ )
|
||||||
|
{
|
||||||
|
dst_image_ptr[ row_i * dst_step + col_i ] = \
|
||||||
|
src_image_ptr[ (row_i * kernel) * src_step + (col_i * kernel) ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
void print_cvmat( cv::Mat image )
|
||||||
|
{
|
||||||
|
const T* img_ptr = (const T*)image.data;
|
||||||
|
int height = image.size().height;
|
||||||
|
int width = image.size().width;
|
||||||
|
int step = image.step1();
|
||||||
|
int chns = image.channels();
|
||||||
|
|
||||||
|
printf("print_cvmat()::Image of size height = %d, width = %d, step = %d\n", \
|
||||||
|
height, width, step );
|
||||||
|
|
||||||
|
if ( chns == 1 )
|
||||||
|
{
|
||||||
|
for ( int row_i = 0; row_i < height; ++row_i )
|
||||||
|
{
|
||||||
|
int row_i_offset = row_i * step;
|
||||||
|
for ( int col_i = 0; col_i < width; ++col_i )
|
||||||
|
{
|
||||||
|
printf("%3.d ", img_ptr[ row_i_offset + col_i ]);
|
||||||
|
//printf("%3.d ", int( image.at<T>( row_i, col_i ) ) );
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( chns == 3 )
|
||||||
|
{
|
||||||
|
for ( int row_i = 0; row_i < height; ++row_i )
|
||||||
|
{
|
||||||
|
int row_i_offset = row_i * step;
|
||||||
|
for ( int col_i = 0; col_i < width; ++col_i )
|
||||||
|
{
|
||||||
|
printf("[%3.d, %3.d, %3.d] ", img_ptr[ row_i_offset + col_i * 3 + 0 ], \
|
||||||
|
img_ptr[ row_i_offset + col_i * 3 + 1 ], \
|
||||||
|
img_ptr[ row_i_offset + col_i * 3 + 2 ] );
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("cv::Mat number of channel currently not support to print\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extract RGB channel seprately from bayer image
|
||||||
|
*
|
||||||
|
* @tparam T data tyoe of bayer image.
|
||||||
|
* @return vector of RGB image. OpenCV internally maintain reference count.
|
||||||
|
* Thus this step won't create deep copy overhead.
|
||||||
|
*
|
||||||
|
* @example extract_rgb_from_bayer<uint16_t>( bayer_img, rgb_vector_container );
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
void extract_rgb_from_bayer( const cv::Mat& bayer_img, \
|
||||||
|
cv::Mat& img_ch1, cv::Mat& img_ch2, cv::Mat& img_ch3, cv::Mat& img_ch4 )
|
||||||
|
{
|
||||||
|
const T* bayer_img_ptr = (const T*)bayer_img.data;
|
||||||
|
int bayer_width = bayer_img.size().width;
|
||||||
|
int bayer_height = bayer_img.size().height;
|
||||||
|
int bayer_step = bayer_img.step1();
|
||||||
|
|
||||||
|
if ( bayer_width % 2 != 0 || bayer_height % 2 != 0 )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("Bayer image data size incorrect, must be multiplier of 2\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB image is half the size of bayer image
|
||||||
|
int rgb_width = bayer_width / 2;
|
||||||
|
int rgb_height = bayer_height / 2;
|
||||||
|
img_ch1.create( rgb_height, rgb_width, bayer_img.type() );
|
||||||
|
img_ch2.create( rgb_height, rgb_width, bayer_img.type() );
|
||||||
|
img_ch3.create( rgb_height, rgb_width, bayer_img.type() );
|
||||||
|
img_ch4.create( rgb_height, rgb_width, bayer_img.type() );
|
||||||
|
int rgb_step = img_ch1.step1();
|
||||||
|
|
||||||
|
T* img_ch1_ptr = (T*)img_ch1.data;
|
||||||
|
T* img_ch2_ptr = (T*)img_ch2.data;
|
||||||
|
T* img_ch3_ptr = (T*)img_ch3.data;
|
||||||
|
T* img_ch4_ptr = (T*)img_ch4.data;
|
||||||
|
|
||||||
|
#pragma omp parallel for
|
||||||
|
for ( int rgb_row_i = 0; rgb_row_i < rgb_height; rgb_row_i++ )
|
||||||
|
{
|
||||||
|
int rgb_row_i_offset = rgb_row_i * rgb_step;
|
||||||
|
|
||||||
|
// Every RGB row corresbonding to two Bayer image row
|
||||||
|
int bayer_row_i_offset0 = ( rgb_row_i * 2 + 0 ) * bayer_step; // For RG
|
||||||
|
int bayer_row_i_offset1 = ( rgb_row_i * 2 + 1 ) * bayer_step; // For GB
|
||||||
|
|
||||||
|
for ( int rgb_col_j = 0; rgb_col_j < rgb_width; rgb_col_j++ )
|
||||||
|
{
|
||||||
|
// img_ch1/2/3/4 : (0,0), (1,0), (0,1), (1,1)
|
||||||
|
int bayer_col_i_offset0 = rgb_col_j * 2 + 0;
|
||||||
|
int bayer_col_i_offset1 = rgb_col_j * 2 + 1;
|
||||||
|
|
||||||
|
img_ch1_ptr[ rgb_row_i_offset + rgb_col_j ] = bayer_img_ptr[ bayer_row_i_offset0 + bayer_col_i_offset0 ];
|
||||||
|
img_ch3_ptr[ rgb_row_i_offset + rgb_col_j ] = bayer_img_ptr[ bayer_row_i_offset0 + bayer_col_i_offset1 ];
|
||||||
|
img_ch2_ptr[ rgb_row_i_offset + rgb_col_j ] = bayer_img_ptr[ bayer_row_i_offset1 + bayer_col_i_offset0 ];
|
||||||
|
img_ch4_ptr[ rgb_row_i_offset + rgb_col_j ] = bayer_img_ptr[ bayer_row_i_offset1 + bayer_col_i_offset1 ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert RGB image to gray image through same weight linear combination.
|
||||||
|
* Also support implicit data type conversion.
|
||||||
|
*
|
||||||
|
* @tparam RGB_DTYPE rgb image type (e.g. uint16_t)
|
||||||
|
* @tparam GRAY_DTYPE gray image type (e.g. uint16_t)
|
||||||
|
* @tparam GRAY_CVTYPE opencv gray image type
|
||||||
|
*/
|
||||||
|
template< typename RGB_DTYPE, typename GRAY_DTYPE, int GRAY_CVTYPE >
|
||||||
|
cv::Mat rgb_2_gray( const cv::Mat& rgb_img )
|
||||||
|
{
|
||||||
|
const RGB_DTYPE* rgb_img_ptr = (const RGB_DTYPE*)rgb_img.data;
|
||||||
|
int img_width = rgb_img.size().width;
|
||||||
|
int img_height = rgb_img.size().height;
|
||||||
|
int rgb_img_step = rgb_img.step1();
|
||||||
|
|
||||||
|
// Create output gray cv::Mat
|
||||||
|
cv::Mat gray_img( img_height, img_width, GRAY_CVTYPE );
|
||||||
|
GRAY_DTYPE* gray_img_ptr = (GRAY_DTYPE*)gray_img.data;
|
||||||
|
int gray_img_step = gray_img.step1();
|
||||||
|
|
||||||
|
#pragma omp parallel for
|
||||||
|
for ( int row_i = 0; row_i < img_height; row_i++ )
|
||||||
|
{
|
||||||
|
int rgb_row_i_offset = row_i * rgb_img_step;
|
||||||
|
int gray_row_i_offset = row_i * gray_img_step;
|
||||||
|
|
||||||
|
UNROLL_LOOP( 32 ) // multiplier of cache line size
|
||||||
|
for ( int col_j = 0; col_j < img_width; col_j++ )
|
||||||
|
{
|
||||||
|
GRAY_DTYPE avg_ij(0);
|
||||||
|
|
||||||
|
avg_ij += rgb_img_ptr[ rgb_row_i_offset + (col_j * 3 + 0) ];
|
||||||
|
avg_ij += rgb_img_ptr[ rgb_row_i_offset + (col_j * 3 + 1) ];
|
||||||
|
avg_ij += rgb_img_ptr[ rgb_row_i_offset + (col_j * 3 + 2) ];
|
||||||
|
|
||||||
|
avg_ij /= 3;
|
||||||
|
|
||||||
|
gray_img_ptr[ gray_row_i_offset + col_j ] = avg_ij;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenCV use reference count. Thus return won't create deep copy
|
||||||
|
return gray_img;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void print_tile( const cv::Mat& img, int tile_size, int start_idx_row, int start_idx_col )
|
||||||
|
{
|
||||||
|
const T* img_ptr = (T*)img.data;
|
||||||
|
int src_step = img.step1();
|
||||||
|
|
||||||
|
for ( int row = start_idx_row; row < tile_size + start_idx_row; ++row )
|
||||||
|
{
|
||||||
|
const T* img_ptr_row = img_ptr + row * src_step;
|
||||||
|
for ( int col = start_idx_col; col < tile_size + start_idx_col; ++col )
|
||||||
|
{
|
||||||
|
printf("%u ", img_ptr_row[ col ] );
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template< typename T>
|
||||||
|
void print_img( const cv::Mat& img, int img_height = -1, int img_width = -1 )
|
||||||
|
{
|
||||||
|
const T* img_ptr = (T*)img.data;
|
||||||
|
if ( img_height == -1 && img_width == -1 )
|
||||||
|
{
|
||||||
|
img_height = img.size().height;
|
||||||
|
img_width = img.size().width;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
img_height = std::min( img.size().height, img_height );
|
||||||
|
img_width = std::min( img.size().width, img_width );
|
||||||
|
}
|
||||||
|
printf("Image size (h=%d, w=%d), Print range (h=0-%d, w=0-%d)]\n", \
|
||||||
|
img.size().height, img.size().width, img_height, img_width );
|
||||||
|
|
||||||
|
int img_step = img.step1();
|
||||||
|
|
||||||
|
for ( int row = 0; row < img_height; ++row )
|
||||||
|
{
|
||||||
|
const T* img_ptr_row = img_ptr + row * img_step;
|
||||||
|
for ( int col = 0; col < img_width; ++col )
|
||||||
|
{
|
||||||
|
printf("%u ", img_ptr_row[ col ]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,990 @@
|
|||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <limits>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <utility> // std::make_pair
|
||||||
|
#include <stdexcept> // std::runtime_error
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include <omp.h>
|
||||||
|
#include "hdrplus/align.h"
|
||||||
|
#include "hdrplus/burst.h"
|
||||||
|
#include "hdrplus/utility.h"
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
// Function declration
|
||||||
|
static void build_per_grayimg_pyramid( \
|
||||||
|
std::vector<cv::Mat>& images_pyramid, \
|
||||||
|
const cv::Mat& src_image, \
|
||||||
|
const std::vector<int>& inv_scale_factors );
|
||||||
|
|
||||||
|
|
||||||
|
template< int pyramid_scale_factor_prev_curr, int tilesize_scale_factor_prev_curr, int tile_size >
|
||||||
|
static void build_upsampled_prev_aligement( \
|
||||||
|
const std::vector<std::vector<std::pair<int, int>>>& src_alignment, \
|
||||||
|
std::vector<std::vector<std::pair<int, int>>>& dst_alignment, \
|
||||||
|
int num_tiles_h, int num_tiles_w, \
|
||||||
|
const cv::Mat& ref_img, const cv::Mat& alt_img, \
|
||||||
|
bool consider_nbr = false );
|
||||||
|
|
||||||
|
|
||||||
|
template< typename data_type, typename return_type, int tile_size >
|
||||||
|
static unsigned long long l1_distance( const cv::Mat& img1, const cv::Mat& img2, \
|
||||||
|
int img1_tile_row_start_idx, int img1_tile_col_start_idx, \
|
||||||
|
int img2_tile_row_start_idx, int img2_tile_col_start_idx );
|
||||||
|
|
||||||
|
|
||||||
|
template< typename data_type, typename return_type, int tile_size >
|
||||||
|
static return_type l2_distance( const cv::Mat& img1, const cv::Mat& img2, \
|
||||||
|
int img1_tile_row_start_idx, int img1_tile_col_start_idx, \
|
||||||
|
int img2_tile_row_start_idx, int img2_tile_col_start_idx );
|
||||||
|
|
||||||
|
|
||||||
|
static void align_image_level( \
|
||||||
|
const cv::Mat& ref_img, \
|
||||||
|
const cv::Mat& alt_img, \
|
||||||
|
std::vector<std::vector<std::pair<int, int>>>& prev_aligement, \
|
||||||
|
std::vector<std::vector<std::pair<int, int>>>& curr_alignment, \
|
||||||
|
int scale_factor_prev_curr, \
|
||||||
|
int curr_tile_size, \
|
||||||
|
int prev_tile_size, \
|
||||||
|
int search_radiou, \
|
||||||
|
int distance_type );
|
||||||
|
|
||||||
|
|
||||||
|
// Function Implementations
|
||||||
|
|
||||||
|
|
||||||
|
// static function only visible within file
|
||||||
|
static void build_per_grayimg_pyramid( \
|
||||||
|
std::vector<cv::Mat>& images_pyramid, \
|
||||||
|
const cv::Mat& src_image, \
|
||||||
|
const std::vector<int>& inv_scale_factors )
|
||||||
|
{
|
||||||
|
// #ifndef NDEBUG
|
||||||
|
// printf("%s::%s build_per_grayimg_pyramid start with scale factor : ", __FILE__, __func__ );
|
||||||
|
// for ( int i = 0; i < inv_scale_factors.size(); ++i )
|
||||||
|
// {
|
||||||
|
// printf("%d ", inv_scale_factors.at( i ));
|
||||||
|
// }
|
||||||
|
// printf("\n");
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
images_pyramid.resize( inv_scale_factors.size() );
|
||||||
|
|
||||||
|
for ( size_t i = 0; i < inv_scale_factors.size(); ++i )
|
||||||
|
{
|
||||||
|
cv::Mat blur_image;
|
||||||
|
cv::Mat downsample_image;
|
||||||
|
|
||||||
|
switch ( inv_scale_factors[ i ] )
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
images_pyramid[ i ] = src_image.clone();
|
||||||
|
// cv::Mat use reference count, will not create deep copy
|
||||||
|
downsample_image = src_image;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// printf("(2) downsample with gaussian sigma %.2f", inv_scale_factors[ i ] * 0.5 );
|
||||||
|
// // Gaussian blur
|
||||||
|
cv::GaussianBlur( images_pyramid.at( i-1 ), blur_image, cv::Size(0, 0), inv_scale_factors[ i ] * 0.5 );
|
||||||
|
|
||||||
|
// // Downsample
|
||||||
|
downsample_image = downsample_nearest_neighbour<uint16_t, 2>( blur_image );
|
||||||
|
// downsample_image = downsample_nearest_neighbour<uint16_t, 2>( images_pyramid.at( i-1 ) );
|
||||||
|
|
||||||
|
// Add
|
||||||
|
images_pyramid.at( i ) = downsample_image.clone();
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
// printf("(4) downsample with gaussian sigma %.2f", inv_scale_factors[ i ] * 0.5 );
|
||||||
|
cv::GaussianBlur( images_pyramid.at( i-1 ), blur_image, cv::Size(0, 0), inv_scale_factors[ i ] * 0.5 );
|
||||||
|
downsample_image = downsample_nearest_neighbour<uint16_t, 4>( blur_image );
|
||||||
|
// downsample_image = downsample_nearest_neighbour<uint16_t, 4>( images_pyramid.at( i-1 ) );
|
||||||
|
images_pyramid.at( i ) = downsample_image.clone();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("inv scale factor " + std::to_string( inv_scale_factors[ i ]) + "invalid" );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool operator!=( const std::pair<int, int>& lhs, const std::pair<int, int>& rhs )
|
||||||
|
{
|
||||||
|
return lhs.first != rhs.first || lhs.second != rhs.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template< int pyramid_scale_factor_prev_curr, int tilesize_scale_factor_prev_curr, int tile_size >
|
||||||
|
static void build_upsampled_prev_aligement( \
|
||||||
|
const std::vector<std::vector<std::pair<int, int>>>& src_alignment, \
|
||||||
|
std::vector<std::vector<std::pair<int, int>>>& dst_alignment, \
|
||||||
|
int num_tiles_h, int num_tiles_w, \
|
||||||
|
const cv::Mat& ref_img, const cv::Mat& alt_img, \
|
||||||
|
bool consider_nbr )
|
||||||
|
{
|
||||||
|
int src_num_tiles_h = src_alignment.size();
|
||||||
|
int src_num_tiles_w = src_alignment[ 0 ].size();
|
||||||
|
|
||||||
|
constexpr int repeat_factor = pyramid_scale_factor_prev_curr / tilesize_scale_factor_prev_curr;
|
||||||
|
|
||||||
|
// printf("build_upsampled_prev_aligement with scale factor %d, repeat factor %d, tile size factor %d\n", \
|
||||||
|
// pyramid_scale_factor_prev_curr, repeat_factor, tilesize_scale_factor_prev_curr );
|
||||||
|
|
||||||
|
int dst_num_tiles_main_h = src_num_tiles_h * repeat_factor;
|
||||||
|
int dst_num_tiles_main_w = src_num_tiles_w * repeat_factor;
|
||||||
|
|
||||||
|
if ( dst_num_tiles_main_h > num_tiles_h || dst_num_tiles_main_w > num_tiles_w )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("current level number of tiles smaller than upsampled tiles\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate data for dst_alignment
|
||||||
|
// NOTE: number of tiles h, number of tiles w might be different from dst_num_tiles_main_h, dst_num_tiles_main_w
|
||||||
|
// For tiles between num_tile_h and dst_num_tiles_main_h, use (0,0)
|
||||||
|
dst_alignment.resize( num_tiles_h, std::vector<std::pair<int, int>>( num_tiles_w, std::pair<int, int>(0, 0) ) );
|
||||||
|
|
||||||
|
// Upsample alignment
|
||||||
|
#pragma omp parallel for collapse(2)
|
||||||
|
for ( int row_i = 0; row_i < src_num_tiles_h; row_i++ )
|
||||||
|
{
|
||||||
|
for ( int col_i = 0; col_i < src_num_tiles_w; col_i++ )
|
||||||
|
{
|
||||||
|
// Scale alignment
|
||||||
|
std::pair<int, int> align_i = src_alignment[ row_i ][ col_i ];
|
||||||
|
align_i.first *= pyramid_scale_factor_prev_curr;
|
||||||
|
align_i.second *= pyramid_scale_factor_prev_curr;
|
||||||
|
|
||||||
|
// repeat
|
||||||
|
UNROLL_LOOP( repeat_factor )
|
||||||
|
for ( int repeat_row_i = 0; repeat_row_i < repeat_factor; ++repeat_row_i )
|
||||||
|
{
|
||||||
|
int repeat_row_i_offset = row_i * repeat_factor + repeat_row_i;
|
||||||
|
UNROLL_LOOP( repeat_factor )
|
||||||
|
for ( int repeat_col_i = 0; repeat_col_i < repeat_factor; ++repeat_col_i )
|
||||||
|
{
|
||||||
|
int repeat_col_i_offset = col_i * repeat_factor + repeat_col_i;
|
||||||
|
dst_alignment[ repeat_row_i_offset ][ repeat_col_i_offset ] = align_i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( consider_nbr )
|
||||||
|
{
|
||||||
|
// Copy consurtctor
|
||||||
|
std::vector<std::vector<std::pair<int, int>>> upsampled_alignment{ dst_alignment };
|
||||||
|
|
||||||
|
// Distance function
|
||||||
|
unsigned long long (*distance_func_ptr)(const cv::Mat&, const cv::Mat&, int, int, int, int) = \
|
||||||
|
&l1_distance<uint16_t, unsigned long long, tile_size>;
|
||||||
|
|
||||||
|
#pragma omp parallel for collapse(2)
|
||||||
|
for ( int tile_row_i = 0; tile_row_i < num_tiles_h; tile_row_i++ )
|
||||||
|
{
|
||||||
|
for ( int tile_col_i = 0; tile_col_i < num_tiles_w; tile_col_i++ )
|
||||||
|
{
|
||||||
|
const auto& curr_align_i = upsampled_alignment[ tile_row_i ][ tile_col_i ];
|
||||||
|
|
||||||
|
// Container for nbr alignment pair
|
||||||
|
std::vector<std::pair<int, int>> nbrs_align_i;
|
||||||
|
|
||||||
|
// Consider 4 neighbour's alignment
|
||||||
|
// Only compute distance if alignment is different
|
||||||
|
if ( tile_col_i > 0 )
|
||||||
|
{
|
||||||
|
const auto& nbr1_align_i = upsampled_alignment[ tile_row_i + 0 ][ tile_col_i - 1 ];
|
||||||
|
if ( curr_align_i != nbr1_align_i ) nbrs_align_i.emplace_back( nbr1_align_i );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( tile_col_i < num_tiles_w - 1 )
|
||||||
|
{
|
||||||
|
const auto& nbr2_align_i = upsampled_alignment[ tile_row_i + 0 ][ tile_col_i + 1 ];
|
||||||
|
if ( curr_align_i != nbr2_align_i ) nbrs_align_i.emplace_back( nbr2_align_i );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( tile_row_i > 0 )
|
||||||
|
{
|
||||||
|
const auto& nbr3_align_i = upsampled_alignment[ tile_row_i - 1 ][ tile_col_i + 0 ];
|
||||||
|
if ( curr_align_i != nbr3_align_i ) nbrs_align_i.emplace_back( nbr3_align_i );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( tile_row_i < num_tiles_h - 1 )
|
||||||
|
{
|
||||||
|
const auto& nbr4_align_i = upsampled_alignment[ tile_row_i + 1 ][ tile_col_i + 0 ];
|
||||||
|
if ( curr_align_i != nbr4_align_i ) nbrs_align_i.emplace_back( nbr4_align_i );
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a nbr alignment that need to be considered. Compute distance
|
||||||
|
if ( ! nbrs_align_i.empty() )
|
||||||
|
{
|
||||||
|
int ref_tile_row_start_idx_i = tile_row_i * tile_size / 2;
|
||||||
|
int ref_tile_col_start_idx_i = tile_col_i * tile_size / 2;
|
||||||
|
|
||||||
|
// curr_align_i's distance
|
||||||
|
auto curr_align_i_distance = distance_func_ptr(
|
||||||
|
ref_img, alt_img, \
|
||||||
|
ref_tile_row_start_idx_i, \
|
||||||
|
ref_tile_col_start_idx_i, \
|
||||||
|
ref_tile_row_start_idx_i + curr_align_i.first, \
|
||||||
|
ref_tile_col_start_idx_i + curr_align_i.second );
|
||||||
|
|
||||||
|
for ( const auto& nbr_align_i : nbrs_align_i )
|
||||||
|
{
|
||||||
|
auto nbr_align_i_distance = distance_func_ptr(
|
||||||
|
ref_img, alt_img, \
|
||||||
|
ref_tile_row_start_idx_i, \
|
||||||
|
ref_tile_col_start_idx_i, \
|
||||||
|
ref_tile_row_start_idx_i + nbr_align_i.first, \
|
||||||
|
ref_tile_col_start_idx_i + nbr_align_i.second );
|
||||||
|
|
||||||
|
if ( nbr_align_i_distance < curr_align_i_distance )
|
||||||
|
{
|
||||||
|
#ifdef NDEBUG
|
||||||
|
printf("tile [%d, %d] update align, prev align (%d, %d) curr align (%d, %d), prev distance %d curr distance %d\n", \
|
||||||
|
tile_row_i, tile_col_i, \
|
||||||
|
curr_align_i.first, curr_align_i.second, \
|
||||||
|
nbr_align_i.first, nbr_align_i.second, \
|
||||||
|
int(curr_align_i_distance), int(nbr_align_i_distance) );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
dst_alignment[ tile_row_i ][ tile_col_i ] = nbr_align_i;
|
||||||
|
curr_align_i_distance = nbr_align_i_distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set tilesize as template argument for better compiler optimization result.
|
||||||
|
template< typename data_type, typename return_type, int tile_size >
|
||||||
|
static unsigned long long l1_distance( const cv::Mat& img1, const cv::Mat& img2, \
|
||||||
|
int img1_tile_row_start_idx, int img1_tile_col_start_idx, \
|
||||||
|
int img2_tile_row_start_idx, int img2_tile_col_start_idx )
|
||||||
|
{
|
||||||
|
#define CUSTOME_ABS( x ) ( x ) > 0 ? ( x ) : - ( x )
|
||||||
|
|
||||||
|
const data_type* img1_ptr = (const data_type*)img1.data;
|
||||||
|
const data_type* img2_ptr = (const data_type*)img2.data;
|
||||||
|
|
||||||
|
int img1_step = img1.step1();
|
||||||
|
int img2_step = img2.step1();
|
||||||
|
|
||||||
|
int img1_width = img1.size().width;
|
||||||
|
int img1_height = img1.size().height;
|
||||||
|
|
||||||
|
int img2_width = img2.size().width;
|
||||||
|
int img2_height = img2.size().height;
|
||||||
|
|
||||||
|
// Range check for safety
|
||||||
|
if ( img1_tile_row_start_idx < 0 || img1_tile_row_start_idx > img1_height - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("l1 distance img1_tile_row_start_idx" + std::to_string( img1_tile_row_start_idx ) + \
|
||||||
|
" out of valid range (0, " + std::to_string( img1_height - tile_size ) + ")\n" );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( img1_tile_col_start_idx < 0 || img1_tile_col_start_idx > img1_width - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("l1 distance img1_tile_col_start_idx" + std::to_string( img1_tile_col_start_idx ) + \
|
||||||
|
" out of valid range (0, " + std::to_string( img1_width - tile_size ) + ")\n" );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( img2_tile_row_start_idx < 0 || img2_tile_row_start_idx > img2_height - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("l1 distance img2_tile_row_start_idx out of valid range\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( img2_tile_col_start_idx < 0 || img2_tile_col_start_idx > img2_width - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("l1 distance img2_tile_col_start_idx out of valid range\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return_type sum(0);
|
||||||
|
|
||||||
|
UNROLL_LOOP( tile_size )
|
||||||
|
for ( int row_i = 0; row_i < tile_size; ++row_i )
|
||||||
|
{
|
||||||
|
const data_type* img1_ptr_row_i = img1_ptr + (img1_tile_row_start_idx + row_i) * img1_step + img1_tile_col_start_idx;
|
||||||
|
const data_type* img2_ptr_row_i = img2_ptr + (img2_tile_row_start_idx + row_i) * img2_step + img2_tile_col_start_idx;
|
||||||
|
|
||||||
|
UNROLL_LOOP( tile_size )
|
||||||
|
for ( int col_i = 0; col_i < tile_size; ++col_i )
|
||||||
|
{
|
||||||
|
data_type l1 = CUSTOME_ABS( img1_ptr_row_i[ col_i ] - img2_ptr_row_i[ col_i ] );
|
||||||
|
sum += l1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CUSTOME_ABS
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template< typename data_type, typename return_type, int tile_size >
|
||||||
|
static return_type l2_distance( const cv::Mat& img1, const cv::Mat& img2, \
|
||||||
|
int img1_tile_row_start_idx, int img1_tile_col_start_idx, \
|
||||||
|
int img2_tile_row_start_idx, int img2_tile_col_start_idx )
|
||||||
|
{
|
||||||
|
#define CUSTOME_ABS( x ) ( x ) > 0 ? ( x ) : - ( x )
|
||||||
|
|
||||||
|
const data_type* img1_ptr = (const data_type*)img1.data;
|
||||||
|
const data_type* img2_ptr = (const data_type*)img2.data;
|
||||||
|
|
||||||
|
int img1_step = img1.step1();
|
||||||
|
int img2_step = img2.step1();
|
||||||
|
|
||||||
|
int img1_width = img1.size().width;
|
||||||
|
int img1_height = img1.size().height;
|
||||||
|
|
||||||
|
int img2_width = img2.size().width;
|
||||||
|
int img2_height = img2.size().height;
|
||||||
|
|
||||||
|
// Range check for safety
|
||||||
|
if ( img1_tile_row_start_idx < 0 || img1_tile_row_start_idx > img1_height - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("l2 distance img1_tile_row_start_idx" + std::to_string( img1_tile_row_start_idx ) + \
|
||||||
|
" out of valid range (0, " + std::to_string( img1_height - tile_size ) + ")\n" );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( img1_tile_col_start_idx < 0 || img1_tile_col_start_idx > img1_width - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("l2 distance img1_tile_col_start_idx" + std::to_string( img1_tile_col_start_idx ) + \
|
||||||
|
" out of valid range (0, " + std::to_string( img1_width - tile_size ) + ")\n" );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( img2_tile_row_start_idx < 0 || img2_tile_row_start_idx > img2_height - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("l2 distance img2_tile_row_start_idx out of valid range\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( img2_tile_col_start_idx < 0 || img2_tile_col_start_idx > img2_width - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("l2 distance img2_tile_col_start_idx out of valid range\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// printf("Search two tile with ref : \n");
|
||||||
|
// print_tile<data_type>( img1, tile_size, img1_tile_row_start_idx, img1_tile_col_start_idx );
|
||||||
|
// printf("Search two tile with alt :\n");
|
||||||
|
// print_tile<data_type>( img2, tile_size, img2_tile_row_start_idx, img2_tile_col_start_idx );
|
||||||
|
|
||||||
|
return_type sum(0);
|
||||||
|
|
||||||
|
UNROLL_LOOP( tile_size )
|
||||||
|
for ( int row_i = 0; row_i < tile_size; ++row_i )
|
||||||
|
{
|
||||||
|
const data_type* img1_ptr_row_i = img1_ptr + (img1_tile_row_start_idx + row_i) * img1_step + img1_tile_col_start_idx;
|
||||||
|
const data_type* img2_ptr_row_i = img2_ptr + (img2_tile_row_start_idx + row_i) * img2_step + img2_tile_col_start_idx;
|
||||||
|
|
||||||
|
UNROLL_LOOP( tile_size )
|
||||||
|
for ( int col_i = 0; col_i < tile_size; ++col_i )
|
||||||
|
{
|
||||||
|
data_type l1 = CUSTOME_ABS( img1_ptr_row_i[ col_i ] - img2_ptr_row_i[ col_i ] );
|
||||||
|
sum += ( l1 * l1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CUSTOME_ABS
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T, int tile_size>
|
||||||
|
static cv::Mat extract_img_tile( const cv::Mat& img, int img_tile_row_start_idx, int img_tile_col_start_idx )
|
||||||
|
{
|
||||||
|
const T* img_ptr = (const T*)img.data;
|
||||||
|
int img_width = img.size().width;
|
||||||
|
int img_height = img.size().height;
|
||||||
|
int img_step = img.step1();
|
||||||
|
|
||||||
|
if ( img_tile_row_start_idx < 0 || img_tile_row_start_idx > img_height - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return cv::Mat();
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("extract_img_tile img_tile_row_start_idx " + std::to_string( img_tile_row_start_idx ) + \
|
||||||
|
" out of valid range (0, " + std::to_string( img_height - tile_size ) + ")\n" );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( img_tile_col_start_idx < 0 || img_tile_col_start_idx > img_width - tile_size )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return cv::Mat();
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("extract_img_tile img_tile_col_start_idx " + std::to_string( img_tile_col_start_idx ) + \
|
||||||
|
" out of valid range (0, " + std::to_string( img_width - tile_size ) + ")\n" );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat img_tile( tile_size, tile_size, img.type() );
|
||||||
|
T* img_tile_ptr = (T*)img_tile.data;
|
||||||
|
int img_tile_step = img_tile.step1();
|
||||||
|
|
||||||
|
UNROLL_LOOP( tile_size )
|
||||||
|
for ( int row_i = 0; row_i < tile_size; ++row_i )
|
||||||
|
{
|
||||||
|
const T* img_ptr_row_i = img_ptr + img_step * ( img_tile_row_start_idx + row_i );
|
||||||
|
T* img_tile_ptr_row_i = img_tile_ptr + img_tile_step * row_i;
|
||||||
|
|
||||||
|
UNROLL_LOOP( tile_size )
|
||||||
|
for ( int col_i = 0; col_i < tile_size; ++col_i )
|
||||||
|
{
|
||||||
|
img_tile_ptr_row_i[ col_i ] = img_ptr_row_i[ img_tile_col_start_idx + col_i ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return img_tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void align_image_level( \
|
||||||
|
const cv::Mat& ref_img, \
|
||||||
|
const cv::Mat& alt_img, \
|
||||||
|
std::vector<std::vector<std::pair<int, int>>>& prev_aligement, \
|
||||||
|
std::vector<std::vector<std::pair<int, int>>>& curr_alignment, \
|
||||||
|
int scale_factor_prev_curr, \
|
||||||
|
int curr_tile_size, \
|
||||||
|
int prev_tile_size, \
|
||||||
|
int search_radiou, \
|
||||||
|
int distance_type )
|
||||||
|
{
|
||||||
|
// Every align image level share the same distance function.
|
||||||
|
// Use function ptr to reduce if else overhead inside for loop
|
||||||
|
unsigned long long (*distance_func_ptr)(const cv::Mat&, const cv::Mat&, int, int, int, int) = nullptr;
|
||||||
|
|
||||||
|
if ( distance_type == 1 ) // l1 distance
|
||||||
|
{
|
||||||
|
if ( curr_tile_size == 8 )
|
||||||
|
{
|
||||||
|
distance_func_ptr = &l1_distance<uint16_t, unsigned long long, 8>;
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size == 16 )
|
||||||
|
{
|
||||||
|
distance_func_ptr = &l1_distance<uint16_t, unsigned long long, 16>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( distance_type == 2 ) // l2 distance
|
||||||
|
{
|
||||||
|
if ( curr_tile_size == 8 )
|
||||||
|
{
|
||||||
|
distance_func_ptr = &l2_distance<uint16_t, unsigned long long, 8>;
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size == 16 )
|
||||||
|
{
|
||||||
|
distance_func_ptr = &l2_distance<uint16_t, unsigned long long, 16>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every level share the same upsample function
|
||||||
|
void (*upsample_alignment_func_ptr)(const std::vector<std::vector<std::pair<int, int>>>&, \
|
||||||
|
std::vector<std::vector<std::pair<int, int>>>&, \
|
||||||
|
int, int, const cv::Mat&, const cv::Mat&, bool) = nullptr;
|
||||||
|
if ( scale_factor_prev_curr == 2 )
|
||||||
|
{
|
||||||
|
if ( curr_tile_size / prev_tile_size == 2 )
|
||||||
|
{
|
||||||
|
if ( curr_tile_size == 8 )
|
||||||
|
{
|
||||||
|
upsample_alignment_func_ptr = &build_upsampled_prev_aligement<2, 2, 8>;
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size == 16 )
|
||||||
|
{
|
||||||
|
upsample_alignment_func_ptr = &build_upsampled_prev_aligement<2, 2, 16>;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("Something wrong with upsampling function setting\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size / prev_tile_size == 1 )
|
||||||
|
{
|
||||||
|
if ( curr_tile_size == 8 )
|
||||||
|
{
|
||||||
|
upsample_alignment_func_ptr = &build_upsampled_prev_aligement<2, 1, 8>;
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size == 16 )
|
||||||
|
{
|
||||||
|
upsample_alignment_func_ptr = &build_upsampled_prev_aligement<2, 1, 16>;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("Something wrong with upsampling function setting\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("Something wrong with upsampling function setting\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( scale_factor_prev_curr == 4 )
|
||||||
|
{
|
||||||
|
if ( curr_tile_size / prev_tile_size == 2 )
|
||||||
|
{
|
||||||
|
if ( curr_tile_size == 8 )
|
||||||
|
{
|
||||||
|
upsample_alignment_func_ptr = &build_upsampled_prev_aligement<4, 2, 8>;
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size == 16 )
|
||||||
|
{
|
||||||
|
upsample_alignment_func_ptr = &build_upsampled_prev_aligement<4, 2, 16>;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("Something wrong with upsampling function setting\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size / prev_tile_size == 1 )
|
||||||
|
{
|
||||||
|
if ( curr_tile_size == 8 )
|
||||||
|
{
|
||||||
|
upsample_alignment_func_ptr = &build_upsampled_prev_aligement<4, 1, 8>;
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size == 16 )
|
||||||
|
{
|
||||||
|
upsample_alignment_func_ptr = &build_upsampled_prev_aligement<4, 1, 16>;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("Something wrong with upsampling function setting\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("Something wrong with upsampling function setting\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Function to extract reference image tile for memory cache
|
||||||
|
cv::Mat (*extract_ref_img_tile)(const cv::Mat&, int, int) = nullptr;
|
||||||
|
if ( curr_tile_size == 8 )
|
||||||
|
{
|
||||||
|
extract_ref_img_tile = &extract_img_tile<uint16_t, 8>;
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size == 16 )
|
||||||
|
{
|
||||||
|
extract_ref_img_tile = &extract_img_tile<uint16_t, 16>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to extract search image tile for memory cache
|
||||||
|
cv::Mat (*extract_alt_img_search)(const cv::Mat&, int, int) = nullptr;
|
||||||
|
if ( curr_tile_size == 8 )
|
||||||
|
{
|
||||||
|
if ( search_radiou == 1 )
|
||||||
|
{
|
||||||
|
extract_alt_img_search = &extract_img_tile<uint16_t, 8+1*2>;
|
||||||
|
}
|
||||||
|
else if ( search_radiou == 4 )
|
||||||
|
{
|
||||||
|
extract_alt_img_search = &extract_img_tile<uint16_t, 8+4*2>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( curr_tile_size == 16 )
|
||||||
|
{
|
||||||
|
if ( search_radiou == 1 )
|
||||||
|
{
|
||||||
|
extract_alt_img_search = &extract_img_tile<uint16_t, 16+1*2>;
|
||||||
|
}
|
||||||
|
else if ( search_radiou == 4 )
|
||||||
|
{
|
||||||
|
extract_alt_img_search = &extract_img_tile<uint16_t, 16+4*2>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_tiles_h = ref_img.size().height / (curr_tile_size / 2) - 1;
|
||||||
|
int num_tiles_w = ref_img.size().width / (curr_tile_size / 2 ) - 1;
|
||||||
|
|
||||||
|
/* Upsample pervious layer alignment */
|
||||||
|
std::vector<std::vector<std::pair<int, int>>> upsampled_prev_aligement;
|
||||||
|
|
||||||
|
// Coarsest level
|
||||||
|
// prev_alignment is invalid / empty, construct alignment as (0,0)
|
||||||
|
if ( prev_tile_size == -1 )
|
||||||
|
{
|
||||||
|
upsampled_prev_aligement.resize( num_tiles_h, \
|
||||||
|
std::vector<std::pair<int, int>>( num_tiles_w, std::pair<int, int>(0, 0) ) );
|
||||||
|
}
|
||||||
|
// Upsample previous level alignment
|
||||||
|
else
|
||||||
|
{
|
||||||
|
upsample_alignment_func_ptr( prev_aligement, upsampled_prev_aligement, \
|
||||||
|
num_tiles_h, num_tiles_w, ref_img, alt_img, false );
|
||||||
|
|
||||||
|
// printf("\n!!!!!Upsampled previous alignment\n");
|
||||||
|
// for ( int tile_row = 0; tile_row < int(upsampled_prev_aligement.size()); tile_row++ )
|
||||||
|
// {
|
||||||
|
// for ( int tile_col = 0; tile_col < int(upsampled_prev_aligement.at(0).size()); tile_col++ )
|
||||||
|
// {
|
||||||
|
// const auto tile_start = upsampled_prev_aligement.at( tile_row ).at( tile_col );
|
||||||
|
// printf("up tile (%d, %d) -> start idx (%d, %d)\n", \
|
||||||
|
// tile_row, tile_col, tile_start.first, tile_start.second);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
printf("%s::%s start: \n", __FILE__, __func__ );
|
||||||
|
printf(" scale_factor_prev_curr %d, tile_size %d, prev_tile_size %d, search_radiou %d, distance L%d, \n", \
|
||||||
|
scale_factor_prev_curr, curr_tile_size, prev_tile_size, search_radiou, distance_type );
|
||||||
|
printf(" ref img size h=%d w=%d, alt img size h=%d w=%d, \n", \
|
||||||
|
ref_img.size().height, ref_img.size().width, alt_img.size().height, alt_img.size().width );
|
||||||
|
printf(" num tile h (upsampled) %d, num tile w (upsampled) %d\n", num_tiles_h, num_tiles_w);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// allocate memory for current alignmenr
|
||||||
|
curr_alignment.resize( num_tiles_h, std::vector<std::pair<int, int>>( num_tiles_w, std::pair<int, int>(0, 0) ) );
|
||||||
|
|
||||||
|
/* Pad alternative image */
|
||||||
|
cv::Mat alt_img_pad;
|
||||||
|
cv::copyMakeBorder( alt_img, \
|
||||||
|
alt_img_pad, \
|
||||||
|
search_radiou, search_radiou, search_radiou, search_radiou, \
|
||||||
|
cv::BORDER_CONSTANT, cv::Scalar( UINT_LEAST16_MAX ) );
|
||||||
|
|
||||||
|
// printf("Reference image h=%d, w=%d: \n", ref_img.size().height, ref_img.size().width );
|
||||||
|
// print_img<uint16_t>( ref_img );
|
||||||
|
|
||||||
|
// printf("Alter image pad h=%d, w=%d: \n", alt_img_pad.size().height, alt_img_pad.size().width );
|
||||||
|
// print_img<uint16_t>( alt_img_pad );
|
||||||
|
|
||||||
|
// printf("!! enlarged tile size %d\n", curr_tile_size + 2 * search_radiou );
|
||||||
|
|
||||||
|
int alt_tile_row_idx_max = alt_img_pad.size().height - ( curr_tile_size + 2 * search_radiou );
|
||||||
|
int alt_tile_col_idx_max = alt_img_pad.size().width - ( curr_tile_size + 2 * search_radiou );
|
||||||
|
|
||||||
|
// Dlete below distance vector, this is for debug only
|
||||||
|
std::vector<std::vector<uint16_t>> distances( num_tiles_h, std::vector<uint16_t>( num_tiles_w, 0 ));
|
||||||
|
|
||||||
|
/* Iterate through all reference tile & compute distance */
|
||||||
|
#pragma omp parallel for collapse(2)
|
||||||
|
for ( int ref_tile_row_i = 0; ref_tile_row_i < num_tiles_h; ref_tile_row_i++ )
|
||||||
|
{
|
||||||
|
for ( int ref_tile_col_i = 0; ref_tile_col_i < num_tiles_w; ref_tile_col_i++ )
|
||||||
|
{
|
||||||
|
// Upper left index of reference tile
|
||||||
|
int ref_tile_row_start_idx_i = ref_tile_row_i * curr_tile_size / 2;
|
||||||
|
int ref_tile_col_start_idx_i = ref_tile_col_i * curr_tile_size / 2;
|
||||||
|
|
||||||
|
// printf("\nRef img tile [%d, %d] -> start idx [%d, %d] (row, col)\n", \
|
||||||
|
// ref_tile_row_i, ref_tile_col_i, ref_tile_row_start_idx_i, ref_tile_col_start_idx_i );
|
||||||
|
// printf("\nRef img tile [%d, %d]\n", ref_tile_row_i, ref_tile_col_i );
|
||||||
|
// print_tile<uint16_t>( ref_img, curr_tile_size, ref_tile_row_start_idx_i, ref_tile_col_start_idx_i );
|
||||||
|
|
||||||
|
// Upsampled alignment at this tile
|
||||||
|
// Alignment are relative displacement in pixel value
|
||||||
|
int prev_alignment_row_i = upsampled_prev_aligement.at( ref_tile_row_i ).at( ref_tile_col_i ).first;
|
||||||
|
int prev_alignment_col_i = upsampled_prev_aligement.at( ref_tile_row_i ).at( ref_tile_col_i ).second;
|
||||||
|
|
||||||
|
// Alternative image tile start idx
|
||||||
|
int alt_tile_row_start_idx_i = ref_tile_row_start_idx_i + prev_alignment_row_i;
|
||||||
|
int alt_tile_col_start_idx_i = ref_tile_col_start_idx_i + prev_alignment_col_i;
|
||||||
|
|
||||||
|
// Ensure alternative image tile within range
|
||||||
|
if ( alt_tile_row_start_idx_i < 0 )
|
||||||
|
alt_tile_row_start_idx_i = 0;
|
||||||
|
if ( alt_tile_col_start_idx_i < 0 )
|
||||||
|
alt_tile_col_start_idx_i = 0;
|
||||||
|
if ( alt_tile_row_start_idx_i > alt_tile_row_idx_max )
|
||||||
|
{
|
||||||
|
// int before = alt_tile_row_start_idx_i;
|
||||||
|
alt_tile_row_start_idx_i = alt_tile_row_idx_max;
|
||||||
|
// printf("@@ change start x from %d to %d\n", before, alt_tile_row_idx_max);
|
||||||
|
}
|
||||||
|
if ( alt_tile_col_start_idx_i > alt_tile_col_idx_max )
|
||||||
|
{
|
||||||
|
// int before = alt_tile_col_start_idx_i;
|
||||||
|
alt_tile_col_start_idx_i = alt_tile_col_idx_max;
|
||||||
|
// printf("@@ change start y from %d to %d\n", before, alt_tile_col_idx_max );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly caching reference image tile
|
||||||
|
cv::Mat ref_img_tile_i = extract_ref_img_tile( ref_img, ref_tile_row_start_idx_i, ref_tile_col_start_idx_i );
|
||||||
|
cv::Mat alt_img_search_i = extract_alt_img_search( alt_img_pad, alt_tile_row_start_idx_i, alt_tile_col_start_idx_i );
|
||||||
|
|
||||||
|
// Because alternative image is padded with search radious.
|
||||||
|
// Using same coordinate with reference image will automatically considered search radious * 2
|
||||||
|
// printf("Alt image tile [%d, %d]-> start idx [%d, %d]\n", \
|
||||||
|
// ref_tile_row_i, ref_tile_col_i, alt_tile_row_start_idx_i, alt_tile_col_start_idx_i );
|
||||||
|
// printf("\nAlt image tile [%d, %d]\n", ref_tile_row_i, ref_tile_col_i );
|
||||||
|
// print_tile<uint16_t>( alt_img_pad, curr_tile_size + 2 * search_radiou, alt_tile_row_start_idx_i, alt_tile_col_start_idx_i );
|
||||||
|
|
||||||
|
// Search based on L1/L2 distance
|
||||||
|
unsigned long long min_distance_i = ULONG_LONG_MAX;
|
||||||
|
int min_distance_row_i = -1;
|
||||||
|
int min_distance_col_i = -1;
|
||||||
|
for ( int search_row_j = 0; search_row_j < ( search_radiou * 2 + 1 ); search_row_j++ )
|
||||||
|
{
|
||||||
|
for ( int search_col_j = 0; search_col_j < ( search_radiou * 2 + 1 ); search_col_j++ )
|
||||||
|
{
|
||||||
|
// printf("\n--->tile at [%d, %d] search (%d, %d)\n", \
|
||||||
|
// ref_tile_row_i, ref_tile_col_i, search_row_j - search_radiou, search_col_j - search_radiou );
|
||||||
|
|
||||||
|
// unsigned long long distance_j = distance_func_ptr( ref_img, alt_img_pad, \
|
||||||
|
// ref_tile_row_start_idx_i, ref_tile_col_start_idx_i, \
|
||||||
|
// alt_tile_row_start_idx_i + search_row_j, alt_tile_col_start_idx_i + search_col_j );
|
||||||
|
|
||||||
|
// unsigned long long distance_j = distance_func_ptr( ref_img_tile_i, alt_img_pad, \
|
||||||
|
// 0, 0, \
|
||||||
|
// alt_tile_row_start_idx_i + search_row_j, alt_tile_col_start_idx_i + search_col_j );
|
||||||
|
|
||||||
|
unsigned long long distance_j = distance_func_ptr( ref_img_tile_i, alt_img_search_i, \
|
||||||
|
0, 0, \
|
||||||
|
search_row_j, search_col_j );
|
||||||
|
|
||||||
|
// printf("<---tile at [%d, %d] search (%d, %d), new dis %llu, old dis %llu\n", \
|
||||||
|
// ref_tile_row_i, ref_tile_col_i, search_row_j - search_radiou, search_col_j - search_radiou, distance_j, min_distance_i );
|
||||||
|
|
||||||
|
// If this is smaller distance
|
||||||
|
if ( distance_j < min_distance_i )
|
||||||
|
{
|
||||||
|
min_distance_i = distance_j;
|
||||||
|
min_distance_col_i = search_col_j;
|
||||||
|
min_distance_row_i = search_row_j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If same value, choose the one closer to the original tile location
|
||||||
|
if ( distance_j == min_distance_i && min_distance_row_i != -1 && min_distance_col_i != -1 )
|
||||||
|
{
|
||||||
|
int prev_distance_row_2_ref = min_distance_row_i - search_radiou;
|
||||||
|
int prev_distance_col_2_ref = min_distance_col_i - search_radiou;
|
||||||
|
int curr_distance_row_2_ref = search_row_j - search_radiou;
|
||||||
|
int curr_distance_col_2_ref = search_col_j - search_radiou;
|
||||||
|
|
||||||
|
int prev_distance_2_ref_sqr = prev_distance_row_2_ref * prev_distance_row_2_ref + prev_distance_col_2_ref * prev_distance_col_2_ref;
|
||||||
|
int curr_distance_2_ref_sqr = curr_distance_row_2_ref * curr_distance_row_2_ref + curr_distance_col_2_ref * curr_distance_col_2_ref;
|
||||||
|
|
||||||
|
// previous min distance idx is farther away from ref tile start location
|
||||||
|
if ( prev_distance_2_ref_sqr > curr_distance_2_ref_sqr )
|
||||||
|
{
|
||||||
|
// printf("@@@ Same distance %d, choose closer one (%d, %d) instead of (%d, %d)\n", \
|
||||||
|
// distance_j, search_row_j, search_col_j, min_distance_row_i, min_distance_col_i);
|
||||||
|
min_distance_col_i = search_col_j;
|
||||||
|
min_distance_row_i = search_row_j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printf("tile at (%d, %d) alignment (%d, %d)\n", \
|
||||||
|
// ref_tile_row_i, ref_tile_col_i, min_distance_row_i, min_distance_col_i );
|
||||||
|
|
||||||
|
int alignment_row_i = prev_alignment_row_i + min_distance_row_i - search_radiou;
|
||||||
|
int alignment_col_i = prev_alignment_col_i + min_distance_col_i - search_radiou;
|
||||||
|
|
||||||
|
std::pair<int, int> alignment_i( alignment_row_i, alignment_col_i );
|
||||||
|
|
||||||
|
// Add min_distance_i's corresbonding idx as min
|
||||||
|
curr_alignment.at( ref_tile_row_i ).at( ref_tile_col_i ) = alignment_i;
|
||||||
|
distances.at( ref_tile_row_i ).at( ref_tile_col_i ) = min_distance_i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printf("\n!!!!!Min distance for each tile \n");
|
||||||
|
// for ( int tile_row = 0; tile_row < num_tiles_h; tile_row++ )
|
||||||
|
// {
|
||||||
|
// for ( int tile_col = 0; tile_col < num_tiles_w; ++tile_col )
|
||||||
|
// {
|
||||||
|
// printf("tile (%d, %d) distance %u\n", \
|
||||||
|
// tile_row, tile_col, distances.at( tile_row).at(tile_col ) );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// printf("\n!!!!!Alignment at current level\n");
|
||||||
|
// for ( int tile_row = 0; tile_row < num_tiles_h; tile_row++ )
|
||||||
|
// {
|
||||||
|
// for ( int tile_col = 0; tile_col < num_tiles_w; tile_col++ )
|
||||||
|
// {
|
||||||
|
// const auto tile_start = curr_alignment.at( tile_row ).at( tile_col );
|
||||||
|
// printf("tile (%d, %d) -> start idx (%d, %d)\n", \
|
||||||
|
// tile_row, tile_col, tile_start.first, tile_start.second);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void align::process( const hdrplus::burst& burst_images, \
|
||||||
|
std::vector<std::vector<std::vector<std::pair<int, int>>>>& images_alignment )
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
printf("%s::%s align::process start\n", __FILE__, __func__ ); fflush(stdout);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
images_alignment.clear();
|
||||||
|
images_alignment.resize( burst_images.num_images );
|
||||||
|
|
||||||
|
// image pyramid per image, per pyramid level
|
||||||
|
std::vector<std::vector<cv::Mat>> per_grayimg_pyramid;
|
||||||
|
|
||||||
|
// printf("!!!!! ref bayer padded\n");
|
||||||
|
// print_img<uint16_t>( burst_images.bayer_images_pad.at( burst_images.reference_image_idx) );
|
||||||
|
// exit(1);
|
||||||
|
|
||||||
|
// printf("!!!!! ref gray padded\n");
|
||||||
|
// print_img<uint16_t>( burst_images.grayscale_images_pad.at( burst_images.reference_image_idx) );
|
||||||
|
// exit(1);
|
||||||
|
|
||||||
|
per_grayimg_pyramid.resize( burst_images.num_images );
|
||||||
|
|
||||||
|
#pragma omp parallel for
|
||||||
|
for ( int img_idx = 0; img_idx < burst_images.num_images; ++img_idx )
|
||||||
|
{
|
||||||
|
// per_grayimg_pyramid[ img_idx ][ 0 ] is the original image
|
||||||
|
// per_grayimg_pyramid[ img_idx ][ 3 ] is the coarsest image
|
||||||
|
build_per_grayimg_pyramid( per_grayimg_pyramid.at( img_idx ), \
|
||||||
|
burst_images.grayscale_images_pad.at( img_idx ), \
|
||||||
|
this->inv_scale_factors );
|
||||||
|
}
|
||||||
|
|
||||||
|
// #ifndef NDEBUG
|
||||||
|
// printf("%s::%s build image pyramid of size : ", __FILE__, __func__ );
|
||||||
|
// for ( int level_i = 0; level_i < num_levels; ++level_i )
|
||||||
|
// {
|
||||||
|
// printf("(%d, %d) ", per_grayimg_pyramid[ 0 ][ level_i ].size().height,
|
||||||
|
// per_grayimg_pyramid[ 0 ][ level_i ].size().width );
|
||||||
|
// }
|
||||||
|
// printf("\n"); fflush(stdout);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// print image pyramid
|
||||||
|
// for ( int level_i; level_i < num_levels; ++level_i )
|
||||||
|
// {
|
||||||
|
// printf("\n\n!!!!! ref gray pyramid level %d img : \n" , level_i );
|
||||||
|
// print_img<uint16_t>( per_grayimg_pyramid[ burst_images.reference_image_idx ][ level_i ] );
|
||||||
|
// }
|
||||||
|
// exit(-1);
|
||||||
|
|
||||||
|
// Align every image
|
||||||
|
const std::vector<cv::Mat>& ref_grayimg_pyramid = per_grayimg_pyramid[ burst_images.reference_image_idx ];
|
||||||
|
for ( int img_idx = 0; img_idx < burst_images.num_images; ++img_idx )
|
||||||
|
{
|
||||||
|
// Do not align with reference image
|
||||||
|
if ( img_idx == burst_images.reference_image_idx )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const std::vector<cv::Mat>& alt_grayimg_pyramid = per_grayimg_pyramid[ img_idx ];
|
||||||
|
|
||||||
|
// Align every level from coarse to grain
|
||||||
|
// level 0 : finest level, the original image
|
||||||
|
// level 3 : coarsest level
|
||||||
|
std::vector<std::vector<std::pair<int, int>>> curr_alignment;
|
||||||
|
std::vector<std::vector<std::pair<int, int>>> prev_alignment;
|
||||||
|
for ( int level_i = num_levels - 1; level_i >= 0; level_i-- ) // 3,2,1,0
|
||||||
|
{
|
||||||
|
// make curr alignment as previous alignment
|
||||||
|
prev_alignment.swap( curr_alignment );
|
||||||
|
curr_alignment.clear();
|
||||||
|
|
||||||
|
// printf("\n\n########################align level %d\n", level_i );
|
||||||
|
align_image_level(
|
||||||
|
ref_grayimg_pyramid[ level_i ], // reference image at current level
|
||||||
|
alt_grayimg_pyramid[ level_i ], // alternative image at current level
|
||||||
|
prev_alignment, // previous layer alignment
|
||||||
|
curr_alignment, // current layer alignment
|
||||||
|
( level_i == ( num_levels - 1 ) ? -1 : inv_scale_factors[ level_i + 1 ] ), // scale factor between previous layer and current layer. -1 if current layer is the coarsest layer, [-1, 4, 4, 2]
|
||||||
|
grayimg_tile_sizes[ level_i ], // current level tile size
|
||||||
|
( level_i == ( num_levels - 1 ) ? -1 : grayimg_tile_sizes[ level_i + 1 ] ), // previous level tile size
|
||||||
|
grayimg_search_radious[ level_i ], // search radious
|
||||||
|
distances[ level_i ] ); // L1/L2 distance
|
||||||
|
|
||||||
|
// printf("@@@Alignment at level %d is h=%d, w=%d", level_i, curr_alignment.size(), curr_alignment.at(0).size() );
|
||||||
|
|
||||||
|
|
||||||
|
} // for pyramid level
|
||||||
|
|
||||||
|
// Alignment at grayscale image
|
||||||
|
images_alignment.at( img_idx ).swap( curr_alignment );
|
||||||
|
|
||||||
|
// printf("\n!!!!!Alternative Image Alignment\n");
|
||||||
|
// for ( int tile_row = 0; tile_row < images_alignment.at( img_idx ).size(); tile_row++ )
|
||||||
|
// {
|
||||||
|
// for ( int tile_col = 0; tile_col < images_alignment.at( img_idx ).at(0).size(); tile_col++ )
|
||||||
|
// {
|
||||||
|
// const auto tile_start = images_alignment.at( img_idx ).at( tile_row ).at( tile_col );
|
||||||
|
// printf("tile (%d, %d) -> start idx (%d, %d)\n", \
|
||||||
|
// tile_row, tile_col, tile_start.first, tile_start.second);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
} // for alternative image
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,102 @@
|
|||||||
|
#include <string>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <iostream>
|
||||||
|
#include <utility> // std::pair, std::makr_pair
|
||||||
|
#include <memory> // std::shared_ptr
|
||||||
|
#include <stdexcept> // std::runtime_error
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include <libraw/libraw.h>
|
||||||
|
#include <exiv2/exiv2.hpp> // exiv2
|
||||||
|
#include "hdrplus/bayer_image.h"
|
||||||
|
#include "hdrplus/utility.h" // box_filter_kxk
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
bayer_image::bayer_image( const std::string& bayer_image_path )
|
||||||
|
{
|
||||||
|
libraw_processor = std::make_shared<LibRaw>();
|
||||||
|
|
||||||
|
// Open RAW image file
|
||||||
|
int return_code;
|
||||||
|
if ( ( return_code = libraw_processor->open_file( bayer_image_path.c_str() ) ) != LIBRAW_SUCCESS )
|
||||||
|
{
|
||||||
|
libraw_processor->recycle();
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("Error opening file " + bayer_image_path + " " + libraw_strerror( return_code ));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack the raw image
|
||||||
|
if ( ( return_code = libraw_processor->unpack() ) != LIBRAW_SUCCESS )
|
||||||
|
{
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw std::runtime_error("Error unpack file " + bayer_image_path + " " + libraw_strerror( return_code ));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get image basic info
|
||||||
|
width = int( libraw_processor->imgdata.rawdata.sizes.raw_width );
|
||||||
|
height = int( libraw_processor->imgdata.rawdata.sizes.raw_height );
|
||||||
|
|
||||||
|
// Read exif tags
|
||||||
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(bayer_image_path);
|
||||||
|
assert(image.get() != 0);
|
||||||
|
image->readMetadata();
|
||||||
|
Exiv2::ExifData &exifData = image->exifData();
|
||||||
|
if (exifData.empty()) {
|
||||||
|
std::string error(bayer_image_path);
|
||||||
|
error += ": No Exif data found in the file";
|
||||||
|
std::cout << error << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
white_level = exifData["Exif.Image.WhiteLevel"].toLong();
|
||||||
|
black_level_per_channel.resize( 4 );
|
||||||
|
black_level_per_channel.at(0) = exifData["Exif.Image.BlackLevel"].toLong(0);
|
||||||
|
black_level_per_channel.at(1) = exifData["Exif.Image.BlackLevel"].toLong(1);
|
||||||
|
black_level_per_channel.at(2) = exifData["Exif.Image.BlackLevel"].toLong(2);
|
||||||
|
black_level_per_channel.at(3) = exifData["Exif.Image.BlackLevel"].toLong(3);
|
||||||
|
iso = exifData["Exif.Image.ISOSpeedRatings"].toLong();
|
||||||
|
|
||||||
|
// Create CV mat
|
||||||
|
// https://answers.opencv.org/question/105972/de-bayering-a-cr2-image/
|
||||||
|
// https://www.libraw.org/node/2141
|
||||||
|
raw_image = cv::Mat( height, width, CV_16U, libraw_processor->imgdata.rawdata.raw_image ).clone(); // changed the order of width and height
|
||||||
|
|
||||||
|
// 2x2 box filter
|
||||||
|
grayscale_image = box_filter_kxk<uint16_t, 2>( raw_image );
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
printf("%s::%s read bayer image %s with\n width %zu\n height %zu\n iso %.3f\n white level %d\n black level %d %d %d %d\n", \
|
||||||
|
__FILE__, __func__, bayer_image_path.c_str(), width, height, iso, white_level, \
|
||||||
|
black_level_per_channel[0], black_level_per_channel[1], black_level_per_channel[2], black_level_per_channel[3] );
|
||||||
|
fflush( stdout );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<double, double> bayer_image::get_noise_params() const
|
||||||
|
{
|
||||||
|
// Set ISO to 100 if not positive
|
||||||
|
double iso_ = iso <= 0 ? 100 : iso;
|
||||||
|
|
||||||
|
// Calculate shot noise and read noise parameters w.r.t ISO 100
|
||||||
|
double lambda_shot_p = iso_ / 100.0f * baseline_lambda_shot;
|
||||||
|
double lambda_read_p = (iso_ / 100.0f) * (iso_ / 100.0f) * baseline_lambda_read;
|
||||||
|
|
||||||
|
double black_level = (black_level_per_channel[0] + \
|
||||||
|
black_level_per_channel[1] + \
|
||||||
|
black_level_per_channel[2] + \
|
||||||
|
black_level_per_channel[3]) / 4.0;
|
||||||
|
|
||||||
|
// Rescale shot and read noise to normal range
|
||||||
|
double lambda_shot = lambda_shot_p * (white_level - black_level);
|
||||||
|
double lambda_read = lambda_read_p * (white_level - black_level) * (white_level - black_level);
|
||||||
|
|
||||||
|
// return pair
|
||||||
|
return std::make_pair(lambda_shot, lambda_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <omp.h>
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include "hdrplus/burst.h"
|
||||||
|
#include "hdrplus/utility.h"
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
burst::burst( const std::string& burst_path, const std::string& reference_image_path )
|
||||||
|
{
|
||||||
|
std::vector<cv::String> bayer_image_paths;
|
||||||
|
// Search through the input path directory to get all input image path
|
||||||
|
if ( burst_path.at( burst_path.size() - 1) == '/')
|
||||||
|
cv::glob( burst_path + "*.dng", bayer_image_paths, false );
|
||||||
|
else
|
||||||
|
cv::glob( burst_path + "/*.dng", bayer_image_paths, false );
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
for ( const auto& bayer_img_path_i : bayer_image_paths )
|
||||||
|
{
|
||||||
|
printf("img i path %s\n", bayer_img_path_i.c_str()); fflush(stdout);
|
||||||
|
}
|
||||||
|
printf("ref img path %s\n", reference_image_path.c_str()); fflush(stdout);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Number of images
|
||||||
|
num_images = bayer_image_paths.size();
|
||||||
|
|
||||||
|
// Find reference image path in input directory
|
||||||
|
// reference image path need to be absolute path
|
||||||
|
reference_image_idx = -1;
|
||||||
|
for ( size_t i = 0; i < bayer_image_paths.size(); ++i )
|
||||||
|
{
|
||||||
|
if ( bayer_image_paths[ i ] == reference_image_path )
|
||||||
|
{
|
||||||
|
reference_image_idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( reference_image_idx == -1 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
// throw std::runtime_error("Error unable to locate reference image " + reference_image_path );
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
for ( const auto& bayer_image_path_i : bayer_image_paths )
|
||||||
|
{
|
||||||
|
printf("%s::%s Find image %s\n", \
|
||||||
|
__FILE__, __func__, bayer_image_path_i.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s::%s reference image idx %d\n", \
|
||||||
|
__FILE__, __func__, reference_image_idx );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Get source bayer image
|
||||||
|
// Downsample original bayer image by 2x2 box filter
|
||||||
|
for ( const auto& bayer_image_path_i : bayer_image_paths )
|
||||||
|
{
|
||||||
|
bayer_images.emplace_back( bayer_image_path_i );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad information
|
||||||
|
int tile_size_bayer = 32;
|
||||||
|
int padding_top = tile_size_bayer / 2;
|
||||||
|
int padding_bottom = tile_size_bayer / 2 + \
|
||||||
|
( (bayer_images[ 0 ].height % tile_size_bayer) == 0 ? \
|
||||||
|
0 : tile_size_bayer - bayer_images[ 0 ].height % tile_size_bayer );
|
||||||
|
int padding_left = tile_size_bayer / 2;
|
||||||
|
int padding_right = tile_size_bayer / 2 + \
|
||||||
|
( (bayer_images[ 0 ].width % tile_size_bayer) == 0 ? \
|
||||||
|
0 : tile_size_bayer - bayer_images[ 0 ].width % tile_size_bayer );
|
||||||
|
padding_info_bayer = std::vector<int>{ padding_top, padding_bottom, padding_left, padding_right };
|
||||||
|
|
||||||
|
// Pad bayer image
|
||||||
|
for ( const auto& bayer_image_i : bayer_images )
|
||||||
|
{
|
||||||
|
cv::Mat bayer_image_pad_i;
|
||||||
|
cv::copyMakeBorder( bayer_image_i.raw_image, \
|
||||||
|
bayer_image_pad_i, \
|
||||||
|
padding_top, padding_bottom, padding_left, padding_right, \
|
||||||
|
cv::BORDER_REFLECT );
|
||||||
|
|
||||||
|
// cv::Mat use internal reference count
|
||||||
|
bayer_images_pad.emplace_back( bayer_image_pad_i );
|
||||||
|
grayscale_images_pad.emplace_back( box_filter_kxk<uint16_t, 2>( bayer_image_pad_i ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
printf("%s::%s Pad bayer image from (%d, %d) -> (%d, %d)\n", \
|
||||||
|
__FILE__, __func__, \
|
||||||
|
bayer_images[ 0 ].height, \
|
||||||
|
bayer_images[ 0 ].width, \
|
||||||
|
bayer_images_pad[ 0 ].size().height, \
|
||||||
|
bayer_images_pad[ 0 ].size().width );
|
||||||
|
printf("%s::%s pad top %d, buttom %d, left %d, right %d\n", \
|
||||||
|
__FILE__, __func__, \
|
||||||
|
padding_top, padding_bottom, padding_left, padding_right );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
burst::burst( const std::vector<std::string>& bayer_image_paths, int reference_image_index )
|
||||||
|
{
|
||||||
|
// Number of images
|
||||||
|
num_images = bayer_image_paths.size();
|
||||||
|
|
||||||
|
// Find reference image path in input directory
|
||||||
|
// reference image path need to be absolute path
|
||||||
|
reference_image_idx = -1;
|
||||||
|
if ( reference_image_index >= 0 && reference_image_index < bayer_image_paths.size() )
|
||||||
|
{
|
||||||
|
reference_image_idx = reference_image_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( reference_image_idx == -1 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
// throw std::runtime_error("Error reference image index is out of range " );
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
for ( const auto& bayer_image_path_i : bayer_image_paths )
|
||||||
|
{
|
||||||
|
printf("%s::%s Find image %s\n", \
|
||||||
|
__FILE__, __func__, bayer_image_path_i.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s::%s reference image idx %d\n", \
|
||||||
|
__FILE__, __func__, reference_image_idx );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Get source bayer image
|
||||||
|
// Downsample original bayer image by 2x2 box filter
|
||||||
|
for ( const auto& bayer_image_path_i : bayer_image_paths )
|
||||||
|
{
|
||||||
|
bayer_images.emplace_back( bayer_image_path_i );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad information
|
||||||
|
int tile_size_bayer = 32;
|
||||||
|
int padding_top = tile_size_bayer / 2;
|
||||||
|
int padding_bottom = tile_size_bayer / 2 + \
|
||||||
|
( (bayer_images[ 0 ].height % tile_size_bayer) == 0 ? \
|
||||||
|
0 : tile_size_bayer - bayer_images[ 0 ].height % tile_size_bayer );
|
||||||
|
int padding_left = tile_size_bayer / 2;
|
||||||
|
int padding_right = tile_size_bayer / 2 + \
|
||||||
|
( (bayer_images[ 0 ].width % tile_size_bayer) == 0 ? \
|
||||||
|
0 : tile_size_bayer - bayer_images[ 0 ].width % tile_size_bayer );
|
||||||
|
padding_info_bayer = std::vector<int>{ padding_top, padding_bottom, padding_left, padding_right };
|
||||||
|
|
||||||
|
// Pad bayer image
|
||||||
|
for ( const auto& bayer_image_i : bayer_images )
|
||||||
|
{
|
||||||
|
cv::Mat bayer_image_pad_i;
|
||||||
|
cv::copyMakeBorder( bayer_image_i.raw_image, \
|
||||||
|
bayer_image_pad_i, \
|
||||||
|
padding_top, padding_bottom, padding_left, padding_right, \
|
||||||
|
cv::BORDER_REFLECT );
|
||||||
|
|
||||||
|
// cv::Mat use internal reference count
|
||||||
|
bayer_images_pad.emplace_back( bayer_image_pad_i );
|
||||||
|
grayscale_images_pad.emplace_back( box_filter_kxk<uint16_t, 2>( bayer_image_pad_i ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
printf("%s::%s Pad bayer image from (%d, %d) -> (%d, %d)\n", \
|
||||||
|
__FILE__, __func__, \
|
||||||
|
bayer_images[ 0 ].height, \
|
||||||
|
bayer_images[ 0 ].width, \
|
||||||
|
bayer_images_pad[ 0 ].size().height, \
|
||||||
|
bayer_images_pad[ 0 ].size().width );
|
||||||
|
printf("%s::%s pad top %d, buttom %d, left %d, right %d\n", \
|
||||||
|
__FILE__, __func__, \
|
||||||
|
padding_top, padding_bottom, padding_left, padding_right );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,784 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include "hdrplus/finish.h"
|
||||||
|
#include "hdrplus/utility.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
#define DBG_OUTPUT_ROOT "/sdcard/com.xypower.mpapp/tmp/"
|
||||||
|
#else
|
||||||
|
#define DBG_OUTPUT_ROOT ""
|
||||||
|
#endif
|
||||||
|
// #include <type_traits>
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
cv::Mat convert16bit2_8bit_(cv::Mat ans){
|
||||||
|
if(ans.type()==CV_16UC3){
|
||||||
|
cv::MatIterator_<cv::Vec3w> it, end;
|
||||||
|
for( it = ans.begin<cv::Vec3w>(), end = ans.end<cv::Vec3w>(); it != end; ++it)
|
||||||
|
{
|
||||||
|
// std::cout<<sizeof (*it)[0] <<std::endl;
|
||||||
|
(*it)[0] *=(255.0/USHRT_MAX);
|
||||||
|
(*it)[1] *=(255.0/USHRT_MAX);
|
||||||
|
(*it)[2] *=(255.0/USHRT_MAX);
|
||||||
|
}
|
||||||
|
ans.convertTo(ans, CV_8UC3);
|
||||||
|
}else if(ans.type()==CV_16UC1){
|
||||||
|
u_int16_t* ptr = (u_int16_t*)ans.data;
|
||||||
|
int end = ans.rows*ans.cols;
|
||||||
|
for(int i=0;i<end;i++){
|
||||||
|
*(ptr+i) *=(255.0/USHRT_MAX);
|
||||||
|
}
|
||||||
|
ans.convertTo(ans, CV_8UC1);
|
||||||
|
}else{
|
||||||
|
std::cout<<"Unsupported Data Type"<<std::endl;
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat convert8bit2_16bit_(cv::Mat ans){
|
||||||
|
if(ans.type()==CV_8UC3){
|
||||||
|
ans.convertTo(ans, CV_16UC3);
|
||||||
|
cv::MatIterator_<cv::Vec3w> it, end;
|
||||||
|
for( it = ans.begin<cv::Vec3w>(), end = ans.end<cv::Vec3w>(); it != end; ++it)
|
||||||
|
{
|
||||||
|
// std::cout<<sizeof (*it)[0] <<std::endl;
|
||||||
|
(*it)[0] *=(65535.0/255.0);
|
||||||
|
(*it)[1] *=(65535.0/255.0);
|
||||||
|
(*it)[2] *=(65535.0/255.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}else if(ans.type()==CV_8UC1){
|
||||||
|
ans.convertTo(ans, CV_16UC1);
|
||||||
|
u_int16_t* ptr = (u_int16_t*)ans.data;
|
||||||
|
int end = ans.rows*ans.cols;
|
||||||
|
for(int i=0;i<end;i++){
|
||||||
|
*(ptr+i) *=(65535.0/255.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
std::cout<<"Unsupported Data Type"<<std::endl;
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat convert8bit2_12bit_(cv::Mat ans){
|
||||||
|
// cv::Mat ans(I);
|
||||||
|
cv::MatIterator_<cv::Vec3w> it, end;
|
||||||
|
for( it = ans.begin<cv::Vec3w>(), end = ans.end<cv::Vec3w>(); it != end; ++it)
|
||||||
|
{
|
||||||
|
// std::cout<<sizeof (*it)[0] <<std::endl;
|
||||||
|
(*it)[0] *=(2048.0/255.0);
|
||||||
|
(*it)[1] *=(2048.0/255.0);
|
||||||
|
(*it)[2] *=(2048.0/255.0);
|
||||||
|
}
|
||||||
|
ans.convertTo(ans, CV_16UC3);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t uGammaCompress_1pix(float x, float threshold,float gainMin,float gainMax,float exponent){
|
||||||
|
// Normalize pixel val
|
||||||
|
x/=USHRT_MAX;
|
||||||
|
// check the val against the threshold
|
||||||
|
if(x<=threshold){
|
||||||
|
x =gainMin*x;
|
||||||
|
}else{
|
||||||
|
x = gainMax* pow(x,exponent)-gainMax+1;
|
||||||
|
}
|
||||||
|
// clip
|
||||||
|
if(x<0){
|
||||||
|
x=0;
|
||||||
|
}else{
|
||||||
|
if(x>1){
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x*=USHRT_MAX;
|
||||||
|
|
||||||
|
return (uint16_t)x;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t uGammaDecompress_1pix(float x, float threshold,float gainMin,float gainMax,float exponent){
|
||||||
|
// Normalize pixel val
|
||||||
|
x/=65535.0;
|
||||||
|
// check the val against the threshold
|
||||||
|
if(x<=threshold){
|
||||||
|
x = x/gainMin;
|
||||||
|
}else{
|
||||||
|
x = pow((x+gainMax-1)/gainMax,exponent);
|
||||||
|
}
|
||||||
|
// clip
|
||||||
|
if(x<0){
|
||||||
|
x=0;
|
||||||
|
}else{
|
||||||
|
if(x>1){
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x*=65535;
|
||||||
|
|
||||||
|
return (uint16_t)x;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat uGammaCompress_(cv::Mat m,float threshold,float gainMin,float gainMax,float exponent){
|
||||||
|
if(m.type()==CV_16UC3){
|
||||||
|
cv::MatIterator_<cv::Vec3w> it, end;
|
||||||
|
for( it = m.begin<cv::Vec3w>(), end = m.end<cv::Vec3w>(); it != end; ++it)
|
||||||
|
{
|
||||||
|
(*it)[0] =uGammaCompress_1pix((*it)[0],threshold,gainMin,gainMax,exponent);
|
||||||
|
(*it)[1] =uGammaCompress_1pix((*it)[1],threshold,gainMin,gainMax,exponent);
|
||||||
|
(*it)[2] =uGammaCompress_1pix((*it)[2],threshold,gainMin,gainMax,exponent);
|
||||||
|
}
|
||||||
|
}else if(m.type()==CV_16UC1){
|
||||||
|
u_int16_t* ptr = (u_int16_t*)m.data;
|
||||||
|
int end = m.rows*m.cols;
|
||||||
|
for(int i=0;i<end;i++){
|
||||||
|
*(ptr+i) = uGammaCompress_1pix(*(ptr+i),threshold,gainMin,gainMax,exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
std::cout<<"Unsupported Data Type"<<std::endl;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat uGammaDecompress_(cv::Mat m,float threshold,float gainMin,float gainMax,float exponent){
|
||||||
|
if(m.type()==CV_16UC3){
|
||||||
|
cv::MatIterator_<cv::Vec3w> it, end;
|
||||||
|
for( it = m.begin<cv::Vec3w>(), end = m.end<cv::Vec3w>(); it != end; ++it)
|
||||||
|
{
|
||||||
|
(*it)[0] =uGammaDecompress_1pix((*it)[0],threshold,gainMin,gainMax,exponent);
|
||||||
|
(*it)[1] =uGammaDecompress_1pix((*it)[1],threshold,gainMin,gainMax,exponent);
|
||||||
|
(*it)[2] =uGammaDecompress_1pix((*it)[2],threshold,gainMin,gainMax,exponent);
|
||||||
|
}
|
||||||
|
}else if(m.type()==CV_16UC1){
|
||||||
|
u_int16_t* ptr = (u_int16_t*)m.data;
|
||||||
|
int end = m.rows*m.cols;
|
||||||
|
for(int i=0;i<end;i++){
|
||||||
|
*(ptr+i) = uGammaDecompress_1pix(*(ptr+i),threshold,gainMin,gainMax,exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
std::cout<<"Unsupported Data Type"<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat gammasRGB(cv::Mat img, bool mode){
|
||||||
|
if(mode){// compress
|
||||||
|
return uGammaCompress_(img,0.0031308, 12.92, 1.055, 1. / 2.4);
|
||||||
|
}else{ // decompress
|
||||||
|
return uGammaDecompress_(img, 0.04045, 12.92, 1.055, 2.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_mat_16U_2(u_int16_t* ptr_A, cv::Mat B){
|
||||||
|
// u_int16_t* ptr_A = (u_int16_t*)A.data;
|
||||||
|
u_int16_t* ptr_B = (u_int16_t*)B.data;
|
||||||
|
for(int r = 0; r < B.rows; r++) {
|
||||||
|
for(int c = 0; c < B.cols; c++) {
|
||||||
|
*(ptr_A+r*B.cols+c) = *(ptr_B+r*B.cols+c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat mean_(cv::Mat img){
|
||||||
|
// initialize processedImg
|
||||||
|
int H = img.rows;
|
||||||
|
int W = img.cols;
|
||||||
|
cv::Mat processedImg = cv::Mat(H,W,CV_16UC1);
|
||||||
|
u_int16_t* ptr = (u_int16_t*)processedImg.data;
|
||||||
|
|
||||||
|
// traverse img
|
||||||
|
int idx = 0;
|
||||||
|
cv::MatIterator_<cv::Vec3w> it, end;
|
||||||
|
for( it = img.begin<cv::Vec3w>(), end = img.end<cv::Vec3w>(); it != end; ++it)
|
||||||
|
{
|
||||||
|
uint32_t tmp = (*it)[0]+(*it)[1]+(*it)[2];
|
||||||
|
uint16_t avg_val = tmp/3;
|
||||||
|
*(ptr+idx) = avg_val;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedImg;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMean(cv::Mat img){
|
||||||
|
u_int16_t* ptr = (u_int16_t*)img.data;
|
||||||
|
int max_idx = img.rows*img.cols*img.channels();
|
||||||
|
double sum=0;
|
||||||
|
for(int i=0;i<max_idx;i++){
|
||||||
|
sum += *(ptr+i);
|
||||||
|
}
|
||||||
|
sum/=max_idx;
|
||||||
|
sum/=USHRT_MAX;
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat matMultiply_scalar(cv::Mat img,float gain){
|
||||||
|
u_int16_t* ptr = (u_int16_t*)img.data;
|
||||||
|
int max_idx = img.rows*img.cols*img.channels();
|
||||||
|
for(int i=0;i<max_idx;i++){
|
||||||
|
double tmp = *(ptr+i)*gain;
|
||||||
|
if(tmp<0){
|
||||||
|
*(ptr+i)=0;
|
||||||
|
}else if(tmp>USHRT_MAX){
|
||||||
|
*(ptr+i) = USHRT_MAX;
|
||||||
|
}else{
|
||||||
|
*(ptr+i)=(u_int16_t)tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getSaturated(cv::Mat img, double threshold){
|
||||||
|
threshold *= USHRT_MAX;
|
||||||
|
double count=0;
|
||||||
|
u_int16_t* ptr = (u_int16_t*)img.data;
|
||||||
|
int max_idx = img.rows*img.cols*img.channels();
|
||||||
|
for(int i=0;i<max_idx;i++){
|
||||||
|
if(*(ptr+i)>threshold){
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count/(double)max_idx;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat meanGain_(cv::Mat img,int gain){
|
||||||
|
if(img.channels()!=3){
|
||||||
|
std::cout<<"unsupport img type in meanGain_()"<<std::endl;
|
||||||
|
return cv::Mat();
|
||||||
|
}else{ // RGB img
|
||||||
|
int H = img.rows;
|
||||||
|
int W = img.cols;
|
||||||
|
cv::Mat processedImg = cv::Mat(H,W,CV_16UC1);
|
||||||
|
u_int16_t* ptr = (u_int16_t*)processedImg.data;
|
||||||
|
int idx=0;
|
||||||
|
|
||||||
|
cv::MatIterator_<cv::Vec3w> it, end;
|
||||||
|
for( it = img.begin<cv::Vec3w>(), end = img.end<cv::Vec3w>(); it != end; ++it)
|
||||||
|
{
|
||||||
|
double sum = 0;
|
||||||
|
// R
|
||||||
|
double tmp = (*it)[0]*gain;
|
||||||
|
if(tmp<0) tmp=0;
|
||||||
|
if(tmp>USHRT_MAX) tmp = USHRT_MAX;
|
||||||
|
sum+=tmp;
|
||||||
|
|
||||||
|
// G
|
||||||
|
tmp = (*it)[1]*gain;
|
||||||
|
if(tmp<0) tmp=0;
|
||||||
|
if(tmp>USHRT_MAX) tmp = USHRT_MAX;
|
||||||
|
sum+=tmp;
|
||||||
|
|
||||||
|
// B
|
||||||
|
tmp = (*it)[2]*gain;
|
||||||
|
if(tmp<0) tmp=0;
|
||||||
|
if(tmp>USHRT_MAX) tmp = USHRT_MAX;
|
||||||
|
sum+=tmp;
|
||||||
|
|
||||||
|
// put into processedImg
|
||||||
|
uint16_t avg_val = sum/3;
|
||||||
|
*(ptr+idx) = avg_val;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
return processedImg;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat applyScaling_(cv::Mat mergedImage, cv::Mat shortGray, cv::Mat fusedGray){
|
||||||
|
cv::Mat result = mergedImage.clone();
|
||||||
|
u_int16_t* ptr_shortg = (u_int16_t*)shortGray.data;
|
||||||
|
u_int16_t* ptr_fusedg = (u_int16_t*)fusedGray.data;
|
||||||
|
int count = 0;
|
||||||
|
cv::MatIterator_<cv::Vec3w> it, end;
|
||||||
|
for( it = result.begin<cv::Vec3w>(), end = result.end<cv::Vec3w>(); it != end; ++it)
|
||||||
|
{
|
||||||
|
double s = 1;
|
||||||
|
if(*(ptr_shortg+count)!=0){
|
||||||
|
s = *(ptr_fusedg+count);
|
||||||
|
s/=*(ptr_shortg+count);
|
||||||
|
}
|
||||||
|
for(int c=0;c<mergedImage.channels();c++){
|
||||||
|
double tmp = (*it)[c]*s;
|
||||||
|
if(tmp<0){
|
||||||
|
(*it)[c] = 0;
|
||||||
|
}else if(tmp>USHRT_MAX){
|
||||||
|
(*it)[c] = USHRT_MAX;
|
||||||
|
}else{
|
||||||
|
(*it)[c] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void localToneMap(cv::Mat& mergedImage, Options options, cv::Mat& shortg,
|
||||||
|
cv::Mat& longg, cv::Mat& fusedg, int& gain){
|
||||||
|
std::cout<<"HDR Tone Mapping..."<<std::endl;
|
||||||
|
// # Work with grayscale images
|
||||||
|
cv::Mat shortGray = rgb_2_gray<uint16_t, uint16_t, CV_16U>(mergedImage); //mean_(mergedImage);
|
||||||
|
std::cout<<"--- Compute grayscale image"<<std::endl;
|
||||||
|
|
||||||
|
// compute gain
|
||||||
|
gain = 0;
|
||||||
|
if(options.ltmGain==-1){
|
||||||
|
double dsFactor = 25;
|
||||||
|
int down_height = round(shortGray.rows/dsFactor);
|
||||||
|
int down_width = round(shortGray.cols/dsFactor);
|
||||||
|
cv::Mat shortS;
|
||||||
|
cv::resize(shortGray,shortS,cv::Size(down_height,down_width),cv::INTER_LINEAR);
|
||||||
|
shortS = shortS.reshape(1,1);
|
||||||
|
|
||||||
|
bool bestGain = false;
|
||||||
|
double compression = 1.0;
|
||||||
|
double saturated = 0.0;
|
||||||
|
cv::Mat shortSg = gammasRGB(shortS.clone(), true);
|
||||||
|
double sSMean = getMean(shortSg);
|
||||||
|
|
||||||
|
while((compression < 1.9 && saturated < .95)||((!bestGain) && (compression < 6) && (gain < 30) && (saturated < 0.33))){
|
||||||
|
gain += 2;
|
||||||
|
cv::Mat longSg = gammasRGB(shortS.clone()*gain, true);
|
||||||
|
double lSMean = getMean(longSg);
|
||||||
|
compression = lSMean / sSMean;
|
||||||
|
bestGain = lSMean > (1 - sSMean) / 2; // only works if burst underexposed
|
||||||
|
saturated = getSaturated(longSg,0.95);
|
||||||
|
if(options.verbose==4){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
if(options.ltmGain>0){
|
||||||
|
gain = options.ltmGain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout<<"--- Compute gain"<<std::endl;
|
||||||
|
// create a synthetic long exposure
|
||||||
|
cv::Mat longGray = meanGain_(mergedImage.clone(),gain);
|
||||||
|
std::cout<<"--- Synthetic long expo"<<std::endl;
|
||||||
|
// apply gamma correction to both
|
||||||
|
longg = gammasRGB(longGray.clone(), true);
|
||||||
|
shortg = gammasRGB(shortGray.clone(),true);
|
||||||
|
std::cout<<"--- Apply Gamma correction"<<std::endl;
|
||||||
|
// perform tone mapping by exposure fusion in grayscale
|
||||||
|
cv::Ptr<cv::MergeMertens> mergeMertens = cv::createMergeMertens();
|
||||||
|
std::cout<<"--- Create Mertens"<<std::endl;
|
||||||
|
// hack: cv2 mergeMertens expects inputs between 0 and 255
|
||||||
|
// but the result is scaled between 0 and 1 (some values can actually be greater than 1!)
|
||||||
|
std::vector<cv::Mat> src_expos;
|
||||||
|
src_expos.push_back(convert16bit2_8bit_(shortg.clone()));
|
||||||
|
src_expos.push_back(convert16bit2_8bit_(longg.clone()));
|
||||||
|
mergeMertens->process(src_expos, fusedg);
|
||||||
|
fusedg = fusedg*USHRT_MAX;
|
||||||
|
fusedg.convertTo(fusedg, CV_16UC1);
|
||||||
|
std::cout<<"--- Apply Mertens"<<std::endl;
|
||||||
|
// undo gamma correction
|
||||||
|
cv::Mat fusedGray = gammasRGB(fusedg.clone(), false);
|
||||||
|
// cv::imwrite("fusedg_degamma.png", fusedGray);
|
||||||
|
std::cout<<"--- Un-apply Gamma correction"<<std::endl;
|
||||||
|
// scale each RGB channel of the short exposure accordingly
|
||||||
|
mergedImage = applyScaling_(mergedImage, shortGray, fusedGray);
|
||||||
|
std::cout<<"--- Scale channels"<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
u_int16_t enhanceContrast_1pix(u_int16_t pix_val,double gain){
|
||||||
|
double x = pix_val;
|
||||||
|
x/=USHRT_MAX;
|
||||||
|
x = x - gain*sin(2*M_PI*x);
|
||||||
|
if(x<0){
|
||||||
|
x = 0;
|
||||||
|
}else if(x>1){
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
u_int16_t result = x*USHRT_MAX;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat enhanceContrast(cv::Mat image, Options options){
|
||||||
|
if(options.gtmContrast>=0 && options.gtmContrast<=1){
|
||||||
|
u_int16_t* ptr = (u_int16_t*)image.data;
|
||||||
|
int end = image.rows*image.cols*image.channels();
|
||||||
|
for(int idx = 0;idx<end;idx++){
|
||||||
|
*(ptr+idx) = enhanceContrast_1pix(*(ptr+idx),options.gtmContrast);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
std::cout<<"GTM ignored, expected a contrast enhancement ratio between 0 and 1"<<std::endl;
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat distL1_(cv::Mat X, cv::Mat Y){
|
||||||
|
int end_x = X.rows*X.cols*X.channels();
|
||||||
|
int end_y = Y.rows*Y.cols*Y.channels();
|
||||||
|
cv::Mat result = cv::Mat(X.rows,X.cols,X.type());
|
||||||
|
if(end_x==end_y){
|
||||||
|
u_int16_t* ptr_x = (u_int16_t*)X.data;
|
||||||
|
u_int16_t* ptr_y = (u_int16_t*)Y.data;
|
||||||
|
u_int16_t* ptr_r = (u_int16_t*)result.data;
|
||||||
|
for(int i=0;i<end_x;i++){
|
||||||
|
if(*(ptr_x+i)<*(ptr_y+i)){
|
||||||
|
*(ptr_r+i) = *(ptr_y+i) - *(ptr_x+i);
|
||||||
|
}else{
|
||||||
|
*(ptr_r+i) = *(ptr_x+i) - *(ptr_y+i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
std::cout<<"Mat size not match. distL1_ failed!"<<std::endl;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat sharpenTriple_(cv::Mat image,
|
||||||
|
cv::Mat blur0, cv::Mat low0, float th0, float k0,
|
||||||
|
cv::Mat blur1, cv::Mat low1, float th1, float k1,
|
||||||
|
cv::Mat blur2, cv::Mat low2, float th2, float k2){
|
||||||
|
// create result mat
|
||||||
|
cv::Mat result = cv::Mat(image.rows,image.cols,image.type());
|
||||||
|
// initialize iteraters
|
||||||
|
u_int16_t* ptr_r = (u_int16_t*)result.data;
|
||||||
|
u_int16_t* ptr_img = (u_int16_t*)image.data;
|
||||||
|
u_int16_t* ptr_blur0 = (u_int16_t*)blur0.data;
|
||||||
|
u_int16_t* ptr_low0 = (u_int16_t*)low0.data;
|
||||||
|
u_int16_t* ptr_blur1 = (u_int16_t*)blur1.data;
|
||||||
|
u_int16_t* ptr_low1 = (u_int16_t*)low1.data;
|
||||||
|
u_int16_t* ptr_blur2 = (u_int16_t*)blur2.data;
|
||||||
|
u_int16_t* ptr_low2 = (u_int16_t*)low2.data;
|
||||||
|
int n_channels = image.channels();
|
||||||
|
int end = image.rows*image.cols*n_channels;
|
||||||
|
// traverse Image
|
||||||
|
for(int idx = 0;idx<end;idx++){
|
||||||
|
double r, r0, r1, r2;
|
||||||
|
double x = *(ptr_img+idx);
|
||||||
|
double l0 = *(ptr_low0+idx)/(double)USHRT_MAX;
|
||||||
|
double l1 = *(ptr_low1+idx)/(double)USHRT_MAX;
|
||||||
|
double l2 = *(ptr_low2+idx)/(double)USHRT_MAX;
|
||||||
|
double b0 = *(ptr_blur0+idx);
|
||||||
|
double b1 = *(ptr_blur1+idx);
|
||||||
|
double b2 = *(ptr_blur2+idx);
|
||||||
|
r0 = l0<th0? x:x+k0*(x-b0);
|
||||||
|
r1 = l1<th1? x:x+k1*(x-b1);
|
||||||
|
r2 = l2<th2? x:x+k2*(x-b2);
|
||||||
|
r = (r0+r1+r2)/3.0;
|
||||||
|
if(r<0) r=0;
|
||||||
|
if(r>USHRT_MAX) r = USHRT_MAX;
|
||||||
|
*(ptr_r+idx) = (u_int16_t)r;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat sharpenTriple(cv::Mat image, Tuning tuning, Options options){
|
||||||
|
// sharpen the image using unsharp masking
|
||||||
|
std::vector<float> amounts = tuning.sharpenAmount;
|
||||||
|
std::vector<float> sigmas = tuning.sharpenSigma;
|
||||||
|
std::vector<float> thresholds = tuning.sharpenThreshold;
|
||||||
|
// Compute all Gaussian blur
|
||||||
|
cv::Mat blur0,blur1,blur2;
|
||||||
|
cv::GaussianBlur(image,blur0,cv::Size(0,0),sigmas[0]);
|
||||||
|
cv::GaussianBlur(image,blur1,cv::Size(0,0),sigmas[1]);
|
||||||
|
cv::GaussianBlur(image,blur2,cv::Size(0,0),sigmas[2]);
|
||||||
|
std::cout<<" --- gaussian blur"<<std::endl;
|
||||||
|
// cv::imwrite("blur2.png", blur2);
|
||||||
|
// Compute all low contrast images
|
||||||
|
cv::Mat low0 = distL1_(blur0, image);
|
||||||
|
cv::Mat low1 = distL1_(blur1, image);
|
||||||
|
cv::Mat low2 = distL1_(blur2, image);
|
||||||
|
std::cout<<" --- low contrast"<<std::endl;
|
||||||
|
// cv::imwrite("low2.png", low2);
|
||||||
|
// Compute the triple sharpen
|
||||||
|
cv::Mat sharpImage = sharpenTriple_(image,
|
||||||
|
blur0, low0, thresholds[0], amounts[0],
|
||||||
|
blur1, low1, thresholds[1], amounts[1],
|
||||||
|
blur2, low2, thresholds[2], amounts[2]);
|
||||||
|
std::cout<<" --- sharpen"<<std::endl;
|
||||||
|
return sharpImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_mat_16U_3(u_int16_t* ptr_A, cv::Mat B){
|
||||||
|
// u_int16_t* ptr_A = (u_int16_t*)A.data;
|
||||||
|
u_int16_t* ptr_B = (u_int16_t*)B.data;
|
||||||
|
int H = B.rows;
|
||||||
|
int W = B.cols;
|
||||||
|
int end = H*W;
|
||||||
|
for(int i=0;i<end;i++){
|
||||||
|
*(ptr_A+i) = *(ptr_B+i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// void copy_mat_16U_3(u_int16_t* ptr_A, cv::Mat B){
|
||||||
|
// // u_int16_t* ptr_A = (u_int16_t*)A.data;
|
||||||
|
// u_int16_t* ptr_B = (u_int16_t*)B.data;
|
||||||
|
// for(int r = 0; r < B.rows; r++) {
|
||||||
|
// for(int c = 0; c < B.cols; c++) {
|
||||||
|
// *(ptr_A+r*B.cols+c) = *(ptr_B+r*B.cols+c);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
cv::Mat processMergedMat(cv::Mat mergedImg, int opencv_type){
|
||||||
|
cv::Mat m;
|
||||||
|
uint16_t* ptr = (uint16_t*)mergedImg.data;
|
||||||
|
for(int r = 0; r < mergedImg.rows; r++) {
|
||||||
|
std::vector<int> dvals;
|
||||||
|
for(int c = 0; c < mergedImg.cols; c++) {
|
||||||
|
dvals.push_back(*(ptr+r*mergedImg.cols+c));
|
||||||
|
}
|
||||||
|
cv::Mat mline(dvals, true);
|
||||||
|
cv::transpose(mline, mline);
|
||||||
|
m.push_back(mline);
|
||||||
|
}
|
||||||
|
int ch = CV_MAT_CN(opencv_type);
|
||||||
|
|
||||||
|
m = m.reshape(ch);
|
||||||
|
m.convertTo(m, opencv_type);
|
||||||
|
|
||||||
|
return m;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void show20_20(cv::Mat m){
|
||||||
|
u_int16_t* ptr = (u_int16_t*)m.data;
|
||||||
|
for(int i=0;i<20;i++){
|
||||||
|
for(int j=0;j<20;j++){
|
||||||
|
std::cout<<*(ptr+i*m.cols+j)<<", ";
|
||||||
|
}
|
||||||
|
std::cout<<std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeCSV(std::string filename, cv::Mat m)
|
||||||
|
{
|
||||||
|
std::ofstream myfile;
|
||||||
|
myfile.open(filename.c_str());
|
||||||
|
myfile<< cv::format(m, cv::Formatter::FMT_CSV) << std::endl;
|
||||||
|
myfile.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish::process(const hdrplus::burst& burst_images, cv::Mat& finalOutputImage){
|
||||||
|
// copy mergedBayer to rawReference
|
||||||
|
std::cout<<"finish pipeline start ..."<<std::endl;
|
||||||
|
|
||||||
|
// save merged Image value
|
||||||
|
// #ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
writeCSV(DBG_OUTPUT_ROOT "merged.csv",burst_images.merged_bayer_image);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
this->refIdx = burst_images.reference_image_idx;
|
||||||
|
// this->burstPath = burstPath;
|
||||||
|
// std::cout<<"processMerged:"<<std::endl;
|
||||||
|
// show20_20(mergedB);
|
||||||
|
|
||||||
|
this->mergedBayer = loadFromCSV(DBG_OUTPUT_ROOT "merged.csv", CV_16UC1);
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
// this->mergedBayer = processMergedMat(mergedB,CV_16UC1);//loadFromCSV("merged.csv", CV_16UC1);
|
||||||
|
// std::cout<<"processMerged:"<<std::endl;
|
||||||
|
// show20_20(this->mergedBayer);
|
||||||
|
// this->mergedBayer = loadFromCSV(DBG_OUTPUT_ROOT "merged.csv", CV_16UC1);
|
||||||
|
// this->mergedBayer = processMergedMat(burst_images.merged_bayer_image, CV_16UC1);
|
||||||
|
#else
|
||||||
|
// this->mergedBayer = loadFromCSV(DBG_OUTPUT_ROOT "merged.csv", CV_16UC1);
|
||||||
|
// this->mergedBayer = processMergedMat(burst_images.merged_bayer_image, CV_16UC1);
|
||||||
|
// std::cout<<"processMerged:"<<std::endl;
|
||||||
|
#endif
|
||||||
|
// std::cout<<"csv:"<<std::endl;
|
||||||
|
// show20_20(this->mergedBayer);
|
||||||
|
// load_rawPathList(burstPath);
|
||||||
|
|
||||||
|
// read in ref img
|
||||||
|
// bayer_image* ref = new bayer_image(rawPathList[refIdx]);
|
||||||
|
bayer_image* ref = new bayer_image(burst_images.bayer_images[burst_images.reference_image_idx]);
|
||||||
|
cv::Mat processedRefImage = postprocess(ref->libraw_processor,params.rawpyArgs);
|
||||||
|
|
||||||
|
std::cout<<"size ref: "<<processedRefImage.rows<<"*"<<processedRefImage.cols<<std::endl;
|
||||||
|
|
||||||
|
// write reference image
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeReferenceImage"]){
|
||||||
|
std::cout<<"writing reference img ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = convert16bit2_8bit_(processedRefImage.clone());
|
||||||
|
cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
|
||||||
|
// cv::imshow("test",processedImage);
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "processedRef.jpg", outputImg);
|
||||||
|
// cv::waitKey(0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// write gamma reference
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeGammaReference"]){
|
||||||
|
std::cout<<"writing Gamma reference img ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = gammasRGB(processedRefImage.clone(),true);
|
||||||
|
outputImg = convert16bit2_8bit_(outputImg);
|
||||||
|
cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "processedRefGamma.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// get the bayer_image of the merged image
|
||||||
|
// bayer_image* mergedImg = new bayer_image(rawPathList[refIdx]);
|
||||||
|
bayer_image* mergedImg = new bayer_image(burst_images.bayer_images[this->refIdx]);
|
||||||
|
mergedImg->libraw_processor->imgdata.rawdata.raw_image = (uint16_t*)this->mergedBayer.data;
|
||||||
|
// copy_mat_16U_3(mergedImg->libraw_processor->imgdata.rawdata.raw_image,this->mergedBayer);
|
||||||
|
cv::Mat processedMerge = postprocess(mergedImg->libraw_processor,params.rawpyArgs);
|
||||||
|
|
||||||
|
// write merged image
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeMergedImage"]){
|
||||||
|
std::cout<<"writing Merged img ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = convert16bit2_8bit_(processedMerge.clone());
|
||||||
|
cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "mergedImg.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// write gamma merged image
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeMergedImage"]){
|
||||||
|
std::cout<<"writing Gamma Merged img ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = gammasRGB(processedMerge.clone(),true);
|
||||||
|
outputImg = convert16bit2_8bit_(outputImg);
|
||||||
|
cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "mergedImgGamma.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// step 5. HDR tone mapping
|
||||||
|
// processedImage, gain, shortExposure, longExposure, fusedExposure = localToneMap(burstPath, processedImage, options)
|
||||||
|
int gain;
|
||||||
|
if(params.options.ltmGain){
|
||||||
|
cv::Mat shortExposure, longExposure, fusedExposure;
|
||||||
|
|
||||||
|
localToneMap(processedMerge, params.options,shortExposure,longExposure,fusedExposure,gain);
|
||||||
|
std::cout<<"gain="<< gain<<std::endl;
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeShortExposure"]){
|
||||||
|
std::cout<<"writing ShortExposure img ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = convert16bit2_8bit_(shortExposure);
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "shortg.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeLongExposure"]){
|
||||||
|
std::cout<<"writing LongExposure img ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = convert16bit2_8bit_(longExposure);
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "longg.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeFusedExposure"]){
|
||||||
|
std::cout<<"writing FusedExposure img ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = convert16bit2_8bit_(fusedExposure);
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "fusedg.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeLTMImage"]){
|
||||||
|
std::cout<<"writing LTMImage ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = convert16bit2_8bit_(processedMerge.clone());
|
||||||
|
cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "ltmGain.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeLTMGamma"]){
|
||||||
|
std::cout<<"writing LTMImage Gamma ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = gammasRGB(processedMerge.clone(),true);
|
||||||
|
outputImg = convert16bit2_8bit_(outputImg);
|
||||||
|
cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "ltmGain_gamma.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 6 GTM: contrast enhancement / global tone mapping
|
||||||
|
if(params.options.gtmContrast){
|
||||||
|
processedMerge = enhanceContrast(processedMerge, params.options);
|
||||||
|
std::cout<<"STEP 6 -- Apply GTM"<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the final sRGB gamma curve
|
||||||
|
processedMerge = gammasRGB(processedMerge.clone(),true);
|
||||||
|
std::cout<<"-- Apply Gamma"<<std::endl;
|
||||||
|
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeGTMImage"]) {
|
||||||
|
std::cout<<"writing GTMImage ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = convert16bit2_8bit_(processedMerge.clone());
|
||||||
|
cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
|
||||||
|
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "GTM_gamma.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Step 7: sharpen
|
||||||
|
finalOutputImage = sharpenTriple(processedMerge.clone(), params.tuning, params.options);
|
||||||
|
cv::Mat& processedImage = finalOutputImage;
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeFinalImage"]){
|
||||||
|
std::cout<<"writing FinalImage ..."<<std::endl;
|
||||||
|
cv::Mat outputImg = convert16bit2_8bit_(processedImage.clone());
|
||||||
|
cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
|
||||||
|
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "FinalImage.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// write final ref
|
||||||
|
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
|
||||||
|
if(params.flags["writeReferenceFinal"]){
|
||||||
|
std::cout<<"writing Final Ref Image ..."<<std::endl;
|
||||||
|
if(params.options.ltmGain){
|
||||||
|
params.options.ltmGain = gain;
|
||||||
|
}
|
||||||
|
cv::Mat shortExposureRef, longExposureRef, fusedExposureRef;
|
||||||
|
localToneMap(processedRefImage, params.options,shortExposureRef,longExposureRef,fusedExposureRef,gain);
|
||||||
|
if(params.options.gtmContrast){ // contrast enhancement / global tone mapping
|
||||||
|
processedRefImage = enhanceContrast(processedRefImage, params.options);
|
||||||
|
}
|
||||||
|
processedRefImage = gammasRGB(processedRefImage.clone(),true);
|
||||||
|
// sharpen
|
||||||
|
processedRefImage = sharpenTriple(processedRefImage.clone(), params.tuning, params.options);
|
||||||
|
cv::Mat outputImg = convert16bit2_8bit_(processedRefImage.clone());
|
||||||
|
cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
|
||||||
|
|
||||||
|
cv::imwrite(DBG_OUTPUT_ROOT "FinalReference.jpg", outputImg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// End of finishing
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish::copy_mat_16U(cv::Mat& A, cv::Mat B){
|
||||||
|
u_int16_t* ptr_A = (u_int16_t*)A.data;
|
||||||
|
u_int16_t* ptr_B = (u_int16_t*)B.data;
|
||||||
|
for(int r = 0; r < A.rows; r++) {
|
||||||
|
for(int c = 0; c < A.cols; c++) {
|
||||||
|
*(ptr_A+r*A.cols+c) = *(ptr_B+r*B.cols+c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void finish::copy_rawImg2libraw(std::shared_ptr<LibRaw>& libraw_ptr, cv::Mat B){
|
||||||
|
u_int16_t* ptr_A = (u_int16_t*)libraw_ptr->imgdata.rawdata.raw_image;
|
||||||
|
u_int16_t* ptr_B = (u_int16_t*)B.data;
|
||||||
|
for(int r = 0; r < B.rows; r++) {
|
||||||
|
for(int c = 0; c < B.cols; c++) {
|
||||||
|
*(ptr_A+r*B.cols+c) = *(ptr_B+r*B.cols+c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,55 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility> // std::pair
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include "hdrplus/hdrplus_pipeline.h"
|
||||||
|
#include "hdrplus/burst.h"
|
||||||
|
#include "hdrplus/align.h"
|
||||||
|
#include "hdrplus/merge.h"
|
||||||
|
#include "hdrplus/finish.h"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
void hdrplus_pipeline::run_pipeline( \
|
||||||
|
const std::string& burst_path, \
|
||||||
|
const std::string& reference_image_path )
|
||||||
|
{
|
||||||
|
// Create burst of images
|
||||||
|
burst burst_images( burst_path, reference_image_path );
|
||||||
|
std::vector<std::vector<std::vector<std::pair<int, int>>>> alignments;
|
||||||
|
|
||||||
|
// Run align
|
||||||
|
align_module.process( burst_images, alignments );
|
||||||
|
|
||||||
|
// Run merging
|
||||||
|
merge_module.process( burst_images, alignments );
|
||||||
|
|
||||||
|
// Run finishing
|
||||||
|
cv::Mat finalImg;
|
||||||
|
finish_module.process( burst_images, finalImg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hdrplus_pipeline::run_pipeline( \
|
||||||
|
const std::vector<std::string>& burst_paths, \
|
||||||
|
int reference_image_index, cv::Mat& finalImg )
|
||||||
|
{
|
||||||
|
// Create burst of images
|
||||||
|
burst burst_images( burst_paths, reference_image_index );
|
||||||
|
std::vector<std::vector<std::vector<std::pair<int, int>>>> alignments;
|
||||||
|
|
||||||
|
// Run align
|
||||||
|
align_module.process( burst_images, alignments );
|
||||||
|
|
||||||
|
// Run merging
|
||||||
|
merge_module.process( burst_images, alignments );
|
||||||
|
|
||||||
|
// Run finishing
|
||||||
|
finish_module.process( burst_images, finalImg);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,333 @@
|
|||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
#include "hdrplus/merge.h"
|
||||||
|
#include "hdrplus/burst.h"
|
||||||
|
#include "hdrplus/utility.h"
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
void merge::process(hdrplus::burst& burst_images, \
|
||||||
|
std::vector<std::vector<std::vector<std::pair<int, int>>>>& alignments)
|
||||||
|
{
|
||||||
|
// 4.1 Noise Parameters and RMS
|
||||||
|
// Noise parameters calculated from baseline ISO noise parameters
|
||||||
|
double lambda_shot, lambda_read;
|
||||||
|
std::tie(lambda_shot, lambda_read) = burst_images.bayer_images[burst_images.reference_image_idx].get_noise_params();
|
||||||
|
|
||||||
|
// 4.2-4.4 Denoising and Merging
|
||||||
|
|
||||||
|
// Get padded bayer image
|
||||||
|
cv::Mat reference_image = burst_images.bayer_images_pad[burst_images.reference_image_idx];
|
||||||
|
cv::imwrite("ref.jpg", reference_image);
|
||||||
|
|
||||||
|
// Get raw channels
|
||||||
|
std::vector<cv::Mat> channels(4);
|
||||||
|
hdrplus::extract_rgb_from_bayer<uint16_t>(reference_image, channels[0], channels[1], channels[2], channels[3]);
|
||||||
|
|
||||||
|
std::vector<cv::Mat> processed_channels(4);
|
||||||
|
// For each channel, perform denoising and merge
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
// Get channel mat
|
||||||
|
cv::Mat channel_i = channels[i];
|
||||||
|
// cv::imwrite("ref" + std::to_string(i) + ".jpg", channel_i);
|
||||||
|
|
||||||
|
//we should be getting the individual channel in the same place where we call the processChannel function with the reference channel in its arguments
|
||||||
|
//possibly we could add another argument in the processChannel function which is the channel_i for the alternate image. maybe using a loop to cover all the other images
|
||||||
|
|
||||||
|
//create list of channel_i of alternate images:
|
||||||
|
std::vector<cv::Mat> alternate_channel_i_list;
|
||||||
|
for (int j = 0; j < burst_images.num_images; j++) {
|
||||||
|
if (j != burst_images.reference_image_idx) {
|
||||||
|
|
||||||
|
//get alternate image
|
||||||
|
cv::Mat alt_image = burst_images.bayer_images_pad[j];
|
||||||
|
std::vector<cv::Mat> alt_channels(4);
|
||||||
|
hdrplus::extract_rgb_from_bayer<uint16_t>(alt_image, alt_channels[0], alt_channels[1], alt_channels[2], alt_channels[3]);
|
||||||
|
|
||||||
|
alternate_channel_i_list.push_back(alt_channels[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply merging on the channel
|
||||||
|
cv::Mat merged_channel = processChannel(burst_images, alignments, channel_i, alternate_channel_i_list, lambda_shot, lambda_read);
|
||||||
|
// cv::imwrite("merged" + std::to_string(i) + ".jpg", merged_channel);
|
||||||
|
|
||||||
|
// Put channel raw data back to channels
|
||||||
|
merged_channel.convertTo(processed_channels[i], CV_16U);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write all channels back to a bayer mat
|
||||||
|
cv::Mat merged(reference_image.rows, reference_image.cols, CV_16U);
|
||||||
|
int x, y;
|
||||||
|
for (y = 0; y < reference_image.rows; ++y){
|
||||||
|
uint16_t* row = merged.ptr<uint16_t>(y);
|
||||||
|
if (y % 2 == 0){
|
||||||
|
uint16_t* i0 = processed_channels[0].ptr<uint16_t>(y / 2);
|
||||||
|
uint16_t* i1 = processed_channels[1].ptr<uint16_t>(y / 2);
|
||||||
|
|
||||||
|
for (x = 0; x < reference_image.cols;){
|
||||||
|
//R
|
||||||
|
row[x] = i0[x / 2];
|
||||||
|
x++;
|
||||||
|
|
||||||
|
//G1
|
||||||
|
row[x] = i1[x / 2];
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uint16_t* i2 = processed_channels[2].ptr<uint16_t>(y / 2);
|
||||||
|
uint16_t* i3 = processed_channels[3].ptr<uint16_t>(y / 2);
|
||||||
|
|
||||||
|
for(x = 0; x < reference_image.cols;){
|
||||||
|
//G2
|
||||||
|
row[x] = i2[x / 2];
|
||||||
|
x++;
|
||||||
|
|
||||||
|
//B
|
||||||
|
row[x] = i3[x / 2];
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove padding
|
||||||
|
std::vector<int> padding = burst_images.padding_info_bayer;
|
||||||
|
cv::Range horizontal = cv::Range(padding[2], reference_image.cols - padding[3]);
|
||||||
|
cv::Range vertical = cv::Range(padding[0], reference_image.rows - padding[1]);
|
||||||
|
burst_images.merged_bayer_image = merged(vertical, horizontal);
|
||||||
|
cv::imwrite("merged.jpg", burst_images.merged_bayer_image);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<cv::Mat> merge::getReferenceTiles(cv::Mat reference_image) {
|
||||||
|
std::vector<cv::Mat> reference_tiles;
|
||||||
|
for (int y = 0; y < reference_image.rows - offset; y += offset) {
|
||||||
|
for (int x = 0; x < reference_image.cols - offset; x += offset) {
|
||||||
|
cv::Mat tile = reference_image(cv::Rect(x, y, TILE_SIZE, TILE_SIZE));
|
||||||
|
reference_tiles.push_back(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reference_tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat merge::mergeTiles(std::vector<cv::Mat> tiles, int num_rows, int num_cols) {
|
||||||
|
// 1. get all four subsets: original (evenly split), horizontal overlapped,
|
||||||
|
// vertical overlapped, 2D overlapped
|
||||||
|
std::vector<std::vector<cv::Mat>> tiles_original;
|
||||||
|
for (int y = 0; y < num_rows / offset - 1; y += 2) {
|
||||||
|
std::vector<cv::Mat> row;
|
||||||
|
for (int x = 0; x < num_cols / offset - 1; x += 2) {
|
||||||
|
row.push_back(tiles[y * (num_cols / offset - 1) + x]);
|
||||||
|
}
|
||||||
|
tiles_original.push_back(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<cv::Mat>> tiles_horizontal;
|
||||||
|
for (int y = 0; y < num_rows / offset - 1; y += 2) {
|
||||||
|
std::vector<cv::Mat> row;
|
||||||
|
for (int x = 1; x < num_cols / offset - 1; x += 2) {
|
||||||
|
row.push_back(tiles[y * (num_cols / offset - 1) + x]);
|
||||||
|
}
|
||||||
|
tiles_horizontal.push_back(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<cv::Mat>> tiles_vertical;
|
||||||
|
for (int y = 1; y < num_rows / offset - 1; y += 2) {
|
||||||
|
std::vector<cv::Mat> row;
|
||||||
|
for (int x = 0; x < num_cols / offset - 1; x += 2) {
|
||||||
|
row.push_back(tiles[y * (num_cols / offset - 1) + x]);
|
||||||
|
}
|
||||||
|
tiles_vertical.push_back(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<cv::Mat>> tiles_2d;
|
||||||
|
for (int y = 1; y < num_rows / offset - 1; y += 2) {
|
||||||
|
std::vector<cv::Mat> row;
|
||||||
|
for (int x = 1; x < num_cols / offset - 1; x += 2) {
|
||||||
|
row.push_back(tiles[y * (num_cols / offset - 1) + x]);
|
||||||
|
}
|
||||||
|
tiles_2d.push_back(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Concatenate the four subsets
|
||||||
|
cv::Mat img_original = cat2Dtiles(tiles_original);
|
||||||
|
cv::Mat img_horizontal = cat2Dtiles(tiles_horizontal);
|
||||||
|
cv::Mat img_vertical = cat2Dtiles(tiles_vertical);
|
||||||
|
cv::Mat img_2d = cat2Dtiles(tiles_2d);
|
||||||
|
|
||||||
|
// 3. Add the four subsets together
|
||||||
|
img_original(cv::Rect(offset, 0, num_cols - TILE_SIZE, num_rows)) += img_horizontal;
|
||||||
|
img_original(cv::Rect(0, offset, num_cols, num_rows - TILE_SIZE)) += img_vertical;
|
||||||
|
img_original(cv::Rect(offset, offset, num_cols - TILE_SIZE, num_rows - TILE_SIZE)) += img_2d;
|
||||||
|
|
||||||
|
return img_original;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat merge::processChannel(hdrplus::burst& burst_images, \
|
||||||
|
std::vector<std::vector<std::vector<std::pair<int, int>>>>& alignments, \
|
||||||
|
cv::Mat channel_image, \
|
||||||
|
std::vector<cv::Mat> alternate_channel_i_list,\
|
||||||
|
float lambda_shot, \
|
||||||
|
float lambda_read) {
|
||||||
|
// Get tiles of the reference image
|
||||||
|
std::vector<cv::Mat> reference_tiles = getReferenceTiles(channel_image);
|
||||||
|
|
||||||
|
// Get noise variance (sigma**2 = lambda_shot * tileRMS + lambda_read)
|
||||||
|
std::vector<float> noise_variance = getNoiseVariance(reference_tiles, lambda_shot, lambda_read);
|
||||||
|
|
||||||
|
// Apply FFT on reference tiles (spatial to frequency)
|
||||||
|
std::vector<cv::Mat> reference_tiles_DFT;
|
||||||
|
for (auto ref_tile : reference_tiles) {
|
||||||
|
cv::Mat ref_tile_DFT;
|
||||||
|
ref_tile.convertTo(ref_tile_DFT, CV_32F);
|
||||||
|
cv::dft(ref_tile_DFT, ref_tile_DFT, cv::DFT_COMPLEX_OUTPUT);
|
||||||
|
reference_tiles_DFT.push_back(ref_tile_DFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire alternate tiles and apply FFT on them as well
|
||||||
|
std::vector<std::vector<cv::Mat>> alt_tiles_list(reference_tiles.size());
|
||||||
|
int num_tiles_row = alternate_channel_i_list[0].rows / offset - 1;
|
||||||
|
int num_tiles_col = alternate_channel_i_list[0].cols / offset - 1;
|
||||||
|
for (int y = 0; y < num_tiles_row; ++y) {
|
||||||
|
for (int x = 0; x < num_tiles_col; ++x) {
|
||||||
|
std::vector<cv::Mat> alt_tiles;
|
||||||
|
// Get reference tile location
|
||||||
|
int top_left_y = y * offset;
|
||||||
|
int top_left_x = x * offset;
|
||||||
|
|
||||||
|
for (int i = 0; i < alternate_channel_i_list.size(); ++i) {
|
||||||
|
// Get alignment displacement
|
||||||
|
int displacement_y, displacement_x;
|
||||||
|
std::tie(displacement_y, displacement_x) = alignments[i + 1][y][x];
|
||||||
|
// Get tile
|
||||||
|
cv::Mat alt_tile = alternate_channel_i_list[i](cv::Rect(top_left_x + displacement_x, top_left_y + displacement_y, TILE_SIZE, TILE_SIZE));
|
||||||
|
// Apply FFT
|
||||||
|
cv::Mat alt_tile_DFT;
|
||||||
|
alt_tile.convertTo(alt_tile_DFT, CV_32F);
|
||||||
|
cv::dft(alt_tile_DFT, alt_tile_DFT, cv::DFT_COMPLEX_OUTPUT);
|
||||||
|
alt_tiles.push_back(alt_tile_DFT);
|
||||||
|
}
|
||||||
|
alt_tiles_list[y * num_tiles_col + x] = alt_tiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.2 Temporal Denoising
|
||||||
|
reference_tiles_DFT = temporal_denoise(reference_tiles_DFT, alt_tiles_list, noise_variance, TEMPORAL_FACTOR);
|
||||||
|
|
||||||
|
// 4.3 Spatial Denoising
|
||||||
|
reference_tiles_DFT = spatial_denoise(reference_tiles_DFT, alternate_channel_i_list.size(), noise_variance, SPATIAL_FACTOR);
|
||||||
|
//now reference tiles are temporally and spatially denoised
|
||||||
|
|
||||||
|
// Apply IFFT on reference tiles (frequency to spatial)
|
||||||
|
std::vector<cv::Mat> denoised_tiles;
|
||||||
|
for (auto dft_tile : reference_tiles_DFT) {
|
||||||
|
cv::Mat denoised_tile;
|
||||||
|
cv::divide(dft_tile, TILE_SIZE * TILE_SIZE, dft_tile);
|
||||||
|
cv::dft(dft_tile, denoised_tile, cv::DFT_INVERSE | cv::DFT_REAL_OUTPUT);
|
||||||
|
denoised_tiles.push_back(denoised_tile);
|
||||||
|
}
|
||||||
|
reference_tiles = denoised_tiles;
|
||||||
|
|
||||||
|
// 4.4 Cosine Window Merging
|
||||||
|
// Process tiles through 2D cosine window
|
||||||
|
std::vector<cv::Mat> windowed_tiles;
|
||||||
|
for (auto tile : reference_tiles) {
|
||||||
|
windowed_tiles.push_back(cosineWindow2D(tile));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge tiles
|
||||||
|
return mergeTiles(windowed_tiles, channel_image.rows, channel_image.cols);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<cv::Mat> merge::temporal_denoise(std::vector<cv::Mat> tiles, std::vector<std::vector<cv::Mat>> alt_tiles, std::vector<float> noise_variance, float temporal_factor) {
|
||||||
|
// goal: temporially denoise using the weiner filter
|
||||||
|
// input:
|
||||||
|
// 1. array of 2D dft tiles of the reference image
|
||||||
|
// 2. array of 2D dft tiles of the aligned alternate image
|
||||||
|
// 3. estimated noise variance
|
||||||
|
// 4. temporal factor
|
||||||
|
// return: merged image patches dft
|
||||||
|
|
||||||
|
// calculate noise scaling
|
||||||
|
double temporal_noise_scaling = (TILE_SIZE * TILE_SIZE * (2.0 / 16)) * TEMPORAL_FACTOR;
|
||||||
|
|
||||||
|
// loop across tiles
|
||||||
|
std::vector<cv::Mat> denoised;
|
||||||
|
for (int i = 0; i < tiles.size(); ++i) {
|
||||||
|
// sum of pairwise denoising
|
||||||
|
cv::Mat tile_sum = tiles[i].clone();
|
||||||
|
double coeff = temporal_noise_scaling * noise_variance[i];
|
||||||
|
|
||||||
|
// Ref tile
|
||||||
|
cv::Mat tile = tiles[i];
|
||||||
|
// Alt tiles
|
||||||
|
std::vector<cv::Mat> alt_tiles_i = alt_tiles[i];
|
||||||
|
|
||||||
|
for (int j = 0; j < alt_tiles_i.size(); ++j) {
|
||||||
|
// Alt tile
|
||||||
|
cv::Mat alt_tile = alt_tiles_i[j];
|
||||||
|
// Tile difference
|
||||||
|
cv::Mat diff = tile - alt_tile;
|
||||||
|
|
||||||
|
// Calculate absolute difference
|
||||||
|
cv::Mat complexMats[2];
|
||||||
|
cv::split(diff, complexMats); // planes[0] = Re(DFT(I)), planes[1] = Im(DFT(I))
|
||||||
|
cv::magnitude(complexMats[0], complexMats[1], complexMats[0]); // planes[0] = magnitude
|
||||||
|
cv::Mat absolute_diff = complexMats[0].mul(complexMats[0]);
|
||||||
|
|
||||||
|
// find shrinkage operator A
|
||||||
|
cv::Mat shrinkage;
|
||||||
|
cv::divide(absolute_diff, absolute_diff + coeff, shrinkage);
|
||||||
|
cv::merge(std::vector<cv::Mat>{shrinkage, shrinkage}, shrinkage);
|
||||||
|
|
||||||
|
// Interpolation
|
||||||
|
tile_sum += alt_tile + diff.mul(shrinkage);
|
||||||
|
}
|
||||||
|
// Average by num of frames
|
||||||
|
cv::divide(tile_sum, alt_tiles_i.size() + 1, tile_sum);
|
||||||
|
denoised.push_back(tile_sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return denoised;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<cv::Mat> merge::spatial_denoise(std::vector<cv::Mat> tiles, int num_alts, std::vector<float> noise_variance, float spatial_factor) {
|
||||||
|
|
||||||
|
double spatial_noise_scaling = (TILE_SIZE * TILE_SIZE * (1.0 / 16)) * spatial_factor;
|
||||||
|
|
||||||
|
// Calculate |w| using ifftshift
|
||||||
|
cv::Mat row_distances = cv::Mat::zeros(1, TILE_SIZE, CV_32F);
|
||||||
|
for(int i = 0; i < TILE_SIZE; ++i) {
|
||||||
|
row_distances.at<float>(i) = i - offset;
|
||||||
|
}
|
||||||
|
row_distances = cv::repeat(row_distances.t(), 1, TILE_SIZE);
|
||||||
|
cv::Mat col_distances = row_distances.t();
|
||||||
|
cv::Mat distances;
|
||||||
|
cv::sqrt(row_distances.mul(row_distances) + col_distances.mul(col_distances), distances);
|
||||||
|
ifftshift(distances);
|
||||||
|
|
||||||
|
std::vector<cv::Mat> denoised;
|
||||||
|
// Loop through all tiles
|
||||||
|
for (int i = 0; i < tiles.size(); ++i) {
|
||||||
|
cv::Mat tile = tiles[i];
|
||||||
|
float coeff = noise_variance[i] / (num_alts + 1) * spatial_noise_scaling;
|
||||||
|
|
||||||
|
// Calculate absolute difference
|
||||||
|
cv::Mat complexMats[2];
|
||||||
|
cv::split(tile, complexMats); // planes[0] = Re(DFT(I)), planes[1] = Im(DFT(I))
|
||||||
|
cv::magnitude(complexMats[0], complexMats[1], complexMats[0]); // planes[0] = magnitude
|
||||||
|
cv::Mat absolute_diff = complexMats[0].mul(complexMats[0]);
|
||||||
|
|
||||||
|
// Division
|
||||||
|
cv::Mat scale;
|
||||||
|
cv::divide(absolute_diff, absolute_diff + distances * coeff, scale);
|
||||||
|
cv::merge(std::vector<cv::Mat>{scale, scale}, scale);
|
||||||
|
denoised.push_back(tile.mul(scale));
|
||||||
|
}
|
||||||
|
return denoised;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace hdrplus
|
@ -0,0 +1,53 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <opencv2/opencv.hpp> // all opencv header
|
||||||
|
#include <hdrplus/params.h>
|
||||||
|
|
||||||
|
namespace hdrplus
|
||||||
|
{
|
||||||
|
|
||||||
|
void setParams(std::shared_ptr<LibRaw>& libraw_ptr, RawpyArgs rawpyArgs){
|
||||||
|
libraw_ptr->imgdata.params.user_qual = rawpyArgs.demosaic_algorithm;
|
||||||
|
libraw_ptr->imgdata.params.half_size = rawpyArgs.half_size;
|
||||||
|
libraw_ptr->imgdata.params.use_camera_wb = rawpyArgs.use_camera_wb;
|
||||||
|
libraw_ptr->imgdata.params.use_auto_wb = rawpyArgs.use_auto_wb;
|
||||||
|
libraw_ptr->imgdata.params.no_auto_bright = rawpyArgs.no_auto_bright;
|
||||||
|
libraw_ptr->imgdata.params.output_color = rawpyArgs.output_color;
|
||||||
|
libraw_ptr->imgdata.params.gamm[0] = rawpyArgs.gamma[0];
|
||||||
|
libraw_ptr->imgdata.params.gamm[1] = rawpyArgs.gamma[1];
|
||||||
|
libraw_ptr->imgdata.params.output_bps = rawpyArgs.output_bps;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat postprocess(std::shared_ptr<LibRaw>& libraw_ptr, RawpyArgs rawpyArgs){
|
||||||
|
std::cout<<"postprocessing..."<<std::endl;
|
||||||
|
// set parameters
|
||||||
|
setParams(libraw_ptr,rawpyArgs);
|
||||||
|
|
||||||
|
std::cout<<"conversion to 16 bit using black and white levels, demosaicking, white balance, color correction..."<<std::endl;
|
||||||
|
|
||||||
|
libraw_ptr->dcraw_process();
|
||||||
|
int errorcode;
|
||||||
|
|
||||||
|
libraw_processed_image_t *ret_img = libraw_ptr->dcraw_make_mem_image(&errorcode);
|
||||||
|
|
||||||
|
int opencv_type = CV_16UC3; // 16bit RGB
|
||||||
|
if(ret_img->colors==1){ // grayscale
|
||||||
|
if(ret_img->bits == 8){ // uint8
|
||||||
|
opencv_type = CV_8UC1;
|
||||||
|
}else{ // uint16
|
||||||
|
opencv_type = CV_16UC1;
|
||||||
|
}
|
||||||
|
}else{// RGB
|
||||||
|
if(ret_img->bits == 8){ //8bit
|
||||||
|
opencv_type = CV_8UC3;
|
||||||
|
}else{ // 16bit
|
||||||
|
opencv_type = CV_16UC3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat processedImg(ret_img->height,ret_img->width,opencv_type,ret_img->data);
|
||||||
|
|
||||||
|
std::cout<<"postprocess finished!"<<std::endl;
|
||||||
|
return processedImg;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue