vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
#include "opencv2/xphoto.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
|
||||
const char* keys =
|
||||
{
|
||||
"{i || input image name}"
|
||||
"{o || output image name}"
|
||||
"{sigma || expected noise standard deviation}"
|
||||
"{tw |4| template window size}"
|
||||
"{sw |16| search window size}"
|
||||
};
|
||||
|
||||
int main(int argc, const char** argv)
|
||||
{
|
||||
bool printHelp = (argc == 1);
|
||||
printHelp = printHelp || (argc == 2 && std::string(argv[1]) == "--help");
|
||||
printHelp = printHelp || (argc == 2 && std::string(argv[1]) == "-h");
|
||||
|
||||
if (printHelp)
|
||||
{
|
||||
printf("\nThis sample demonstrates BM3D image denoising\n"
|
||||
"Call:\n"
|
||||
" bm3d_image_denoising -i=<string> -sigma=<double> -tw=<int> -sw=<int> [-o=<string>]\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
cv::CommandLineParser parser(argc, argv, keys);
|
||||
if (!parser.check())
|
||||
{
|
||||
parser.printErrors();
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string inFilename = parser.get<std::string>("i");
|
||||
std::string outFilename = parser.get<std::string>("o");
|
||||
|
||||
cv::Mat src = cv::imread(inFilename, cv::IMREAD_GRAYSCALE);
|
||||
if (src.empty())
|
||||
{
|
||||
printf("Cannot read image file: %s\n", inFilename.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
float sigma = parser.get<float>("sigma");
|
||||
if (sigma == 0.0)
|
||||
sigma = 15.0;
|
||||
|
||||
int templateWindowSize = parser.get<int>("tw");
|
||||
if (templateWindowSize == 0)
|
||||
templateWindowSize = 4;
|
||||
|
||||
int searchWindowSize = parser.get<int>("sw");
|
||||
if (searchWindowSize == 0)
|
||||
searchWindowSize = 16;
|
||||
|
||||
cv::Mat res(src.size(), src.type());
|
||||
cv::xphoto::bm3dDenoising(src, res, sigma, templateWindowSize, searchWindowSize);
|
||||
|
||||
if (outFilename.empty())
|
||||
{
|
||||
cv::namedWindow("input image", cv::WINDOW_NORMAL);
|
||||
cv::imshow("input image", src);
|
||||
cv::namedWindow("denoising result", cv::WINDOW_NORMAL);
|
||||
cv::imshow("denoising result", res);
|
||||
cv::waitKey(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
cv::imwrite(outFilename, res);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "opencv2/xphoto.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
const char *keys = { "{help h usage ? | | print this message}"
|
||||
"{i | | input image name }"
|
||||
"{o | | output image name }"
|
||||
"{a |grayworld| color balance algorithm (simple, grayworld or learning_based)}"
|
||||
"{m | | path to the model for the learning-based algorithm (optional) }" };
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
CommandLineParser parser(argc, argv, keys);
|
||||
parser.about("OpenCV color balance demonstration sample");
|
||||
if (parser.has("help") || argc < 2)
|
||||
{
|
||||
parser.printMessage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
string inFilename = parser.get<string>("i");
|
||||
string outFilename = parser.get<string>("o");
|
||||
string algorithm = parser.get<string>("a");
|
||||
string modelFilename = parser.get<string>("m");
|
||||
|
||||
if (!parser.check())
|
||||
{
|
||||
parser.printErrors();
|
||||
return -1;
|
||||
}
|
||||
|
||||
Mat src = imread(inFilename, 1);
|
||||
if (src.empty())
|
||||
{
|
||||
printf("Cannot read image file: %s\n", inFilename.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
Mat res;
|
||||
Ptr<xphoto::WhiteBalancer> wb;
|
||||
if (algorithm == "simple")
|
||||
wb = xphoto::createSimpleWB();
|
||||
else if (algorithm == "grayworld")
|
||||
wb = xphoto::createGrayworldWB();
|
||||
else if (algorithm == "learning_based")
|
||||
wb = xphoto::createLearningBasedWB(modelFilename);
|
||||
else
|
||||
{
|
||||
printf("Unsupported algorithm: %s\n", algorithm.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
wb->balanceWhite(src, res);
|
||||
|
||||
if (outFilename == "")
|
||||
{
|
||||
namedWindow("after white balance", 1);
|
||||
imshow("after white balance", res);
|
||||
|
||||
waitKey(0);
|
||||
}
|
||||
else
|
||||
imwrite(outFilename, res);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
import os, sys, argparse, json
|
||||
import numpy as np
|
||||
import scipy.io
|
||||
import cv2 as cv
|
||||
import timeit
|
||||
from learn_color_balance import load_ground_truth
|
||||
|
||||
|
||||
def load_json(path):
|
||||
f = open(path, "r")
|
||||
data = json.load(f)
|
||||
return data
|
||||
|
||||
|
||||
def save_json(obj, path):
|
||||
tmp_file = path + ".bak"
|
||||
f = open(tmp_file, "w")
|
||||
json.dump(obj, f, indent=2)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
f.close()
|
||||
try:
|
||||
os.rename(tmp_file, path)
|
||||
except:
|
||||
os.remove(path)
|
||||
os.rename(tmp_file, path)
|
||||
|
||||
|
||||
def parse_sequence(input_str):
|
||||
if len(input_str) == 0:
|
||||
return []
|
||||
else:
|
||||
return [o.strip() for o in input_str.split(",") if o]
|
||||
|
||||
|
||||
def stretch_to_8bit(arr, clip_percentile = 2.5):
|
||||
arr = np.clip(arr * (255.0 / np.percentile(arr, 100 - clip_percentile)), 0, 255)
|
||||
return arr.astype(np.uint8)
|
||||
|
||||
|
||||
def evaluate(im, algo, gt_illuminant, i, range_thresh, bin_num, dst_folder, model_folder):
|
||||
new_im = None
|
||||
start_time = timeit.default_timer()
|
||||
if algo=="grayworld":
|
||||
inst = cv.xphoto.createGrayworldWB()
|
||||
inst.setSaturationThreshold(0.95)
|
||||
new_im = inst.balanceWhite(im)
|
||||
elif algo=="nothing":
|
||||
new_im = im
|
||||
elif algo.split(":")[0]=="learning_based":
|
||||
model_path = ""
|
||||
if len(algo.split(":"))>1:
|
||||
model_path = os.path.join(model_folder, algo.split(":")[1])
|
||||
inst = cv.xphoto.createLearningBasedWB(model_path)
|
||||
inst.setRangeMaxVal(range_thresh)
|
||||
inst.setSaturationThreshold(0.98)
|
||||
inst.setHistBinNum(bin_num)
|
||||
new_im = inst.balanceWhite(im)
|
||||
elif algo=="GT":
|
||||
gains = gt_illuminant / min(gt_illuminant)
|
||||
g1 = float(1.0 / gains[2])
|
||||
g2 = float(1.0 / gains[1])
|
||||
g3 = float(1.0 / gains[0])
|
||||
new_im = cv.xphoto.applyChannelGains(im, g1, g2, g3)
|
||||
time = 1000*(timeit.default_timer() - start_time) #time in ms
|
||||
|
||||
if len(dst_folder)>0:
|
||||
if not os.path.exists(dst_folder):
|
||||
os.makedirs(dst_folder)
|
||||
im_name = ("%04d_" % i) + algo.replace(":","_") + ".jpg"
|
||||
cv.imwrite(os.path.join(dst_folder, im_name), stretch_to_8bit(new_im))
|
||||
|
||||
#recover the illuminant from the color balancing result, assuming the standard model:
|
||||
estimated_illuminant = [0, 0, 0]
|
||||
eps = 0.01
|
||||
estimated_illuminant[2] = np.percentile((im[:,:,0] + eps) / (new_im[:,:,0] + eps), 50)
|
||||
estimated_illuminant[1] = np.percentile((im[:,:,1] + eps) / (new_im[:,:,1] + eps), 50)
|
||||
estimated_illuminant[0] = np.percentile((im[:,:,2] + eps) / (new_im[:,:,2] + eps), 50)
|
||||
|
||||
res = np.arccos(np.dot(gt_illuminant,estimated_illuminant)/
|
||||
(np.linalg.norm(gt_illuminant) * np.linalg.norm(estimated_illuminant)))
|
||||
return (time, (res / np.pi) * 180)
|
||||
|
||||
|
||||
def build_html_table(out, state, stat_list, img_range):
|
||||
stat_dict = {'mean': ('Mean error', lambda arr: np.mean(arr)),
|
||||
'median': ('Median error',lambda arr: np.percentile(arr, 50)),
|
||||
'p05': ('5<sup>th</sup> percentile',lambda arr: np.percentile(arr, 5)),
|
||||
'p20': ('20<sup>th</sup> percentile',lambda arr: np.percentile(arr, 20)),
|
||||
'p80': ('80<sup>th</sup> percentile',lambda arr: np.percentile(arr, 80)),
|
||||
'p95': ('95<sup>th</sup> percentile',lambda arr: np.percentile(arr, 95))
|
||||
}
|
||||
html_out = ['<style type="text/css">\n',
|
||||
' html, body {font-family: Lucida Console, Courier New, Courier;font-size: 16px;color:#3e4758;}\n',
|
||||
' .tbl{background:none repeat scroll 0 0 #FFFFFF;border-collapse:collapse;font-family:"Lucida Sans Unicode","Lucida Grande",Sans-Serif;font-size:14px;margin:20px;text-align:left;width:480px;margin-left: auto;margin-right: auto;white-space:nowrap;}\n',
|
||||
' .tbl span{display:block;white-space:nowrap;}\n',
|
||||
' .tbl thead tr:last-child th {padding-bottom:5px;}\n',
|
||||
' .tbl tbody tr:first-child td {border-top:3px solid #6678B1;}\n',
|
||||
' .tbl th{border:none;color:#003399;font-size:16px;font-weight:normal;white-space:nowrap;padding:3px 10px;}\n',
|
||||
' .tbl td{border:none;border-bottom:1px solid #CCCCCC;color:#666699;padding:6px 8px;white-space:nowrap;}\n',
|
||||
' .tbl tbody tr:hover td{color:#000099;}\n',
|
||||
' .tbl caption{font:italic 16px "Trebuchet MS",Verdana,Arial,Helvetica,sans-serif;padding:0 0 5px;text-align:right;white-space:normal;}\n',
|
||||
' .firstingroup {border-top:2px solid #6678B1;}\n',
|
||||
'</style>\n\n']
|
||||
|
||||
html_out += ['<table class="tbl">\n',
|
||||
' <thead>\n',
|
||||
' <tr>\n',
|
||||
' <th align="center" valign="top"> Algorithm Name </th>\n',
|
||||
' <th align="center" valign="top"> Average Time </th>\n']
|
||||
for stat in stat_list:
|
||||
if stat not in stat_dict.keys():
|
||||
print("Error: unsupported statistic " + stat)
|
||||
sys.exit(1)
|
||||
html_out += [' <th align="center" valign="top"> ' +
|
||||
stat_dict[stat][0] +
|
||||
' </th>\n']
|
||||
html_out += [' </tr>\n',
|
||||
' </thead>\n',
|
||||
' <tbody>\n']
|
||||
|
||||
for algorithm in state.keys():
|
||||
arr = [state[algorithm][file]["angular_error"] for file in state[algorithm].keys() if file>=img_range[0] and file<=img_range[1]]
|
||||
average_time = "%.2f ms" % np.mean([state[algorithm][file]["time"] for file in state[algorithm].keys()
|
||||
if file>=img_range[0] and file<=img_range[1]])
|
||||
html_out += [' <tr>\n',
|
||||
' <td>' + algorithm + '</td>\n',
|
||||
' <td>' + average_time + '</td>\n']
|
||||
for stat in stat_list:
|
||||
html_out += [' <td> ' +
|
||||
"%.2f°" % stat_dict[stat][1](arr) +
|
||||
' </td>\n']
|
||||
html_out += [' </tr>\n']
|
||||
html_out += [' </tbody>\n',
|
||||
'</table>\n']
|
||||
f = open(out, 'w')
|
||||
f.writelines(html_out)
|
||||
f.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
description=("A benchmarking script for color balance algorithms"),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--algorithms",
|
||||
metavar="ALGORITHMS",
|
||||
default="",
|
||||
help=("Comma-separated list of color balance algorithms to evaluate. "
|
||||
"Currently available: GT,learning_based,grayworld,nothing. "
|
||||
"Use a colon to set a specific model for the learning-based "
|
||||
"algorithm, e.g. learning_based:model1.yml,learning_based:model2.yml"))
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--input_folder",
|
||||
metavar="INPUT_FOLDER",
|
||||
default="",
|
||||
help=("Folder containing input images to evaluate on. Assumes minimally "
|
||||
"processed png images like in the Gehler-Shi (http://www.cs.sfu.ca/~colour/data/shi_gehler/) "
|
||||
"or NUS 8-camera (http://www.comp.nus.edu.sg/~whitebal/illuminant/illuminant.html) datasets"))
|
||||
parser.add_argument(
|
||||
"-g",
|
||||
"--ground_truth",
|
||||
metavar="GROUND_TRUTH",
|
||||
default="real_illum_568..mat",
|
||||
help=("Path to the mat file containing ground truth illuminations. Currently "
|
||||
"supports formats supplied by the Gehler-Shi and NUS 8-camera datasets."))
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--out",
|
||||
metavar="OUT",
|
||||
default="./white_balance_eval_result.html",
|
||||
help="Path to the output html table")
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--state",
|
||||
metavar="STATE_JSON",
|
||||
default="./WB_evaluation_state.json",
|
||||
help=("Path to a json file that stores the current evaluation state"))
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--stats",
|
||||
metavar="STATS",
|
||||
default="mean,median,p05,p20,p80,p95",
|
||||
help=("Comma-separated list of error statistics to compute and list "
|
||||
"in the output table. All the available ones are used by default"))
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
"--input_bit_depth",
|
||||
metavar="INPUT_BIT_DEPTH",
|
||||
default="",
|
||||
help=("Assumed bit depth for input images. Should be specified in order to "
|
||||
"use full bit depth for evaluation (for instance, -b 12 for 12 bit images). "
|
||||
"Otherwise, input images are converted to 8 bit prior to the evaluation."))
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--dst_folder",
|
||||
metavar="DST_FOLDER",
|
||||
default="",
|
||||
help=("If specified, this folder will be used to store the color correction results"))
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--range",
|
||||
metavar="RANGE",
|
||||
default="0,0",
|
||||
help=("Comma-separated range of images from the dataset to evaluate on (for instance: 0,568). "
|
||||
"All available images are used by default."))
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--model_folder",
|
||||
metavar="MODEL_FOLDER",
|
||||
default="",
|
||||
help=("Path to the folder containing models for the learning-based color balance algorithm (optional)"))
|
||||
args, other_args = parser.parse_known_args()
|
||||
|
||||
if not os.path.exists(args.input_folder):
|
||||
print("Error: " + args.input_folder + (" does not exist. Please, correctly "
|
||||
"specify the -i parameter"))
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.exists(args.ground_truth):
|
||||
print("Error: " + args.ground_truth + (" does not exist. Please, correctly "
|
||||
"specify the -g parameter"))
|
||||
sys.exit(1)
|
||||
|
||||
state = {}
|
||||
if os.path.isfile(args.state):
|
||||
state = load_json(args.state)
|
||||
|
||||
algorithm_list = parse_sequence(args.algorithms)
|
||||
img_range = list(map(int, parse_sequence(args.range)))
|
||||
if len(img_range)!=2:
|
||||
print("Error: Please specify the -r parameter in form <first_image_index>,<last_image_index>")
|
||||
sys.exit(1)
|
||||
|
||||
img_files = sorted(os.listdir(args.input_folder))
|
||||
(gt_illuminants,black_levels) = load_ground_truth(args.ground_truth)
|
||||
|
||||
for algorithm in algorithm_list:
|
||||
i = 0
|
||||
if algorithm not in state.keys():
|
||||
state[algorithm] = {}
|
||||
sz = len(img_files)
|
||||
for file in img_files:
|
||||
if file not in state[algorithm].keys() and\
|
||||
((i>=img_range[0] and i<img_range[1]) or img_range[0]==img_range[1]==0):
|
||||
cur_path = os.path.join(args.input_folder, file)
|
||||
im = cv.imread(cur_path, -1).astype(np.float32)
|
||||
im -= black_levels[i]
|
||||
range_thresh = 255
|
||||
if len(args.input_bit_depth)>0:
|
||||
range_thresh = 2**int(args.input_bit_depth) - 1
|
||||
im = np.clip(im, 0, range_thresh).astype(np.uint16)
|
||||
else:
|
||||
im = stretch_to_8bit(im)
|
||||
|
||||
(time,angular_err) = evaluate(im, algorithm, gt_illuminants[i], i, range_thresh,
|
||||
256 if range_thresh > 255 else 64, args.dst_folder, args.model_folder)
|
||||
state[algorithm][file] = {"angular_error": angular_err, "time": time}
|
||||
sys.stdout.write("Algorithm: %-20s Done: [%3d/%3d]\r" % (algorithm, i, sz)),
|
||||
sys.stdout.flush()
|
||||
save_json(state, args.state)
|
||||
i+=1
|
||||
save_json(state, args.state)
|
||||
build_html_table(args.out, state, parse_sequence(args.stats), [img_files[img_range[0]], img_files[img_range[1]-1]])
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "opencv2/xphoto.hpp"
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
|
||||
#include "opencv2/core/utility.hpp"
|
||||
|
||||
const char* keys =
|
||||
{
|
||||
"{i || input image name}"
|
||||
"{o || output image name}"
|
||||
"{sigma || expected noise standard deviation}"
|
||||
"{psize |16| expected noise standard deviation}"
|
||||
};
|
||||
|
||||
int main( int argc, const char** argv )
|
||||
{
|
||||
bool printHelp = ( argc == 1 );
|
||||
printHelp = printHelp || ( argc == 2 && std::string(argv[1]) == "--help" );
|
||||
printHelp = printHelp || ( argc == 2 && std::string(argv[1]) == "-h" );
|
||||
|
||||
if ( printHelp )
|
||||
{
|
||||
printf("\nThis sample demonstrates dct-based image denoising\n"
|
||||
"Call:\n"
|
||||
" dct_image_denoising -i=<string> -sigma=<double> -psize=<int> [-o=<string>]\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
cv::CommandLineParser parser(argc, argv, keys);
|
||||
if ( !parser.check() )
|
||||
{
|
||||
parser.printErrors();
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string inFilename = parser.get<std::string>("i");
|
||||
std::string outFilename = parser.get<std::string>("o");
|
||||
|
||||
cv::Mat src = cv::imread(inFilename, 1);
|
||||
if ( src.empty() )
|
||||
{
|
||||
printf("Cannot read image file: %s\n", inFilename.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
double sigma = parser.get<double>("sigma");
|
||||
if (sigma == 0.0)
|
||||
sigma = 15.0;
|
||||
|
||||
int psize = parser.get<int>("psize");
|
||||
if (psize == 0)
|
||||
psize = 16;
|
||||
|
||||
cv::Mat res(src.size(), src.type());
|
||||
cv::xphoto::dctDenoising(src, res, sigma, psize);
|
||||
|
||||
if ( outFilename == "" )
|
||||
{
|
||||
cv::namedWindow("denoising result", 1);
|
||||
cv::imshow("denoising result", res);
|
||||
|
||||
cv::waitKey(0);
|
||||
}
|
||||
else
|
||||
cv::imwrite(outFilename, res);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "opencv2/xphoto.hpp"
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
|
||||
const char* keys =
|
||||
{
|
||||
"{i || input image name}"
|
||||
"{m || mask image name}"
|
||||
"{o || output image name}"
|
||||
};
|
||||
|
||||
int main( int argc, const char** argv )
|
||||
{
|
||||
bool printHelp = ( argc == 1 );
|
||||
printHelp = printHelp || ( argc == 2 && std::string(argv[1]) == "--help" );
|
||||
printHelp = printHelp || ( argc == 2 && std::string(argv[1]) == "-h" );
|
||||
|
||||
if ( printHelp )
|
||||
{
|
||||
printf("\nThis sample demonstrates shift-map image inpainting\n"
|
||||
"Call:\n"
|
||||
" inpainting -i=<string> -m=<string> [-o=<string>]\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
cv::CommandLineParser parser(argc, argv, keys);
|
||||
if ( !parser.check() )
|
||||
{
|
||||
parser.printErrors();
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string inFilename = parser.get<std::string>("i");
|
||||
std::string maskFilename = parser.get<std::string>("m");
|
||||
std::string outFilename = parser.get<std::string>("o");
|
||||
|
||||
cv::Mat src = cv::imread(inFilename, cv::IMREAD_UNCHANGED);
|
||||
if ( src.empty() )
|
||||
{
|
||||
printf( "Cannot read image file: %s\n", inFilename.c_str() );
|
||||
return -1;
|
||||
}
|
||||
|
||||
cv::cvtColor(src, src, cv::COLOR_BGR2Lab);
|
||||
|
||||
cv::Mat mask = cv::imread(maskFilename, cv::IMREAD_GRAYSCALE);
|
||||
if ( mask.empty() )
|
||||
{
|
||||
printf( "Cannot read image file: %s\n", maskFilename.c_str() );
|
||||
return -1;
|
||||
}
|
||||
cv::threshold(mask, mask, 128, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
|
||||
|
||||
cv::Mat res(src.size(), src.type());
|
||||
|
||||
int time = clock();
|
||||
cv::xphoto::inpaint( src, mask, res, cv::xphoto::INPAINT_SHIFTMAP );
|
||||
std::cout << "time = " << (clock() - time)
|
||||
/ double(CLOCKS_PER_SEC) << std::endl;
|
||||
|
||||
cv::cvtColor(res, res, cv::COLOR_Lab2BGR);
|
||||
|
||||
if ( outFilename == "" )
|
||||
{
|
||||
cv::namedWindow("inpainting result", 1);
|
||||
cv::imshow("inpainting result", res);
|
||||
|
||||
cv::waitKey(0);
|
||||
}
|
||||
else
|
||||
cv::imwrite(outFilename, res);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
import os, sys, argparse
|
||||
import numpy as np
|
||||
import scipy.io
|
||||
from sklearn.tree import DecisionTreeRegressor
|
||||
import cv2 as cv
|
||||
import random
|
||||
|
||||
|
||||
def parse_sequence(input_str):
|
||||
if len(input_str) == 0:
|
||||
return []
|
||||
else:
|
||||
return [o.strip() for o in input_str.split(",") if o]
|
||||
|
||||
|
||||
def convert_to_8bit(arr, clip_percentile = 2.5):
|
||||
arr = np.clip(arr * (255.0 / np.percentile(arr, 100 - clip_percentile)), 0, 255)
|
||||
return arr.astype(np.uint8)
|
||||
|
||||
|
||||
def learn_regression_tree_ensemble(img_features, gt_illuminants, num_trees, max_tree_depth):
|
||||
eps = 0.001
|
||||
inst = [[img_features[i], gt_illuminants[i][0] / (sum(gt_illuminants[i]) + eps),
|
||||
gt_illuminants[i][1] / (sum(gt_illuminants[i]) + eps)] for i in range(len(img_features))]
|
||||
|
||||
inst.sort(key = lambda obj: obj[1]) #sort by r chromaticity
|
||||
stride = int(np.ceil(len(inst) / float(num_trees+1)))
|
||||
sz = 2*stride
|
||||
dst_model = []
|
||||
for tree_idx in range(num_trees):
|
||||
#local group in the training data is additionally weighted by num_trees
|
||||
local_group_range = range(tree_idx*stride, min(tree_idx*stride+sz, len(inst)))
|
||||
X = num_trees * [inst[i][0] for i in local_group_range]
|
||||
y_r = num_trees * [inst[i][1] for i in local_group_range]
|
||||
y_g = num_trees * [inst[i][2] for i in local_group_range]
|
||||
|
||||
#add the rest of the training data:
|
||||
X = X + [inst[i][0] for i in range(len(inst)) if i not in local_group_range]
|
||||
y_r = y_r + [inst[i][1] for i in range(len(inst)) if i not in local_group_range]
|
||||
y_g = y_g + [inst[i][2] for i in range(len(inst)) if i not in local_group_range]
|
||||
|
||||
local_model = []
|
||||
for feature_idx in range(len(X[0])):
|
||||
tree_r = DecisionTreeRegressor(max_depth = max_tree_depth, random_state = 1234)
|
||||
tree_r.fit([el[feature_idx][0] for el in X], y_r)
|
||||
tree_g = DecisionTreeRegressor(max_depth = max_tree_depth, random_state = 1234)
|
||||
tree_g.fit([el[feature_idx][0] for el in X], y_g)
|
||||
local_model.append([tree_r, tree_g])
|
||||
dst_model.append(local_model)
|
||||
return dst_model
|
||||
|
||||
|
||||
def get_tree_node_lists(tree, tree_depth):
|
||||
dst_feature_idx = (2**tree_depth-1) * [0]
|
||||
dst_thresh_vals = (2**tree_depth-1) * [.5]
|
||||
dst_leaf_vals = (2**tree_depth) * [-1]
|
||||
leaf_idx_offset = (2**tree_depth-1)
|
||||
left = tree.tree_.children_left
|
||||
right = tree.tree_.children_right
|
||||
threshold = tree.tree_.threshold
|
||||
value = tree.tree_.value
|
||||
feature = tree.tree_.feature
|
||||
|
||||
def recurse(left, right, threshold, feature, node, dst_idx, cur_depth):
|
||||
if (threshold[node] != -2):
|
||||
dst_feature_idx[dst_idx] = feature[node]
|
||||
dst_thresh_vals[dst_idx] = threshold[node]
|
||||
if left[node] != -1:
|
||||
recurse (left, right, threshold, feature, left[node], 2*dst_idx+1, cur_depth + 1)
|
||||
if right[node] != -1:
|
||||
recurse (left, right, threshold, feature, right[node], 2*dst_idx+2, cur_depth + 1)
|
||||
else:
|
||||
range_start = 2**(tree_depth - cur_depth) * dst_idx + (2**(tree_depth - cur_depth) - 1) - leaf_idx_offset
|
||||
range_end = 2**(tree_depth - cur_depth) * dst_idx + (2**(tree_depth - cur_depth+1) - 2) - leaf_idx_offset + 1
|
||||
dst_leaf_vals[range_start:range_end] = (range_end - range_start) * [value[node][0][0]]
|
||||
|
||||
recurse(left, right, threshold, feature, 0, 0, 0)
|
||||
return (dst_feature_idx, dst_thresh_vals, dst_leaf_vals)
|
||||
|
||||
|
||||
def generate_code(model, input_params, use_YML, out_file):
|
||||
feature_idx = []
|
||||
thresh_vals = []
|
||||
leaf_vals = []
|
||||
depth = int(input_params["--max_tree_depth"])
|
||||
for local_model in model:
|
||||
for feature in local_model:
|
||||
(local_feature_idx, local_thresh_vals, local_leaf_vals) = get_tree_node_lists(feature[0], depth)
|
||||
feature_idx += local_feature_idx
|
||||
thresh_vals += local_thresh_vals
|
||||
leaf_vals += local_leaf_vals
|
||||
(local_feature_idx, local_thresh_vals, local_leaf_vals) = get_tree_node_lists(feature[1], depth)
|
||||
feature_idx += local_feature_idx
|
||||
thresh_vals += local_thresh_vals
|
||||
leaf_vals += local_leaf_vals
|
||||
if use_YML:
|
||||
fs = cv.FileStorage(out_file, 1)
|
||||
fs.write("num_trees", len(model))
|
||||
fs.write("num_tree_nodes", 2**depth)
|
||||
fs.write("feature_idx", np.array(feature_idx).astype(np.uint8))
|
||||
fs.write("thresh_vals", np.array(thresh_vals).astype(np.float32))
|
||||
fs.write("leaf_vals", np.array(leaf_vals).astype(np.float32))
|
||||
fs.release()
|
||||
else:
|
||||
res = "/* This file was automatically generated by learn_color_balance.py script\n" +\
|
||||
" * using the following parameters:\n"
|
||||
for key in input_params:
|
||||
res += " " + key + " " + input_params[key]
|
||||
res += "\n */\n"
|
||||
res += "const int num_features = 4;\n"
|
||||
res += "const int _num_trees = " + str(len(model)) + ";\n"
|
||||
res += "const int _num_tree_nodes = " + str(2**depth) + ";\n"
|
||||
|
||||
res += "unsigned char _feature_idx[_num_trees*num_features*2*(_num_tree_nodes-1)] = {" + str(feature_idx[0])
|
||||
for i in range(1,len(feature_idx)):
|
||||
res += "," + str(feature_idx[i])
|
||||
res += "};\n"
|
||||
|
||||
res += "float _thresh_vals[_num_trees*num_features*2*(_num_tree_nodes-1)] = {" + ("%.3ff" % thresh_vals[0])[1:]
|
||||
for i in range(1,len(thresh_vals)):
|
||||
res += "," + ("%.3ff" % thresh_vals[i])[1:]
|
||||
res += "};\n"
|
||||
|
||||
res += "float _leaf_vals[_num_trees*num_features*2*_num_tree_nodes] = {" + ("%.3ff" % leaf_vals[0])[1:]
|
||||
for i in range(1,len(leaf_vals)):
|
||||
res += "," + ("%.3ff" % leaf_vals[i])[1:]
|
||||
res += "};\n"
|
||||
f = open(out_file,"w")
|
||||
f.write(res)
|
||||
f.close()
|
||||
|
||||
|
||||
def load_ground_truth(gt_path):
|
||||
gt = scipy.io.loadmat(gt_path)
|
||||
base_gt_illuminants = []
|
||||
black_levels = []
|
||||
if "groundtruth_illuminants" in gt.keys() and "darkness_level" in gt.keys():
|
||||
#NUS 8-camera dataset format
|
||||
base_gt_illuminants = gt["groundtruth_illuminants"]
|
||||
black_levels = len(base_gt_illuminants) * [gt["darkness_level"][0][0]]
|
||||
elif "real_rgb" in gt.keys():
|
||||
#Gehler-Shi dataset format
|
||||
base_gt_illuminants = gt["real_rgb"]
|
||||
black_levels = 87 * [0] + (len(base_gt_illuminants) - 87) * [129]
|
||||
else:
|
||||
print("Error: unknown ground-truth format, only formats of Gehler-Shi and NUS 8-camera datasets are supported")
|
||||
sys.exit(1)
|
||||
|
||||
return (base_gt_illuminants, black_levels)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
description=("A tool for training the learning-based "
|
||||
"color balance algorithm. Currently supports "
|
||||
"training only on the Gehler-Shi and NUS 8-camera datasets."),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--input_folder",
|
||||
metavar="INPUT_FOLDER",
|
||||
default="",
|
||||
help=("Folder containing the training dataset. Assumes minimally "
|
||||
"processed png images like in the Gehler-Shi (http://www.cs.sfu.ca/~colour/data/shi_gehler/) "
|
||||
"or NUS 8-camera (http://www.comp.nus.edu.sg/~whitebal/illuminant/illuminant.html) datasets"))
|
||||
parser.add_argument(
|
||||
"-g",
|
||||
"--ground_truth",
|
||||
metavar="GROUND_TRUTH",
|
||||
default="real_illum_568..mat",
|
||||
help=("Path to the mat file containing ground truth illuminations. Currently "
|
||||
"supports formats supplied by the Gehler-Shi and NUS 8-camera datasets."))
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--range",
|
||||
metavar="RANGE",
|
||||
default="0,0",
|
||||
help="Range of images from the input dataset to use for training")
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--out",
|
||||
metavar="OUT",
|
||||
default="color_balance_model.yml",
|
||||
help="Path to the output learnt model. Either a .yml (for loading during runtime) "
|
||||
"or .hpp (for compiling with the main code) file ")
|
||||
parser.add_argument(
|
||||
"--hist_bin_num",
|
||||
metavar="HIST_BIN_NUM",
|
||||
default="64",
|
||||
help=("Size of one dimension of a three-dimensional RGB histogram employed in the "
|
||||
"feature extraction step."))
|
||||
parser.add_argument(
|
||||
"--num_trees",
|
||||
metavar="NUM_TREES",
|
||||
default="20",
|
||||
help=("Parameter to control the size of the regression tree ensemble"))
|
||||
parser.add_argument(
|
||||
"--max_tree_depth",
|
||||
metavar="MAX_TREE_DEPTH",
|
||||
default="4",
|
||||
help=("Maxmimum depth of regression trees constructed during training."))
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--num_augmented",
|
||||
metavar="NUM_AUGMENTED",
|
||||
default="2",
|
||||
help=("Number of augmented samples per one training image. Training set "
|
||||
"augmentation tends to improve the learnt model robustness."))
|
||||
|
||||
args, other_args = parser.parse_known_args()
|
||||
|
||||
if not os.path.exists(args.input_folder):
|
||||
print("Error: " + args.input_folder + (" does not exist. Please, correctly "
|
||||
"specify the -i parameter"))
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.exists(args.ground_truth):
|
||||
print("Error: " + args.ground_truth + (" does not exist. Please, correctly "
|
||||
"specify the -g parameter"))
|
||||
sys.exit(1)
|
||||
|
||||
img_range = list(map(int,parse_sequence(args.range)))
|
||||
if len(img_range)!=2:
|
||||
print("Error: Please specify the -r parameter in form <first_image_index>,<last_image_index>")
|
||||
sys.exit(1)
|
||||
|
||||
use_YML = None
|
||||
if args.out.endswith(".yml"):
|
||||
use_YML = True
|
||||
elif args.out.endswith(".hpp"):
|
||||
use_YML = False
|
||||
else:
|
||||
print("Error: Only .hpp and .yml are supported as output formats")
|
||||
sys.exit(1)
|
||||
|
||||
hist_bin_num = int(args.hist_bin_num)
|
||||
num_trees = int(args.num_trees)
|
||||
max_tree_depth = int(args.max_tree_depth)
|
||||
img_files = sorted(os.listdir(args.input_folder))
|
||||
(base_gt_illuminants,black_levels) = load_ground_truth(args.ground_truth)
|
||||
|
||||
features = []
|
||||
gt_illuminants = []
|
||||
i=0
|
||||
sz = len(img_files)
|
||||
random.seed(1234)
|
||||
inst = cv.xphoto.createLearningBasedWB()
|
||||
inst.setRangeMaxVal(255)
|
||||
inst.setSaturationThreshold(0.98)
|
||||
inst.setHistBinNum(hist_bin_num)
|
||||
for file in img_files:
|
||||
if (i>=img_range[0] and i<img_range[1]) or (img_range[0]==img_range[1]==0):
|
||||
cur_path = os.path.join(args.input_folder,file)
|
||||
im = cv.imread(cur_path, -1).astype(np.float32)
|
||||
im -= black_levels[i]
|
||||
im_8bit = convert_to_8bit(im)
|
||||
cur_img_features = inst.extractSimpleFeatures(im_8bit, None)
|
||||
features.append(cur_img_features.tolist())
|
||||
gt_illuminants.append(base_gt_illuminants[i].tolist())
|
||||
|
||||
for iter in range(int(args.num_augmented)):
|
||||
R_coef = random.uniform(0.2, 5.0)
|
||||
G_coef = random.uniform(0.2, 5.0)
|
||||
B_coef = random.uniform(0.2, 5.0)
|
||||
im_8bit = im
|
||||
im_8bit[:,:,0] *= B_coef
|
||||
im_8bit[:,:,1] *= G_coef
|
||||
im_8bit[:,:,2] *= R_coef
|
||||
im_8bit = convert_to_8bit(im)
|
||||
cur_img_features = inst.extractSimpleFeatures(im_8bit, None)
|
||||
features.append(cur_img_features.tolist())
|
||||
illum = base_gt_illuminants[i]
|
||||
illum[0] *= R_coef
|
||||
illum[1] *= G_coef
|
||||
illum[2] *= B_coef
|
||||
gt_illuminants.append(illum.tolist())
|
||||
|
||||
sys.stdout.write("Computing features: [%3d/%3d]\r" % (i, sz)),
|
||||
sys.stdout.flush()
|
||||
i+=1
|
||||
|
||||
print("\nLearning the model...")
|
||||
model = learn_regression_tree_ensemble(features, gt_illuminants, num_trees, max_tree_depth)
|
||||
print("Writing the model...")
|
||||
generate_code(model,{"-r":args.range, "--hist_bin_num": args.hist_bin_num, "--num_trees": args.num_trees,
|
||||
"--max_tree_depth": args.max_tree_depth, "--num_augmented": args.num_augmented},
|
||||
use_YML, args.out)
|
||||
print("Done")
|
||||
@@ -0,0 +1,103 @@
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/xphoto.hpp>
|
||||
#include "opencv2/xphoto/oilpainting.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
static void TrackSlider(int , void *);
|
||||
static void addSlider(String sliderName, String windowName, int minSlider, int maxSlider, int valDefault, int *valSlider, void(*f)(int, void *), void *r);
|
||||
vector<int> colorSpace = { COLOR_BGR2GRAY,COLOR_BGR2HSV,COLOR_BGR2YUV,COLOR_BGR2XYZ };
|
||||
|
||||
struct OilImage {
|
||||
String winName = "Oil painting";
|
||||
int size;
|
||||
int dynRatio;
|
||||
int colorSpace;
|
||||
Mat img;
|
||||
};
|
||||
|
||||
const String keys =
|
||||
"{Help h usage ? help | | Print this message }"
|
||||
"{v | 0 | video index }"
|
||||
"{a | 700 | API index }"
|
||||
"{s | 10 | neighbouring size }"
|
||||
"{d | 1 | dynamic ratio }"
|
||||
"{c | 0 | color space }"
|
||||
"{@arg1 | | file path}"
|
||||
;
|
||||
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
CommandLineParser parser(argc, argv, keys);
|
||||
|
||||
if (parser.has("help"))
|
||||
{
|
||||
parser.printMessage();
|
||||
return 0;
|
||||
}
|
||||
String filename = parser.get<String>(0);
|
||||
OilImage p;
|
||||
p.dynRatio = parser.get<int>("d");
|
||||
p.size = parser.get<int>("s");
|
||||
p.colorSpace = parser.get<int>("c");
|
||||
if (p.colorSpace < 0 || p.colorSpace >= static_cast<int>(colorSpace.size()))
|
||||
{
|
||||
std::cout << "Color space must be >= 0 and <"<< colorSpace.size()<<"\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!filename.empty())
|
||||
{
|
||||
p.img = imread(filename);
|
||||
if (p.img.empty())
|
||||
{
|
||||
std::cout << "Check file path!\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
Mat dst;
|
||||
xphoto::oilPainting(p.img, dst, p.size, p.dynRatio, colorSpace[p.colorSpace]);
|
||||
imshow("oil painting effect", dst);
|
||||
waitKey();
|
||||
return 0;
|
||||
}
|
||||
VideoCapture v(parser.get<int>("v")+ parser.get<int>("a"));
|
||||
v>> p.img;
|
||||
p.winName="Oil Painting";
|
||||
namedWindow(p.winName);
|
||||
addSlider("DynRatio", p.winName, 1,127,p.dynRatio,&p.dynRatio, TrackSlider, &p);
|
||||
addSlider("Size", p.winName, 1, 100, p.size, &p.size, TrackSlider, &p);
|
||||
addSlider("ColorSpace", p.winName, 0, static_cast<int>(colorSpace.size()-1), p.colorSpace, &p.colorSpace, TrackSlider, &p);
|
||||
while (waitKey(20) != 27)
|
||||
{
|
||||
v>>p.img;
|
||||
imshow("Original", p.img);
|
||||
TrackSlider(0, &p);
|
||||
waitKey(10);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void addSlider(String sliderName, String windowName, int minSlider, int maxSlider, int valDefault, int *valSlider, void(*f)(int, void *), void *r)
|
||||
{
|
||||
createTrackbar(sliderName, windowName, valSlider, 1, f, r);
|
||||
setTrackbarMin(sliderName, windowName, minSlider);
|
||||
setTrackbarMax(sliderName, windowName, maxSlider);
|
||||
setTrackbarPos(sliderName, windowName, valDefault);
|
||||
}
|
||||
|
||||
void TrackSlider(int , void *r)
|
||||
{
|
||||
OilImage *p = (OilImage *)r;
|
||||
Mat dst;
|
||||
p->img = p->img / p->dynRatio;
|
||||
p->img = p->img*p->dynRatio;
|
||||
xphoto::oilPainting(p->img, dst, p->size, p->dynRatio,colorSpace[p->colorSpace]);
|
||||
if (!dst.empty())
|
||||
{
|
||||
imshow(p->winName, dst);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user