ENH: consider colors with de < 5 as the same

1. Use cie de2000 as color distance
2.Consider colors with de < 5 as the same color  when calculating
flush

jira:NONE

Signed-off-by: xun.zhang <xun.zhang@bambulab.com>
Change-Id: I4b451910a21c9db3471c63c270f1f120e3c5d160
This commit is contained in:
xun.zhang 2024-09-27 19:07:52 +08:00 committed by Lane.Wei
parent 912c2e31f3
commit 72bc6f44cf
4 changed files with 278 additions and 42 deletions

View File

@ -1,3 +1,5 @@
colors
#000000 #C12E1F #00AE42 #545454 #D1D3D5 #5B6579 #F4EE2A #9D432C #5E43B7 #0A2989 #FF6A13 #8E9089
src dst flush src dst flush
#000000 #545454 236 #000000 #545454 236
#000000 #F4EE2A 386 #000000 #F4EE2A 386

View File

@ -49,7 +49,9 @@ int FlushVolCalculator::calc_flush_vol_rgb(unsigned char src_r, unsigned char sr
{ {
auto& pd = FlushVolPredictor::get_instance(); auto& pd = FlushVolPredictor::get_instance();
float ret_flush_volume = 0; float ret_flush_volume = 0;
bool success = pd.predict(src_r, src_g, src_b, dst_r, dst_g, dst_b, ret_flush_volume); FlushPredict::RGBColor src(src_r, src_g, src_b);
FlushPredict::RGBColor dst(dst_r, dst_g, dst_b);
bool success = pd.predict(src, dst, ret_flush_volume);
// if we could find the color pair from dataset, we need to recalculate // if we could find the color pair from dataset, we need to recalculate
if (!success) { if (!success) {
float src_r_f, src_g_f, src_b_f, dst_r_f, dst_g_f, dst_b_f; float src_r_f, src_g_f, src_b_f, dst_r_f, dst_g_f, dst_b_f;

View File

@ -2,61 +2,245 @@
#include "Utils.hpp" #include "Utils.hpp"
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <cmath>
#include <optional>
namespace FlushPredict
static bool rgb_hex_to_dec(const std::string& hexstr, unsigned char& r, unsigned char& g, unsigned char& b)
{ {
if (hexstr.empty() || hexstr.length() != 7 || hexstr[0] != '#') static double rad_to_deg(double rad) {
{ return 180.0 / M_PI * rad;
assert(false); }
r = 0, g = 0, b = 0;
return false; static double deg_to_rad(double deg) {
return deg * M_PI / 180.0;
}
LABColor RGB2LAB(const RGBColor& color) {
using XYZColor = std::tuple<double, double, double>;
auto gamma = [](double x) {
if (x > 0.04045)
return pow((x + 0.055) / 1.055, 2.4);
else
return x / 12.92;
};
auto RGB2XYZ = [gamma](const RGBColor& color)->XYZColor {
double R = gamma(static_cast<double>(color.r) / 255.0) * 100;
double G = gamma(static_cast<double>(color.g) / 255.0) * 100;
double B = gamma(static_cast<double>(color.b) / 255.0) * 100;
double x = 0.412453 * R + 0.357580 * G + 0.180423 * B;
double y = 0.212671 * R + 0.715160 * G + 0.072169 * B;
double z = 0.019334 * R + 0.119193 * G + 0.950227 * B;
return { x,y,z };
};
static const double XN = 95.0489;
static const double YN = 100;
static const double ZN = 108.8840;
auto f = [](double t) {
static const double threshold = 0.008856f;
if (t > threshold)
return pow(t, 1.0 / 3.0);
else
return 7.787 * t + 0.137931;
};
auto xyz_color = RGB2XYZ(color);
double x = std::get<0>(xyz_color);
double y = std::get<1>(xyz_color);
double z = std::get<2>(xyz_color);
double xn = f(x / XN);
double yn = f(y / YN);
double zn = f(z / ZN);
double L = 116.0 * yn - 16.0;
double A = 500.0 * (xn - yn);
double B = 200.0 * (yn - zn);
return LABColor(L, A, B);
}
float calc_color_distance(const LABColor& lab1, const LABColor& lab2)
{
static const double pow_25_to_7 = pow(25, 7);
const double C1 = sqrt(lab1.a * lab1.a + lab1.b * lab1.b);
const double C2 = sqrt(lab2.a * lab2.a + lab2.b * lab2.b);
const double CMean = (C1 + C2) / 2.0;
const double pow_CMean_to_7 = pow(CMean, 7);
const double G = 0.5 * (1 - sqrt(pow_CMean_to_7 / (pow_CMean_to_7 + pow_25_to_7)));
const double p_l1 = lab1.l;
const double p_l2 = lab2.l;
const double p_a1 = (1. + G) * lab1.a;
const double p_a2 = (1. + G) * lab2.a;
const double p_b1 = lab1.b;
const double p_b2 = lab2.b;
const double p_c1 = sqrt(p_a1 * p_a1 + p_b1 * p_b1);
const double p_c2 = sqrt(p_a2 * p_a2 + p_b2 * p_b2);
double p_h1;
if (p_a1 == 0 && p_b1 == 0)
p_h1 = 0;
else {
p_h1 = atan2(p_b1, p_a1);
if (p_h1 < 0)
p_h1 += M_PI * 2;
}
double p_h2;
if (p_a2 == 0 && p_b2 == 0)
p_h2 = 0;
else {
p_h2 = atan2(p_b2, p_a2);
if (p_h2 < 0)
p_h2 += M_PI * 2;
}
const double delta_L = p_l2 - p_l1;
const double delta_C = p_c2 - p_c1;
double delta_H;
const double p_c_multi = p_c1 * p_c2;
if (p_c_multi == 0)
delta_H = 0;
else {
delta_H = p_h2 - p_h1;
if (delta_H < -M_PI)
delta_H += 2 * M_PI;
else if (delta_H > M_PI)
delta_H -= 2 * M_PI;
delta_H = 2 * sqrt(p_c_multi) * sin(delta_H / 2.);
}
double p_L_mean = (p_l1 + p_l2) / 2.0;
double p_C_mean = (p_c1 + p_c2) / 2.0;
double p_H_mean, p_H_sum = p_h1 + p_h2;
if (p_c1 * p_c2 == 0) {
p_H_mean = p_H_sum;
}
else {
if (fabs(p_h1 - p_h2) <= M_PI)
p_H_mean = p_H_sum / 2;
else {
if (p_H_sum < 2 * M_PI)
p_H_mean = (p_H_sum + 2 * M_PI) / 2.0;
else
p_H_mean = (p_H_sum - 2 * M_PI) / 2.0;
}
}
const double T = 1 - 0.17 * cos(p_H_mean - deg_to_rad(30)) + 0.24 * cos(2 * p_H_mean) + 0.32 * cos(3 * p_H_mean + deg_to_rad(6)) - 0.2 * cos(4 * p_H_mean - deg_to_rad(63));
const double dtheta = deg_to_rad(30) * exp(-pow((p_H_mean - deg_to_rad(275)) / deg_to_rad(25), 2));
const double pow_p_cmean_to_7 = pow(p_C_mean, 7);
const double R_C = 2 * sqrt(pow_p_cmean_to_7 / (pow_p_cmean_to_7 + pow_25_to_7));
const double pow_p_lmean_to_2 = pow(p_L_mean - 50, 2);
const double S_L = 1 + (0.015 * pow_p_lmean_to_2) / sqrt(20 + pow_p_lmean_to_2);
const double S_C = 1 + 0.045 * p_C_mean;
const double S_H = 1 + 0.015 * p_C_mean * T;
const double R_T = -sin(2 * dtheta) * R_C;
const double K_L = 1.0, K_C = 1.0, K_H = 1.0;
double de = sqrt(
pow(delta_L / (K_L * S_L), 2) + pow(delta_C / (K_C * S_C), 2) + pow(delta_H / (K_H * S_H), 2) + (R_T * (delta_C / (K_C * S_C)) * (delta_H / (K_H * S_H)))
);
return de;
}
float calc_color_distance(const RGBColor& color1, const RGBColor& color2) {
LABColor lab1 = RGB2LAB(color1);
LABColor lab2 = RGB2LAB(color2);
return calc_color_distance(lab1, lab2);
}
bool is_similar_color(const RGBColor& from, const RGBColor& to, float distance_threshold)
{
float color_distance = calc_color_distance(from, to);
if (color_distance > distance_threshold)
return false;
return true;
} }
auto hexToByte = [](const std::string& hex)->int
{
unsigned int byte;
std::istringstream(hex) >> std::hex >> byte;
return byte;
};
r = hexToByte(hexstr.substr(1, 2));
g = hexToByte(hexstr.substr(3, 2));
b = hexToByte(hexstr.substr(5, 2));
return true;
} }
uint64_t FlushVolPredictor::generate_hash_key(const RGB& from, const RGB& to)
{
uint64_t key = 0;
key |= (static_cast<uint64_t>(from.r) << 40);
key |= (static_cast<uint64_t>(from.g) << 32);
key |= (static_cast<uint64_t>(from.b) << 24);
key |= (static_cast<uint64_t>(to.r) << 16);
key |= (static_cast<uint64_t>(to.g) << 8);
key |= static_cast<uint64_t>(to.b);
return key;
}
FlushVolPredictor::FlushVolPredictor(const std::string& data_file) FlushVolPredictor::FlushVolPredictor(const std::string& data_file)
{ {
auto rgb_hex_to_dec = [](const std::string& hexstr, FlushPredict::RGBColor& color)->bool
{
if (hexstr.empty() || hexstr.length() != 7 || hexstr[0] != '#')
{
assert(false);
color.r = 0, color.g = 0, color.b = 0;
return false;
}
auto hexToByte = [](const std::string& hex)->int
{
unsigned int byte;
std::istringstream(hex) >> std::hex >> byte;
return byte;
};
color.r = hexToByte(hexstr.substr(1, 2));
color.g = hexToByte(hexstr.substr(3, 2));
color.b = hexToByte(hexstr.substr(5, 2));
return true;
};
std::ifstream in(data_file); std::ifstream in(data_file);
if (!in.is_open()) { if (!in.is_open()) {
m_valid = false; m_valid = false;
return; return;
} }
std::string line; std::string line;
getline(in, line); // skip the first line std::getline(in, line); //skip color description line
std::getline(in, line);
// read and save color lists
{
std::istringstream in(line);
std::string color;
while (in >> color) {
RGB c;
if (!rgb_hex_to_dec(color, c)) {
m_valid = false;
return;
}
m_colors.emplace_back(c);
}
}
std::getline(in, line); // skip colume name line
while (std::getline(in, line)) { while (std::getline(in, line)) {
std::istringstream iss(line); std::istringstream iss(line);
std::string rgb_src, rgb_dst; std::string rgb_from, rgb_to;
float value; float value;
if (iss >> rgb_src >> rgb_dst >> value) { if (iss >> rgb_from >> rgb_to >> value) {
unsigned char r_src = 0, g_src = 0, b_src = 0; RGB from,to;
unsigned char r_dst = 0, g_dst = 0, b_dst = 0; // transfer hex str to rgb format
if (!rgb_hex_to_dec(rgb_src, r_src, g_src, b_src)) { if (!rgb_hex_to_dec(rgb_from, from)) {
m_valid = false; m_valid = false;
return; return;
} }
if (!rgb_hex_to_dec(rgb_dst, r_dst, g_dst, b_dst)) { if (!rgb_hex_to_dec(rgb_to, to)) {
m_valid = false; m_valid = false;
return; return;
} }
uint64_t key = 0; // generate hash key for two rgb color
key |= (static_cast<uint64_t>(r_src) << 40); uint64_t key = generate_hash_key(from,to);
key |= (static_cast<uint64_t>(g_src) << 32);
key |= (static_cast<uint64_t>(b_src) << 24);
key |= (static_cast<uint64_t>(r_dst) << 16);
key |= (static_cast<uint64_t>(g_dst) << 8);
key |= static_cast<uint64_t>(b_dst);
m_flush_map.emplace(key, value); m_flush_map.emplace(key, value);
} }
else { else {
@ -67,19 +251,31 @@ FlushVolPredictor::FlushVolPredictor(const std::string& data_file)
m_valid = true; m_valid = true;
} }
bool FlushVolPredictor::predict(const unsigned char src_r, const unsigned char src_g, const unsigned char src_b, const unsigned char dst_r, const unsigned char dst_g, const unsigned char dst_b, float& flush) bool FlushVolPredictor::predict(const RGB& from, const RGB& to, float& flush)
{ {
if (!m_valid) if (!m_valid)
return false; return false;
uint64_t key = 0; // find similar colors in color list
key |= (static_cast<uint64_t>(src_r) << 40); std::optional<RGB> similar_from, similar_to;
key |= (static_cast<uint64_t>(src_g) << 32); for (auto& color : m_colors) {
key |= (static_cast<uint64_t>(src_b) << 24); if (FlushPredict::is_similar_color(color, from)) {
key |= (static_cast<uint64_t>(dst_r) << 16); similar_from = color;
key |= (static_cast<uint64_t>(dst_g) << 8); break;
key |= static_cast<uint64_t>(dst_b); }
}
for (auto& color : m_colors) {
if (FlushPredict::is_similar_color(color, to)) {
similar_to = color;
break;
}
}
// `from` and `to` should have similar colors in list
if (!similar_from || !similar_to)
return false;
uint64_t key = generate_hash_key(*similar_from,*similar_to);
auto iter = m_flush_map.find(key); auto iter = m_flush_map.find(key);
if (iter == m_flush_map.end()) if (iter == m_flush_map.end())
return false; return false;

View File

@ -3,20 +3,56 @@
#include<unordered_map> #include<unordered_map>
namespace FlushPredict
{
struct RGBColor
{
unsigned char r{ 0 };
unsigned char g{ 0 };
unsigned char b{ 0 };
RGBColor(unsigned char r_,unsigned char g_,unsigned char b_) :r(r_),g(g_),b(b_){}
RGBColor() = default;
};
struct LABColor
{
double l{ 0 };
double a{ 0 };
double b{ 0 };
LABColor() = default;
LABColor(double l_,double a_,double b_):l(l_),a(a_),b(b_){}
};
// transfer colour in RGB space to LAB space
LABColor RGB2LAB(const RGBColor& color);
// calculate DeltaE2000
float calc_color_distance(const LABColor& lab1, const LABColor& lab2);
float calc_color_distance(const RGBColor& rgb1, const RGBColor& rgb2);
// check if DeltaE is within the threshold. We consider colors within the threshold to be the same
bool is_similar_color(const RGBColor& from, const RGBColor& to, float distance_threshold = 5.0);
}
// Singleton pattern
class FlushVolPredictor class FlushVolPredictor
{ {
using RGB = FlushPredict::RGBColor;
public: public:
bool predict(const unsigned char src_r, const unsigned char src_g, const unsigned char src_b, bool predict(const RGB& from,const RGB& to , float& flush);
const unsigned char dst_r, const unsigned char dst_g, const unsigned char dst_b, float& flush);
static FlushVolPredictor& get_instance(); static FlushVolPredictor& get_instance();
private: private:
FlushVolPredictor(const std::string& data_file); FlushVolPredictor(const std::string& data_file);
FlushVolPredictor(const FlushVolPredictor&) = delete; FlushVolPredictor(const FlushVolPredictor&) = delete;
FlushVolPredictor& operator=(const FlushVolPredictor&) = delete; FlushVolPredictor& operator=(const FlushVolPredictor&) = delete;
~FlushVolPredictor() = default; ~FlushVolPredictor() = default;
uint64_t generate_hash_key(const RGB& from, const RGB& to);
private: private:
std::unordered_map<uint64_t, float> m_flush_map; std::unordered_map<uint64_t, float> m_flush_map;
std::vector<RGB> m_colors;
bool m_valid; bool m_valid;
}; };
#endif #endif