import cv2
import sympy
import numpy as np
import easygui as g
import time
import argparse
from tkinter import Tk, simpledialog
from tkinter import filedialog, messagebox
import tkinter as tk
import numpy
from numpy import *
from scipy.optimize import minimize, root_scalar
import os
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import pandas as pd
from scipy.interpolate import interp1d
from skimage import color
from scipy.optimize import least_squares
from scipy.optimize import minimize
from colormath.color_conversions import convert_color
from colormath.color_objects import sRGBColor, LabColor
from scipy.spatial import distance
from pyswarm import pso
from pyswarms.single import GlobalBestPSO
import pyswarms as ps
from colormath.color_diff import delta_e_cie1976
from scipy.spatial.distance import euclidean
from scipy.interpolate import RegularGridInterpolator
# # 全局变量
start_point = (0, 0)
end_point = (0, 0)
selecting = False
def load_array_from_file(file_path):
try:
array = np.loadtxt(file_path)
return array
except FileNotFoundError:
messagebox.showerror("错误", f"文件 '{file_path}' 未找到。")
except Exception as e:
messagebox.showerror("错误", f"读取文件时发生错误 - {e}")
def load_file():
global Lab_ideal_array
file_path = filedialog.askopenfilename(title="选择文件", filetypes=[("Text files", "*.txt")])
if file_path:
Lab_ideal_array = load_array_from_file(file_path)
if Lab_ideal_array is not None:
messagebox.showinfo("成功", f"文件加载成功,数组内容如下:\n{Lab_ideal_array}")
root.quit()
print('lab=', Lab_ideal_array)
return Lab_ideal_array
def open_file():
file_path = filedialog.askopenfilename()
if file_path == "":
os.path.get() #当打开文件路径选择框后点击"取消" 输入框会清空路径,所以使用get()方法再获取一次路径
else:
path_ = file_path.replace("/", "\\") # 实际在代码中执行的路径为“\“ 所以替换一下
os.path.set(path_)
print(os.path.get())
def display_image(img):
img_bgr = cv2.resize(img, (960, 540))
cv2.imshow('RGB Image', img_bgr)
cv2.moveWindow('RGB Image', x=100, y=100)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 色块权重表配置
def save_numbers(entry_grid, numbers, root):
numbers.clear()
for i in range(4):
for j in range(6):
value = entry_grid[i][j].get()
try:
# 尝试将输入值转换为浮点数
float_value = float(value)
numbers.append(float_value)
except ValueError:
# 如果转换失败,显示错误消息并返回
messagebox.showerror("输入错误", f"第 {i+1} 行, 第 {j+1} 列 的输入值 '{value}' 不是有效的数字。")
return
# 关闭窗口
root.quit()
def create_input_grid(default_value=1):
numbers = []
# 创建主窗口
root = tk.Tk()
root.title("24色块权重表")
# 创建色块权重的输入框
entry_grid = []
for i in range(4):
row = []
for j in range(6):
entry = tk.Entry(root, width=5)
entry.insert(0, str(default_value))
entry.grid(row=i, column=j, padx=5, pady=5)
row.append(entry)
entry_grid.append(row)
# 创建保存按钮
save_button = tk.Button(
root,
text="保存",
command=lambda: save_numbers(entry_grid, numbers, root) # 传递必要的参数
)
save_button.grid(row=4, column=0, columnspan=6, pady=10)
# 运行主循环
root.mainloop()
return np.array(numbers)
def degamma(img, block_size=512):
print('max_img', np.max(img))
x = np.arange(0, 129)
y = np.array(
[0,77,110,136,158,178,197,214,231,246,261,276,290,303,316,329,342,354,366,378,389,400,411,422,433,443,453,463,473,483,493,502,511,521,530,539,547,556,564,573,581,589,597,605,613,620,628,635,643,650,657,664,671,678,685,691,698,704,711,717,723,730,736,742,748,753,759,765,770,776,782,787,792,798,803,808,813,818,823,828,833,838,843,847,852,857,861,866,870,875,879,883,888,892,896,900,905,909,913,917,921,925,929,933,937,941,944,948,952,956,959,963,967,970,974,978,981,985,988,992,995,999,1002,1006,1009,1013,1016,1020,1023]) # 填写实际数据
# 建立插值函数
f = interp1d(y, x, kind='linear', fill_value='extrapolate')
height, width, channels = img.shape
linear_img = np.empty_like(img, dtype=np.float32)
# 数据量较大,分块处理
for ch in range(channels):
for y_start in range(0, height, block_size):
for x_start in range(0, width, block_size):
y_end = min(y_start + block_size, height)
x_end = min(x_start + block_size, width)
block = img[y_start:y_end, x_start:x_end, ch]
x_block = (block.astype(np.float32) / 256.0) * 1024.0
linear_block = f(x_block)
linear_block = np.clip(linear_block, 0, 128)
linear_uint8 = (linear_block / 128.0 * 256.0).astype(np.uint8)
linear_img[y_start:y_end, x_start:x_end, ch] = linear_uint8
del block, x_block, linear_block, linear_uint8
return linear_img
def gamma_brt(img, block_size = 512):
x = np.arange(0, 129)
y = np.array(
[0,21,35,48,61,72,83,94,104,114,125,134,144,154,164,173,183,192,202,211,220,230,239,248,258,267,276,285,294,304,313,322,331,341,350,359,368,377,386,395,405,414,423,432,441,450,459,468,476,485,494,503,512,520,529,538,546,555,563,572,580,588,597,605,613,621,629,637,645,653,661,669,677,684,692,700,707,714,722,729,736,743,751,758,765,772,778,785,792,799,805,812,818,825,831,837,844,850,856,862,868,874,880,886,892,898,904,909,915,921,926,932,938,943,949,954,959,965,970,976,981,986,992,997,1002,1007,1013,1018,1023]) # 填写实际数据
# 建立插值函数
f = interp1d(x, y, kind='linear', fill_value='extrapolate')
height, width, channels = img.shape
srgb_img = np.empty_like(img, dtype=np.float32)
#数据量较大,分块处理
for ch in range(channels):
for y_start in range(0, height, block_size):
for x_start in range(0, width, block_size):
y_end = min(y_start + block_size, height)
x_end = min(x_start + block_size, width)
block = img[y_start:y_end, x_start:x_end, ch]
x_block = (block.astype(np.float32) / 256.0) * 128.0
srgb_block = f(x_block)
srgb_block = np.clip(srgb_block, 0, 1024)
srgb_uint8 = (srgb_block / 1024.0 * 256.0).astype(np.uint8)
srgb_img[y_start:y_end, x_start:x_end, ch] = srgb_uint8
del block, x_block, srgb_block, srgb_uint8
print('gamma_brt', np.max(srgb_img), np.min(srgb_img))
return srgb_img
def gamma(img, block_size = 512):
x = np.arange(0, 129)
y = np.array(
[0,55,102,135,165,189,212,232,251,267,283,299,314,327,341,354,367,378,390,401,412,422,433,443,453,462,471,480,488,497,506,514,523,530,538,546,554,561,569,576,584,591,598,605,612,619,626,633,640,646,652,658,665,671,677,683,689,695,701,707,713,718,724,730,736,741,746,751,757,762,767,772,777,782,787,793,798,803,808,812,817,822,827,832,837,842,846,851,856,860,865,870,874,879,883,888,893,897,901,906,910,915,919,923,928,932,936,940,944,949,953,957,961,965,969,973,977,981,985,989,993,996,1000,1004,1008,1011,1015,1019,1023]) # 填写实际数据
# 建立插值函数
f = interp1d(x, y, kind='linear', fill_value='extrapolate')
height, width, channels = img.shape
srgb_img = np.empty_like(img, dtype=np.float32)
#数据量较大,分块处理
for ch in range(channels):
for y_start in range(0, height, block_size):
for x_start in range(0, width, block_size):
y_end = min(y_start + block_size, height)
x_end = min(x_start + block_size, width)
block = img[y_start:y_end, x_start:x_end, ch]
x_block = (block.astype(np.float32) / 256.0) * 128.0
srgb_block = f(x_block)
srgb_block = np.clip(srgb_block, 0, 1024)
srgb_uint8 = (srgb_block / 1024.0 * 256.0).astype(np.uint8)
srgb_img[y_start:y_end, x_start:x_end, ch] = srgb_uint8
del block, x_block, srgb_block, srgb_uint8
return srgb_img
def input_Lab_ideal():
# 初始化全局变量
global root
# 创建主窗口
root = tk.Tk()
root.title("选择文件读取数组")
# 创建按钮
button_load = tk.Button(root, text="选择Lab标准", command=load_file)
# 布局按钮
button_load.pack(pady=10)
# 运行主循环
root.mainloop()
root.destroy()
return Lab_ideal_array
# def mouse_callback(event, x, y, flags, param):
global rectangles
if event == cv2.EVENT_LBUTTONDOWN:
# 记录起点
rectangles.append([x, y])
elif event == cv2.EVENT_LBUTTONUP:
# 记录终点
rectangles[-1].extend([x, y])
# 在图像上画出矩形
cv2.rectangle(img_display, (rectangles[-1][0], rectangles[-1][1]),
(rectangles[-1][2], rectangles[-1][3]), (0,255,0), 2)
cv2.imshow('Image', img_display)
def calculate_sub_rectangle_dimensions(W, H):
# 计算新的小矩形宽度和高度(面积减半)
sub_width = (W / 7.75) / np.sqrt(2)
sub_height = (H / 5.25) / np.sqrt(2)
spacing = (W - 6 * sub_width) / 7
return sub_width, sub_height, spacing
# 鼠标回调函数
def mouse_callback(event, x, y, flags, param):
global start_point, end_point, selecting
if event == cv2.EVENT_LBUTTONDOWN:
start_point = (x, y)
end_point = (x, y)
selecting = True
elif event == cv2.EVENT_MOUSEMOVE:
if selecting:
end_point = (x, y)
elif event == cv2.EVENT_LBUTTONUP:
end_point = (x, y)
selecting = False
def draw_24color_RGB(img_bgr):
# 保存原始图像尺寸
original_height, original_width = img_bgr.shape[:2]
# 将图像调整为960x540
resized_img = cv2.resize(img_bgr, (960, 540), interpolation=cv2.INTER_AREA)
clone_resized = resized_img.copy()
cv2.namedWindow('image')
cv2.setMouseCallback('image', mouse_callback)
avg_rgb_values = []
while True:
img_display = clone_resized.copy()
if selecting:
# 绘制大矩形
cv2.rectangle(img_display, start_point, end_point, (0, 255, 0), 2)
# 计算小矩形的尺寸和位置,并绘制小矩形
x0, y0 = start_point
x1, y1 = end_point
width = abs(x1 - x0)
height = abs(y1 - y0)
x_min = min(x0, x1)
y_min = min(y0, y1)
sub_width, sub_height, spacing = calculate_sub_rectangle_dimensions(width, height)
# 绘制小矩形
for i in range(4):
for j in range(6):
x = x_min + spacing + j * (sub_width + spacing)
y = y_min + spacing + i * (sub_height + spacing)
sub_x0 = int(x)
sub_y0 = int(y)
sub_x1 = int(x + sub_width)
sub_y1 = int(y + sub_height)
cv2.rectangle(img_display, (sub_x0, sub_y0), (sub_x1, sub_y1), (255, 0, 0), 1)
cv2.imshow('image', img_display)
key = cv2.waitKey(1) & 0xFF
if key == 13: # Enter键
# 确认选择,计算小矩形的坐标和平均RGB值
x0, y0 = start_point
x1, y1 = end_point
width = abs(x1 - x0)
height = abs(y1 - y0)
x_min = min(x0, x1)
y_min = min(y0, y1)
sub_width, sub_height, spacing = calculate_sub_rectangle_dimensions(width, height)
# 计算并输出每个小矩形的平均RGB值
for i in range(4):
for j in range(6):
x = x_min + spacing + j * (sub_width + spacing)
y = y_min + spacing + i * (sub_height + spacing)
sub_x0 = int(x)
sub_y0 = int(y)
sub_x1 = int(x + sub_width)
sub_y1 = int(y + sub_height)
# 将小矩形的坐标映射回原始图像尺寸
original_sub_x0 = int(sub_x0 * original_width / 960)
original_sub_y0 = int(sub_y0 * original_height / 540)
original_sub_x1 = int(sub_x1 * original_width / 960)
original_sub_y1 = int(sub_y1 * original_height / 540)
# 提取小矩形区域
roi = img_bgr[original_sub_y0:original_sub_y1, original_sub_x0:original_sub_x1]
# 计算平均RGB值
avg_b = np.mean(roi[:, :, 0])
avg_g = np.mean(roi[:, :, 1])
avg_r = np.mean(roi[:, :, 2])
avg_rgb_values.append((avg_r, avg_g, avg_b))
# 打印每个色色块的平均RGB
# print(f"Index ({i},{j}) Average RGB: ({avg_r}, {avg_g}, {avg_b})")
# 绘制小矩形的边框在调整后的图像上
cv2.rectangle(clone_resized, (sub_x0, sub_y0), (sub_x1, sub_y1), (255, 0, 0), 1)
# 绘制大矩形在调整后的图像上
cv2.rectangle(clone_resized, start_point, end_point, (0, 255, 0), 2)
break
elif key == 27: # Esc键
break
del img_display
# 显示已经完成框选的图片
cv2.imshow('Result', clone_resized)
cv2.waitKey(0)
cv2.destroyAllWindows()
return avg_rgb_values
def rgb2lab(rgb):
# 将RGB值从0-255归一化到0-1
rgb_normalized = rgb / 255.0
# 创建sRGBColor对象
rgb_color = sRGBColor(rgb_normalized[0], rgb_normalized[1], rgb_normalized[2])
# 转换到Lab颜色空间
lab = convert_color(rgb_color, LabColor)
# 确保Lab值在范围内
lab_l = np.clip(lab.lab_l, 0, 100)
lab_a = np.clip(lab.lab_a, -128, 127)
lab_b = np.clip(lab.lab_b, -128, 127)
# 返回ndarray
return np.array([lab_l, lab_a, lab_b])
def deltaE(lab1, lab2):
# 计算色差
return distance.euclidean(lab1, lab2)
def normalize_ccm(ccm):
# 将CCM每行和归一化为1
for row in range(3):
row_sum = np.sum(ccm[row, :])
if row_sum != 0:
ccm[row, :] /= row_sum
return ccm
def objective_function(ccm, rgb, std_lab, weights):
# ccm是群体的位置, shape=(n_particles, 9)
n_particles = ccm.shape[0]
costs = []
for i in range(n_particles):
# 取出第i个粒子的ccm
ccm_i = ccm[i, :].reshape(3, 3)
# 约束条件,将CCM每行和归一化为1
ccm_i = normalize_ccm(ccm_i)
# 计算校正后的rgb
corrected_rgb = rgb[:18] @ ccm_i.T
# 将corrected_rgb的值截断到0-255之间
corrected_rgb = np.clip(corrected_rgb, 0, 255)
# 转换到Lab
lab = np.array([rgb2lab(corrected_rgb[j]) for j in range(18)])
# 计算前18个色块的deltaE
# deltaEs = [deltaE(lab[j], std_lab[j]) for j in range(18)]
deltaEs = [deltaE(lab[j], std_lab[j]) * weights[j] for j in range(18)]
# print('deltaE = ', deltaEs)
# # 计算平均deltaE
# mean_deltaE = np.mean(deltaEs)
# costs.append(mean_deltaE)
# 取最大 deltaE 作为 误差cost
max_deltaE = np.max(deltaEs)
costs.append(max_deltaE)
return np.array(costs)
def generate_init_pos(n_particles):
# 生成初始位置,范围为[-1, 1]
# init_pos = np.random.uniform(-2, 2, size=(n_particles, 9))
init_color = np.array([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0])
init_pos = np.tile(init_color, (n_particles, 1))
return init_pos
def find_optimal_CCM(rgb, std_lab, weights, ccm_limit):
# 确保输入在范围内
rgb = np.clip(rgb, 0, 255)
std_lab = np.clip(std_lab, [0, -128, -128], [100, 127, 127])
# 定义PSO的边界
lb = np.full(9, -np.inf)
ub = np.full(9, np.inf)
# 生成初始位置
init_pos = generate_init_pos(n_particles=30)
# 设置PSO的选项
options = {'c1': 1.2, 'c2': 1.2, 'w': 0.6}
# 初始化PSO
optimizer = ps.single.GlobalBestPSO(
n_particles=30,
dimensions=9,
options=options,
bounds=(lb, ub),
init_pos=init_pos
)
# 运行优化
cost, pos = optimizer.optimize(
objective_function,
iters=300,
rgb=rgb,
std_lab=std_lab,
weights=weights
)
# reshape pos成3x3矩阵
ccm = pos.reshape(3, 3)
# 将CCM每行和归一化为1
ccm = normalize_ccm(ccm)
# 添加CCM矩阵对角线约束
diagonal_elements = np.diag(ccm)
if np.any(diagonal_elements > ccm_limit):
raise ValueError("One or more diagonal elements of the CCM matrix exceed 3. Process terminated.")
# 使用最优 CCM 计算每个色块的色差
corrected_rgb = rgb[:18] @ ccm.T
corrected_rgb = np.clip(corrected_rgb, 0, 255).astype(uint8)
lab = np.array([rgb2lab(corrected_rgb[j]) for j in range(18)])
deltaEs = [deltaE(lab[j], std_lab[j]) for j in range(18)]
# 计算打印每个色块的色差
print("DeltaE for each color patch with optimal CCM:")
for j, deltaE_value in enumerate(deltaEs):
print(f"Patch {j + 1}: {deltaE_value:.4f}")
ccm = np.round(ccm * 255).astype(int)
return ccm
def objective(params, sectors, avg_yuv_list, yuv_ideal, weights):
# def objective(params, sectors, avg_yuv_list, yuv_ideal):
k = params[:24]
theta_deg = params[24:48]
theta_rad = np.deg2rad(theta_deg)
error = 0.0
for i in range(18): # 只优化前18个色块
s = sectors[0, i]
# 构建校正矩阵的转置
cos_theta = np.cos(theta_rad[s])
sin_theta = np.sin(theta_rad[s])
correction_matrix_T = np.array([
[1, 0, 0],
[0, k[s] * cos_theta, k[s] * sin_theta],
[0, -k[s] * sin_theta, k[s] * cos_theta]
])
# 应用校正矩阵
yuv_corrected = np.dot(avg_yuv_list[i], correction_matrix_T)
# 计算误差
saturation_error = (yuv_corrected[1:] - yuv_ideal[i][1:]) ** 2
error += np.sum((yuv_corrected - yuv_ideal[i]) ** 2) + 2 * np.sum(saturation_error)
# error += weights[i] * np.sum((yuv_corrected - yuv_ideal[i]) ** 2)
# # error += np.sum((yuv_corrected - yuv_ideal[i]) ** 2)
return error
def calculate_correction_parameters(avg_rgb_list, Lab_ideal, weights):
# def calculate_correction_parameters(avg_rgb_list, Lab_ideal):
# if weights is None:
# weights = np.ones(18) # 只优化前18个色块
# 确保输入是24x3的数组,输入均为float32
Lab_ideal = np.array(Lab_ideal, dtype=np.float32).reshape(24, 3)
# 将avg_rgb_list从RGB转到HSV
avg_rgb_uint8 = np.clip(avg_rgb_list, 0, 255).astype(np.uint8)
# 将 RGB 转换为 BGR(OpenCV 默认使用 BGR 格式)
avg_rgb_uint8 = avg_rgb_uint8.reshape((1, 24, 3))
avg_bgr_uint8 = cv2.cvtColor(avg_rgb_uint8, cv2.COLOR_RGB2BGR)
# 将 BGR 转换为 HSV
avg_hsv = cv2.cvtColor(avg_bgr_uint8, cv2.COLOR_BGR2HSV)
# 将 H 的范围从 [0, 180] 转换到 [0, 360],转换为 float32 以便进行乘法操作
avg_hsv = avg_hsv.astype(np.float32)
avg_hsv[:, :, 0] *= 2
print('avg_yuv_list_yuv2', avg_hsv[:, :, 0])
# 将H分成24个区间,每个15度
sectors = (avg_hsv[:, :, 0] / 15).astype(int) % 24
print("sectors:", sectors)
# 将avg_rgb_list转到YUV
avg_yuv_list = cv2.cvtColor(avg_rgb_uint8, cv2.COLOR_RGB2YUV).reshape((24, 3))
# print('avg_yuv_list_before', avg_yuv_list)
# UV 减去 128
adjust_list_yuv = avg_yuv_list.copy()
adjust_list_yuv = adjust_list_yuv.astype(np.int16)
adjust_list_yuv[:, 1:] -= 128
# print('avg_yuv_list_after', adjust_list_yuv)
Lab_ideal_copy = Lab_ideal.copy()
Lab_ideal_reshape = Lab_ideal_copy.reshape(1, 24, 3)
# 将 Lab 图像的值调整到 OpenCV 的期望范围
Lab_ideal_reshape[:, :, 0] = Lab_ideal_reshape[:, :, 0] * 255 / 100
Lab_ideal_reshape[:, :, 1:] = Lab_ideal_reshape[:, :, 1:] + 128
# 将浮点型转换为 uint8
Lab_ideal_u8 = Lab_ideal_reshape.astype(np.uint8)
# print('Lab_ideal', Lab_ideal_u8)
# 使用 OpenCV 将 Lab 转换为 BGR
bgr_ideal = cv2.cvtColor(Lab_ideal_u8, cv2.COLOR_LAB2BGR)
# 重塑回24个颜色块
# bgr_ideal = bgr_ideal.reshape(24, 3)
# 转换为YUV
yuv_ideal = cv2.cvtColor(bgr_ideal, cv2.COLOR_BGR2YUV)
yuv_ideal = np.clip(yuv_ideal, 0, 255).reshape(24, 3)
adjust_ideal_yuv = yuv_ideal.copy()
adjust_ideal_yuv = adjust_ideal_yuv.astype(np.int16)
adjust_ideal_yuv[:, 1:] -= 128
# print('ideal_yuv_after', adjust_ideal_yuv)
# 初始猜测值,k=1, theta=0
initial_guess = np.concatenate((np.ones(24), np.zeros(24)))
# initial_guess[13] = 0.7 # 第13个蓝色的k值
# initial_guess[37] = -10 # 第13个蓝色的theta值
# 定义约束条件
bounds = [(0, 2) for _ in range(24)] + [(-60, 60) for _ in range(24)]
# 提高计算精度
adjust_list_yuv_float = adjust_list_yuv.astype(np.float32)
print('adjust_list_yuv_float = ', adjust_list_yuv_float)
print('adjust_list_yuv_float[0] = ', adjust_list_yuv_float[0])
adjust_ideal_yuv_float = adjust_ideal_yuv.astype(np.float32)
# 优化,传递sectors, avg_yuv_list, yuv_ideal作为额外参数
result = minimize(
objective,
initial_guess,
args=(sectors, adjust_list_yuv_float, adjust_ideal_yuv_float, weights),
# args=(sectors, adjust_list_yuv_float, adjust_ideal_yuv_float),
bounds=bounds,
method='L-BFGS-B',
options={'maxiter': 10000, 'ftol': 1e-6}
)
# result = minimize(
# objective,
# initial_guess,
# args=(sectors, adjust_list_yuv_float, adjust_ideal_yuv_float, weights),
# bounds=bounds,
# method='trust-constr',
# options={
# 'maxiter': 10000,
# 'gtol': 1e-6, # 梯度容差
# 'xtol': 1e-8, # 参数变化容差
# }
# )
# 提取优化后的k和theta
k_opt = result.x[:24]
theta_opt = result.x[24:48]
# k_opt[13] -= 0.2
# k_opt[14] -= 0.1
# 计算H_map和S_map
H_map = np.clip((theta_opt / 60.0 * 128 + 128), 0, 255).astype(np.int16)
S_map = np.clip((128 * k_opt - 128), -128, 127).astype(np.int16)
return np.vstack((k_opt, theta_opt)), np.vstack((H_map, S_map))
def apply_hsv_correction(img_corrected, k_theta_matrix, block_size=256):
height, width, channels = img_corrected.shape
img_final = np.empty((height, width, channels), dtype=np.uint8)
# 将k_theta_matrix转置为24x2矩阵,并转换为float32类型,shape: (24, 2)
k_theta_matrix = k_theta_matrix.astype(np.float32).T
for y_start in range(0, height, block_size):
y_end = min(y_start + block_size, height)
for x_start in range(0, width, block_size):
x_end = min(x_start + block_size, width)
# 提取块并转换为HSV
# block_rgb = img_corrected[y_start:y_end, x_start:x_end, :].astype(np.float32)
block_rgb = img_corrected[y_start:y_end, x_start:x_end, :].astype(np.uint8)
# print('block_rgb', block_rgb)
block_hsv = cv2.cvtColor(block_rgb, cv2.COLOR_RGB2HSV)
# print('block_hsv', block_hsv)
# H = block_hsv[:, :, 0]
H = np.clip(block_hsv[:, :, 0].astype(np.int32) * 2, 0, 360)
# print('H = ', H)
# 计算色块索引和插值系数
sector_idx = (H / 15).astype(np.int32) % 24
left_idx = (sector_idx - 1) % 24
right_idx = sector_idx
alpha = (H % 15) / 15
# 线性插值 k 和 theta
k = k_theta_matrix[left_idx, 0] * (1 - alpha) + k_theta_matrix[right_idx, 0] * alpha
theta = k_theta_matrix[left_idx, 1] * (1 - alpha) + k_theta_matrix[right_idx, 1] * alpha
# 计算cos和sin值
cos_theta = np.cos(np.deg2rad(theta))
sin_theta = np.sin(np.deg2rad(theta))
# 构建校正矩阵
# 校正矩阵的形状为 (height, width, 3, 3)
correction_matrix = np.zeros((block_rgb.shape[0], block_rgb.shape[1], 3, 3), dtype=np.float32)
correction_matrix[..., 0, 0] = 1
correction_matrix[..., 1, 1] = k * cos_theta
correction_matrix[..., 1, 2] = -k * sin_theta
correction_matrix[..., 2, 1] = k * sin_theta
correction_matrix[..., 2, 2] = k * cos_theta
# 将RGB转换为YUV并应用校正
block_yuv = cv2.cvtColor(block_rgb, cv2.COLOR_RGB2YUV)
adjust_block_yuv = block_yuv.astype(np.float32)
adjust_block_yuv[..., 1] -= 128
adjust_block_yuv[..., 2] -= 128
block_yuv_corrected = np.matmul(correction_matrix, adjust_block_yuv[..., np.newaxis]).squeeze(axis=-1)
yuv_corrected = block_yuv_corrected.copy()
yuv_corrected[..., 1] += 128
yuv_corrected[..., 2] += 128
yuv_corrected = np.clip(yuv_corrected, 0, 255).astype(uint8)
rgb_corrected = cv2.cvtColor(yuv_corrected, cv2.COLOR_YUV2RGB)
img_final[y_start:y_end, x_start:x_end, :] = np.clip(rgb_corrected, 0, 255).astype(np.uint8)
# # 将校正后的YUV转换回RGB并存储结果
# block_rgb_corrected = cv2.cvtColor(block_yuv_corrected, cv2.COLOR_YUV2RGB)
# img_final[y_start:y_end, x_start:x_end, :] = np.clip(block_rgb_corrected, 0, 255).astype(np.uint8)
return img_final
def read_raw(image_path, bit, w, h):
if bit == 16:
img = np.fromfile(image_path, dtype='uint16')
img = img.reshape(h, w)
elif bit == 12:
with open(image_path, "rb") as rawimg:
data = np.fromfile(rawimg, np.uint8, w * h * 3 // 2)
data = data.astype(np.uint16)
result = np.zeros(data.size * 2 // 3, np.uint16)
result[0::2] = ((data[1::3] & 15) << 8) | data[0::3]
result[1::2] = (data[1::3] >> 4) | (data[2::3] << 4)
img = result.reshape(h, w)
else:
print('Error! raw_bit is Wrong, raw_bit should be 12 or 16')
return None
return img
def correct_ob(img, bit, bl_level):
img = img.astype(np.float32)
img -= bl_level
img = np.clip(img, 0, 2**bit - 1 - bl_level)
# 归一化
img /= (2 ** bit - 1 - bl_level)
img *= (2 ** bit - 1)
img = np.clip(img, 0, 2 ** bit - 1).astype(np.uint16)
return img
def get_Rgain_Bgain(img_bgr):
# 保存原始图像尺寸
original_height, original_width = img_bgr.shape[:2]
# 将图像调整为960x540
resized_img = cv2.resize(img_bgr, (960, 540), interpolation=cv2.INTER_AREA)
clone_resized = resized_img.copy()
cv2.namedWindow('image')
cv2.setMouseCallback('image', mouse_callback)
# 初始化R、G、B的均值累加器
total_r = 0
total_g = 0
total_b = 0
while True:
img_display = clone_resized.copy()
if selecting:
# 绘制大矩形
cv2.rectangle(img_display, start_point, end_point, (0, 255, 0), 2)
# 计算大矩形的尺寸
x0, y0 = start_point
x1, y1 = end_point
width = abs(x1 - x0)
height = abs(y1 - y0)
x_min = min(x0, x1)
y_min = min(y0, y1)
# 计算小矩形的尺寸和间距
spacing_width = (width / 12) # 小矩形宽度的1/6
spacing_height = (height / 6) # 小矩形高度的1/6
sub_width = (width - 7 * spacing_width) / 6 # 6个小矩形 + 7个间距
sub_height = height - 2 * spacing_height # 上下各留一个间距
# 绘制6x1的框
for j in range(6):
x = x_min + spacing_width + j * (sub_width + spacing_width)
y = y_min + spacing_height
sub_x0 = int(x)
sub_y0 = int(y)
sub_x1 = int(x + sub_width)
sub_y1 = int(y + sub_height)
cv2.rectangle(img_display, (sub_x0, sub_y0), (sub_x1, sub_y1), (255, 0, 0), 1)
cv2.imshow('image', img_display)
key = cv2.waitKey(1) & 0xFF
if key == 13: # Enter键
# 确认选择,计算小矩形的坐标和R、G、B的均值
x0, y0 = start_point
x1, y1 = end_point
width = abs(x1 - x0)
height = abs(y1 - y0)
x_min = min(x0, x1)
y_min = min(y0, y1)
# 计算每个框的尺寸和间距
spacing_width = (width / 12)
spacing_height = (height / 6)
sub_width = (width - 7 * spacing_width) / 6
sub_height = height - 2 * spacing_height
# 计算并累加每个小矩形的R、G、B的均值
for j in range(6):
x = x_min + spacing_width + j * (sub_width + spacing_width)
y = y_min + spacing_height
sub_x0 = int(x)
sub_y0 = int(y)
sub_x1 = int(x + sub_width)
sub_y1 = int(y + sub_height)
# 将小矩形的坐标映射回原始图像尺寸
original_sub_x0 = int(sub_x0 * original_width / 960)
original_sub_y0 = int(sub_y0 * original_height / 540)
original_sub_x1 = int(sub_x1 * original_width / 960)
original_sub_y1 = int(sub_y1 * original_height / 540)
# 提取小矩形区域
roi = img_bgr[original_sub_y0:original_sub_y1, original_sub_x0:original_sub_x1]
# 计算R、G、B的均值,并累加到总和中
total_r += np.mean(roi[:, :, 2]) # R通道的均值
total_g += np.mean(roi[:, :, 1]) # G通道的均值
total_b += np.mean(roi[:, :, 0]) # B通道的均值
# 绘制小矩形的边框在调整后的图像上
cv2.rectangle(clone_resized, (sub_x0, sub_y0), (sub_x1, sub_y1), (255, 0, 0), 1)
# 绘制大矩形在调整后的图像上
cv2.rectangle(clone_resized, start_point, end_point, (0, 255, 0), 2)
break
elif key == 27: # Esc键
break
del img_display
# 显示960x540大小的已经完成框选的图片
cv2.imshow('Result', clone_resized)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 计算灰阶色块的R、G、B均值
avg_r = total_r / 6
avg_g = total_g / 6
avg_b = total_b / 6
# 计算WB gain
RG = avg_g / avg_r
BG = avg_g / avg_b
return RG, BG
def correct_white_balance(img, pattern, bit, RG, BG):
img = img.astype(np.float32)
if pattern.lower() == 'rggb':
# 偶数行偶数列乘以BG
img[::2, ::2] *= BG
# 奇数行奇数列乘以RG
img[1::2, 1::2] *= RG
elif pattern.lower() == 'bggr':
# 偶数行偶数列乘以RG
img[::2, ::2] *= RG
# 奇数行奇数列乘以BG
img[1::2, 1::2] *= BG
elif pattern.lower() == 'grbg':
# 偶数行奇数列乘以RG
img[::2, 1::2] *= RG
# 奇数行偶数列乘以BG
img[1::2, ::2] *= BG
elif pattern.lower() == 'gbrg':
# 偶数行奇数列乘以BG
img[::2, 1::2] *= BG
# 奇数行偶数列乘以RG
img[1::2, ::2] *= RG
else:
print('Unsupported pattern:', pattern)
return None
# 确保数据不会超限
img = np.clip(img, 0, 2 ** bit - 1).astype(np.uint16)
return img
def demosaic(img, pattern):
# 确定bayer模式标志
if pattern.lower() == 'bggr':
bayer_flag = cv2.COLOR_BAYER_BG2BGR
elif pattern.lower() == 'rggb':
bayer_flag = cv2.COLOR_BAYER_RG2BGR
elif pattern.lower() == 'grbg':
bayer_flag = cv2.COLOR_BAYER_GR2BGR
elif pattern.lower() == 'gbrg':
bayer_flag = cv2.COLOR_BAYER_GB2BGR
else:
print('Unsupported pattern:', pattern)
return
# 检查图像是否正确读取
if img is None:
raise ValueError("无法读取图像,请检查路径和格式。")
img_bgr = cv2.cvtColor(img, bayer_flag)
img_bgr = (img_bgr / 16).astype(np.uint8)
return img_bgr
def GammatoCCM(img_bgr, Lab_ideal_input, weights, ccm_limit):
if weights is None:
weights = np.ones(18) # 只优化前18个色块
# 获取24色块色块平均RGB值
avg_rgb_list = draw_24color_RGB(img_bgr)
# 确保 avg_rgb_list 是NumPy数组
avg_rgb_list = np.array(avg_rgb_list, dtype=np.float32)
# 对24色块的RGB值进行Gamma校正
avg_rgb_list_gamma = gamma(avg_rgb_list.reshape(24, 1, 3)).reshape(24, 3)
target_lab_list = np.array(Lab_ideal_input, dtype=np.float32)
# 寻找最优的CCM矩阵
best_CCM = find_optimal_CCM(avg_rgb_list_gamma, target_lab_list, weights, ccm_limit)
best_CCM = np.round(best_CCM).astype(int)
print("最优的CCM矩阵:", best_CCM)
# 对24色块进行操作
# 使用 cv2.transform 进行CCM转换
avg_rgb_list_gamma_reshaped = avg_rgb_list_gamma.reshape(24, 1, 3)
# 进行CCM转换
corrected_list_rgb = cv2.transform(avg_rgb_list_gamma_reshaped, best_CCM)
corrected_list_rgb /= 255
# 将结果转换回 (24, 3) 的形状
corrected_list_rgb = corrected_list_rgb.reshape(24, 3)
corrected_rgb_list_uint8 = np.clip(corrected_list_rgb, 0, 255).astype(np.uint8)
# 打印转换后的RGB值
# print("转换后的RGB值:", corrected_rgb_list_uint8)
# 对图像进行Gamma校正
img_gamma_bgr = gamma(img_bgr)
# 应用最优的CCM矩阵进行颜色校正
img_gamma_rgb = cv2.cvtColor(img_gamma_bgr, cv2.COLOR_BGR2RGB).astype(np.float32)
img_corrected_float = cv2.transform(img_gamma_rgb, best_CCM)
img_corrected_float /= 255
img_corrected_float = img_corrected_float.astype(np.float16)
img_corrected = np.clip(img_corrected_float, 0, 255).astype(np.uint8)
return img_corrected, corrected_list_rgb
def CCMtoGamma(img_bgr, Lab_ideal_input, weights, ccm_limit):
if weights is None:
weights = np.ones(18) # 只优化前18个色块
# 将Lab值转换为OpenCV所需的格式,L in [0, 255],a and b in [0, 255]
Lab_ideal = Lab_ideal_input
Lab_ideal[:, 0] = Lab_ideal_input[:, 0] * 255 / 100
Lab_ideal[:, 1:] = Lab_ideal[:, 1:] + 128
# AI提亮
# img_bgr = gamma_brt(img_bgr)
# img_bgr = np.clip(img_bgr, 0, 255).astype(np.uint8)
# 将Lab图像转换为RGB图像
Lab_ideal = Lab_ideal.reshape(24, 1, 3).astype(np.uint8)
rgb_ideal = cv2.cvtColor(Lab_ideal, cv2.COLOR_LAB2RGB)
# degamma后转回Lab
rgb_ideal_degamma = degamma(rgb_ideal)
rgb_ideal_degamma /= 255.0
lab_ideal_degamma = cv2.cvtColor(rgb_ideal_degamma, cv2.COLOR_RGB2Lab)
target_lab_list_degamma = np.array(lab_ideal_degamma, dtype=np.float32).reshape(24, 3)
# 获取24色块色块平均RGB值
avg_rgb_list = draw_24color_RGB(img_bgr)
# 确保 avg_rgb_list 是NumPy数组
avg_rgb_list = np.array(avg_rgb_list, dtype=np.float32)
# 寻找最优的CCM矩阵
best_CCM = find_optimal_CCM(avg_rgb_list, target_lab_list_degamma, weights, ccm_limit)
best_CCM = np.round(best_CCM).astype(int)
print("最优的CCM矩阵:", best_CCM)
# 对24色块进行操作
# 使用 cv2.transform 进行CCM转换
# cv2.transform 需要输入数据的形状为 (N, 1, 3),其中 N 是样本数
avg_rgb_list_reshaped = avg_rgb_list.reshape(24, 1, 3)
# 进行CCM转换
# print("转换前的RGB值:", avg_rgb_list_reshaped)
avg_list_rgb = cv2.transform(avg_rgb_list_reshaped, best_CCM)
avg_list_rgb /= 255
corrected_list_rgb = np.clip(avg_list_rgb, 0, 255)
# print("转换后的RGB值:", corrected_list_rgb)
# gamma处理
corrected_list_rgb = gamma(corrected_list_rgb.reshape(24, 1, 3)).reshape(24, 3)
corrected_list_rgb = np.clip(corrected_list_rgb, 0, 255).astype(np.float32)
# 应用最优的CCM矩阵进行颜色校正
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB).astype(np.float32)
img_corrected_float = cv2.transform(img_rgb, best_CCM)
img_corrected_float /= 255
img_corrected_float = np.clip(img_corrected_float, 0, 255).astype(np.float16)
# img_corrected_float = img_corrected_float.astype(np.float32)
# 对图像进行Gamma校正
img_gamma = gamma(img_corrected_float)
img_corrected = np.clip(img_gamma, 0, 255).astype(np.uint8)
return img_corrected, corrected_list_rgb
# # def main_raw(image_path, pattern, w, h, bit, bl_level, RG, BG, correct_order):
def main_raw(image_path, pattern, w, h, bit, bl_level, correct_order, ccm_limit):
# 读取 RAW 图
img = read_raw(image_path, bit, w, h)
if img is None:
return None
# OB
img = correct_ob(img, bit, bl_level)
# 获取wb gain
img_wb = demosaic(img, pattern)
RG, BG = get_Rgain_Bgain(img_wb)
print('wb_gain', RG, BG)
# 校正白平衡
img = correct_white_balance(img, pattern, bit, RG, BG)
# demosaic
img_bgr = demosaic(img, pattern)
# 输入标准Lab
Lab_ideal_input = input_Lab_ideal()
Lab_ideal = Lab_ideal_input.copy()
# 配置不同色块权重
weights = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
# weights = create_input_grid(default_value=1)
# print('权重分布', weights)
# 显示校正前的图像
# img_before_CCM = gamma(img_bgr)
# img_before_CCM = np.clip(img_before_CCM, 0, 255).astype(np.uint8)
# display_image(img_before_CCM)
# 选择先进行gamma还是CCM
# CCM & gamma
if correct_order == 1:
img_corrected, corrected_list_rgb = GammatoCCM(img_bgr, Lab_ideal_input, weights, ccm_limit)
elif correct_order == 2:
img_corrected, corrected_list_rgb = CCMtoGamma(img_bgr, Lab_ideal_input, weights, ccm_limit)
else:
raise ValueError("Invalid order. Choose 1 or 2.")
# 显示CCM校正后的图像
img_corrected_bgr = cv2.cvtColor(img_corrected, cv2.COLOR_RGB2BGR)
display_image(img_corrected_bgr)
# HSV
k_theta_matrix, H_S_matrix = calculate_correction_parameters(corrected_list_rgb, Lab_ideal, weights)
# k_theta_matrix, H_S_matrix = calculate_correction_parameters(corrected_list_rgb, Lab_ideal)
k_theta_matrix = k_theta_matrix.astype(np.float32)
print('k_theta_corrected', k_theta_matrix)
print('H_S_corrected', H_S_matrix)
# correct_matrices = np.load('correct_matrices.npy')
img_final = apply_hsv_correction(img_corrected, k_theta_matrix)
return img_final
# def main_jpg(image_path, ccm_limit):
# # 读取 JPEG 图像
# img_bgr = cv2.imread(image_path)
# if img_bgr is None:
# print("Failed to load image.")
# return None
#
# # 将图像从BGR转换为RGB
# img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
#
# # 输入标准Lab
# Lab_ideal_input = input_Lab_ideal()
# Lab_ideal = Lab_ideal_input.copy()
#
# # 配置不同色块权重
# weights = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
#
# # 选择先进行gamma还是CCM
# # CCM & gamma
# img_corrected, corrected_list_rgb = CCMtoGamma(img_rgb, Lab_ideal_input, weights, ccm_limit)
#
# # 显示CCM校正后的图像
# img_corrected_bgr = cv2.cvtColor(img_corrected, cv2.COLOR_RGB2BGR)
# display_image(img_corrected_bgr)
#
# # HSV
# k_theta_matrix, H_S_matrix = calculate_correction_parameters(corrected_list_rgb, Lab_ideal)
# k_theta_matrix = k_theta_matrix.astype(np.float32)
# print('k_theta_corrected', k_theta_matrix)
# print('H_S_corrected', H_S_matrix)
#
# # 应用HSV校正
# img_final = apply_hsv_correction(img_corrected, k_theta_matrix)
#
# return img_final
def get_raw_config():
# 弹出输入框获取raw图像相关设置
raw_config = g.multenterbox(msg='输入raw相关设置', title='CCM',
fields=['raw_pattern', 'raw_size_w', 'raw_size_h', 'raw_bit', 'raw_black_level', 'gamma->CCM(1) OR CCM->gamma(2)', 'CCM矩阵约束'],
values=['bggr', '3840', '2160', '12', '256', '1', '3.0'])
raw_pattern, raw_size_w, raw_size_h, raw_bit, bl_level, correct_order, ccm_limit = (
raw_config[0], int(raw_config[1]), int(raw_config[2]), int(raw_config[3]),
int(raw_config[4]), int(raw_config[5]), float(raw_config[6])
)
return raw_pattern, raw_size_w, raw_size_h, raw_bit, bl_level, correct_order, ccm_limit
if __name__ == '__main__':
# 选择raw图像文件
image_path = g.fileopenbox(msg='输入raw图', title='客观校正', default='image/rgb.raw')
# 获取raw配置参数
raw_config = get_raw_config()
# 处理raw图像
image = main_raw(image_path, *raw_config)
img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# cv2.imwrite('Corrected Image', img_bgr)
img_bgr = cv2.resize(img_bgr, (960, 540))
cv2.imshow('Final Image', img_bgr)
cv2.moveWindow('Final Image', x=100, y=100)
cv2.waitKey(0)
cv2.destroyAllWindows()
print('finish')
分析一下以上代码,详细一些,包括整体算法架构以及各函数
最新发布