You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
383 lines
12 KiB
383 lines
12 KiB
import os.path |
|
import numpy as np |
|
from scipy.interpolate import CubicSpline |
|
import cv2 |
|
|
|
import argparse |
|
from ps_image_white_add_white_d import photoshop_add_white |
|
|
|
|
|
def adjust_levels_image(img): |
|
"""""" |
|
img = img.astype(np.float32) |
|
img = 255 * ((img - 20) / (241 - 20)) |
|
img[img < 0] = 0 |
|
img[img > 255] = 255 |
|
img = 255 * np.power(img / 255.0, 1.0 / 1.34) |
|
img = (img / 255) * (255- 0) + 0 |
|
img[img < 0] = 0 |
|
img[img > 255] = 255 |
|
img = img.astype(np.uint8) |
|
return img |
|
|
|
|
|
def photoshop_style_feather(image, mask, radius=150): |
|
""" |
|
""" |
|
if mask.dtype != np.uint8: |
|
mask = (mask * 255).astype(np.uint8) |
|
if len(mask.shape) > 2: |
|
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY) |
|
kernel_size = max(3, int(2 * np.ceil(2 * radius) + 1)) |
|
expanded_size = (mask.shape[0] + 2 * radius, mask.shape[1] + 2 * radius) |
|
expanded_mask = np.zeros(expanded_size, dtype=np.uint8) |
|
center_y, center_x = radius, radius |
|
expanded_mask[center_y:center_y + mask.shape[0], |
|
center_x:center_x + mask.shape[1]] = mask |
|
|
|
blurred_expanded_mask = cv2.GaussianBlur( |
|
expanded_mask, |
|
(kernel_size, kernel_size), |
|
sigmaX=radius, |
|
sigmaY=radius, |
|
borderType=cv2.BORDER_REFLECT_101 |
|
) |
|
|
|
feathered_mask = blurred_expanded_mask[center_y:center_y + mask.shape[0], |
|
center_x:center_x + mask.shape[1]] |
|
|
|
feathered_mask = feathered_mask.astype(np.float32) / 255.0 |
|
feathered_mask = np.power(feathered_mask, 1.1) # 轻微增强对比度 |
|
feathered_mask = np.clip(feathered_mask * 255, 0, 255).astype(np.uint8) |
|
|
|
return feathered_mask |
|
|
|
def calculate_luminance(img): |
|
"""""" |
|
if len(img.shape) == 3: |
|
ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb) |
|
return ycrcb[:, :, 0].astype(np.float32) / 255.0 |
|
else: |
|
return img.astype(np.float32) / 255.0 |
|
|
|
|
|
def photoshop_feather_blend(adjusted_img, original_img, mask, feather_radius=150, brightness_factor=0.95): |
|
""" |
|
""" |
|
if mask.dtype != np.uint8: |
|
mask = (mask * 255).astype(np.uint8) |
|
if len(mask.shape) > 2: |
|
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY) |
|
# plt.figure(figsize=(10, 8)) |
|
# plt.imshow(mask, cmap='gray') |
|
# plt.title("Feathered Mask") |
|
# plt.axis('off') |
|
# plt.colorbar(label='Opacity') |
|
# plt.show() |
|
|
|
feathered_mask = photoshop_style_feather(original_img, mask, feather_radius) |
|
# plt.figure(figsize=(10, 8)) |
|
# plt.imshow(feathered_mask, cmap='gray') |
|
# plt.title("Feathered Mask") |
|
# plt.axis('off') |
|
# plt.colorbar(label='Opacity') |
|
# plt.show() |
|
feathered_mask_float = feathered_mask.astype(np.float32) / 255.0 |
|
|
|
if len(original_img.shape) == 3 and len(feathered_mask_float.shape) == 2: |
|
feathered_mask_float = np.stack([feathered_mask_float] * 3, axis=-1) |
|
|
|
def to_linear(img): |
|
img_linear = img.astype(np.float32) / 255.0 |
|
return np.where(img_linear <= 0.04045, |
|
img_linear / 12.92, |
|
((img_linear + 0.055) / 1.055) ** 2.4) |
|
|
|
def to_srgb(img_linear): |
|
return np.where(img_linear <= 0.0031308, |
|
img_linear * 12.92, |
|
1.055 * (img_linear ** (1 / 2.4)) - 0.055) |
|
|
|
adjusted_linear = to_linear(adjusted_img) |
|
original_linear = to_linear(original_img) |
|
|
|
luminance_adjustment = np.mean(original_linear, axis=-1, keepdims=True) * (1.0 - brightness_factor) |
|
adjusted_linear_corrected = adjusted_linear - luminance_adjustment |
|
|
|
blended_linear = (adjusted_linear_corrected * feathered_mask_float + |
|
original_linear * (1 - feathered_mask_float)) |
|
|
|
blended_srgb = to_srgb(blended_linear) |
|
blended_img = np.clip(blended_srgb * 255, 0, 255).astype(np.uint8) |
|
|
|
return blended_img |
|
|
|
|
|
def rgb2lab_image(rgb_img): |
|
"""""" |
|
rgb = rgb_img.astype(np.float32) / 255.0 |
|
|
|
mask = rgb > 0.04045 |
|
rgb = np.where(mask, |
|
np.power((rgb + 0.055) / 1.055, 2.4), |
|
rgb / 12.92) |
|
|
|
XYZ = np.dot(rgb, [ |
|
[0.436052025, 0.222491598, 0.013929122], |
|
[0.385081593, 0.716886060, 0.097097002], |
|
[0.143087414, 0.060621486, 0.714185470] |
|
]) |
|
|
|
XYZ *= np.array([100.0, 100.0, 100.0]) / [96.4221, 100.0, 82.5211] |
|
|
|
epsilon = 0.008856 |
|
kappa = 903.3 |
|
|
|
XYZ_norm = np.where(XYZ > epsilon, |
|
np.power(XYZ, 1 / 3), |
|
(kappa * XYZ + 16) / 116) |
|
|
|
L = 116 * XYZ_norm[..., 1] - 16 |
|
a = 500 * (XYZ_norm[..., 0] - XYZ_norm[..., 1]) |
|
b = 200 * (XYZ_norm[..., 1] - XYZ_norm[..., 2]) |
|
|
|
return np.stack([L, a, b], axis=-1) |
|
|
|
|
|
def photoshop_lab_color_range_optimized(bgr_img, target_lab, tolerance=59, anti_alias=True): |
|
"""""" |
|
|
|
rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) |
|
lab_img = rgb2lab_image(rgb_img) |
|
|
|
L, a, b = lab_img[:, :, 0], lab_img[:, :, 1], lab_img[:, :, 2] |
|
target_L, target_a, target_b = target_lab |
|
|
|
diff_L = np.abs(L - target_L) |
|
diff_a = np.abs(a - target_a) |
|
diff_b = np.abs(b - target_b) |
|
|
|
dark_boost = np.ones_like(L) |
|
dark_mask = L <40 |
|
dark_boost[dark_mask] = 1.2 |
|
|
|
weighted_diff = np.sqrt( |
|
0.25 * (diff_L / 100) ** 2 + |
|
0.75 * ((diff_a + diff_b) / 255) ** 2 |
|
) * 100 |
|
|
|
weighted_diff = weighted_diff / dark_boost |
|
|
|
threshold = 1.6 * (100 - tolerance) / 100 * 23 |
|
|
|
normalized_diff = weighted_diff / threshold |
|
mask = 0.5 * (np.tanh(4 * (1 - normalized_diff)) + 1) |
|
|
|
if anti_alias: |
|
mask = cv2.GaussianBlur(mask, (5, 5), 0) |
|
|
|
return mask |
|
|
|
|
|
def generate_curve_lut(x_points, y_points, smooth_factor=20): |
|
""" |
|
输入采样点,生成 256 长度的查找表(LUT) |
|
""" |
|
if smooth_factor > 0: |
|
new_x, new_y = [], [] |
|
for i in range(len(x_points) - 1): |
|
x0, x1 = x_points[i], x_points[i + 1] |
|
y0, y1 = y_points[i], y_points[i + 1] |
|
new_x.append(x0) |
|
new_y.append(y0) |
|
|
|
# 在每对原始控制点之间插入平滑点 |
|
steps = max(1, int(smooth_factor * (x1 - x0) / 256)) |
|
for j in range(1, steps): |
|
alpha = j / steps |
|
new_x.append(int(x0 + alpha * (x1 - x0))) |
|
new_y.append(y0 + alpha * (y1 - y0)) |
|
|
|
# 添加最后一个点 |
|
new_x.append(x_points[-1]) |
|
new_y.append(y_points[-1]) |
|
x_points, y_points = new_x, new_y |
|
cs = CubicSpline(x_points, y_points, bc_type='natural') |
|
x = np.arange(256) |
|
y = cs(x) |
|
y = np.clip(y, 0, 255).astype(np.uint8) |
|
return y |
|
|
|
def apply_curve(img, lut): |
|
""" |
|
对图像的每个通道应用曲线 LUT(复合通道) |
|
""" |
|
result = cv2.LUT(img, lut) |
|
return result |
|
|
|
def add_color_image(img): |
|
"""""" |
|
# x_points = [0, 131, 255] |
|
# y_points = [0, 124, 255] |
|
x_points = [6, 184, 255] |
|
y_points = [0, 191, 255] |
|
lut = generate_curve_lut(x_points, y_points) |
|
adjusted = apply_curve(img, lut) |
|
|
|
return adjusted |
|
|
|
def unsharp_mask(img_bgr,amount=0.47, radius=3, threshold=0): |
|
"""""" |
|
|
|
|
|
img = img_bgr.astype(np.float32) / 255.0 |
|
img_ycc = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb) |
|
y, cr, cb = cv2.split(img_ycc) |
|
|
|
# 模糊亮度通道 |
|
blurred_y = cv2.GaussianBlur(y, (0, 0), sigmaX=radius, sigmaY=radius) |
|
|
|
# 差值(高频图像) |
|
high_pass = y - blurred_y |
|
|
|
# 阈值处理:模仿 PS 的 threshold(在 0~1 范围内) |
|
if threshold > 0: |
|
mask = np.abs(high_pass) >= (threshold / 255.0) |
|
high_pass = high_pass * mask |
|
|
|
# 关键:亮边增强 + 暗边压制(模拟 PS 更自然的边缘) |
|
sharp_y = y + amount * high_pass |
|
sharp_y = np.clip(sharp_y, 0, 1) |
|
|
|
# 合并回图像 |
|
merged_ycc = cv2.merge([sharp_y, cr, cb]) |
|
final_img = cv2.cvtColor(merged_ycc, cv2.COLOR_YCrCb2BGR) |
|
final_img = np.clip(final_img * 255.0, 0, 255).astype(np.uint8) |
|
|
|
#cv2.imwrite(output_path, final_img) |
|
return final_img |
|
|
|
|
|
def add_shadow_image(img): |
|
"""""" |
|
x_points = [0, 131, 255] |
|
y_points = [0, 124, 255] |
|
|
|
lut = generate_curve_lut(x_points, y_points) |
|
adjusted = apply_curve(img, lut) |
|
|
|
return adjusted |
|
|
|
|
|
|
|
|
|
def create_red_mask(img): |
|
"""使用 Lab 空间中的 A 通道提取红色区域,返回 (h, w, 1) 掩码""" |
|
lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) |
|
a = lab[..., 1].astype(np.float32) |
|
red_score = np.clip((a - 128) / 50.0, 0, 1) # A 通道 > 128 表示偏红 |
|
red_score = cv2.GaussianBlur(red_score, (9, 9), sigmaX=4) |
|
return red_score[..., np.newaxis] # 变成 (h, w, 1) |
|
|
|
|
|
|
|
def cmyk_to_rgb(cmyk): |
|
"""CMYK → RGB,模拟Photoshop近似""" |
|
c, m, y, k = cmyk[..., 0], cmyk[..., 1], cmyk[..., 2], cmyk[..., 3] |
|
r = (1 - c) * (1 - k) |
|
g = (1 - m) * (1 - k) |
|
b = (1 - y) * (1 - k) |
|
return np.clip(np.stack([r, g, b], axis=-1) * 255, 0, 255) |
|
|
|
|
|
def rgb_to_cmyk(rgb): |
|
"""RGB → CMYK,模拟Photoshop近似""" |
|
r, g, b = rgb[..., 0] / 255.0, rgb[..., 1] / 255.0, rgb[..., 2] / 255.0 |
|
k = 1 - np.maximum.reduce([r, g, b]) |
|
k_safe = np.where(k == 1, 1, k) |
|
|
|
c = np.where(k == 1, 0, (1 - r - k) / (1 - k_safe)) |
|
m = np.where(k == 1, 0, (1 - g - k) / (1 - k_safe)) |
|
y = np.where(k == 1, 0, (1 - b - k) / (1 - k_safe)) |
|
return np.stack([c, m, y, k], axis=-1) |
|
|
|
def selective_color_adjustment(img, target_color, cmyk_adjustments, relative=True): |
|
if target_color != 'red': |
|
raise NotImplementedError("当前只支持 red") |
|
|
|
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) |
|
mask = create_red_mask(img) |
|
|
|
cmyk = rgb_to_cmyk(img_rgb) |
|
|
|
# 应用 CMYK 调整 |
|
for channel, value in cmyk_adjustments.items(): |
|
idx = {'cyan': 0, 'magenta': 1, 'yellow': 2, 'black': 3}.get(channel) |
|
if idx is None: |
|
continue |
|
original = cmyk[..., idx] |
|
v = value / 100.0 |
|
|
|
if relative: |
|
# 非线性相对调整,更接近 Photoshop 曲线(经验公式) |
|
adjusted = original * (1 + v) ** 1.35 # gamma 可调 |
|
else: |
|
adjusted = original + v |
|
|
|
cmyk[..., idx] = np.clip(adjusted, 0, 1) |
|
|
|
# 转换回 RGB 并混合 |
|
adjusted_rgb = cmyk_to_rgb(cmyk) |
|
output_rgb = img_rgb * (1 - mask) + adjusted_rgb * mask |
|
output_rgb = np.clip(output_rgb, 0, 255).astype(np.uint8) |
|
|
|
return cv2.cvtColor(output_rgb, cv2.COLOR_RGB2BGR) |
|
|
|
def reduce_red_black_relative(img): |
|
"""模拟 Photoshop:红色 → 黑色 -8%,相对模式""" |
|
return selective_color_adjustment( |
|
img, |
|
target_color='red', |
|
cmyk_adjustments={'black': -8}, |
|
relative=True |
|
) |
|
|
|
|
|
|
|
def photoshop_actions_emulation(input_path, output_path): |
|
"""""" |
|
original_img = cv2.imread(input_path) |
|
# 加暗 |
|
shadow_image1=add_shadow_image(original_img) |
|
shadow_image2 = add_shadow_image(shadow_image1) |
|
|
|
# output_down_path= output_path.replace(".jpg","down.jpg") |
|
# cv2.imwrite(output_down_path, shadow_image2) |
|
|
|
original_img_color=add_color_image(shadow_image2) |
|
# output_color_path= output_path.replace(".jpg","add_color.jpg") |
|
# cv2.imwrite(output_color_path, original_img_color) |
|
#白位加白 |
|
result_white_image= photoshop_add_white(original_img_color) |
|
# output_white_path= output_color_path.replace(".jpg","white.jpg") |
|
# cv2.imwrite(output_white_path, result_white_image) |
|
#锐化 |
|
result_usm = unsharp_mask(result_white_image,amount=0.47, radius=3, threshold=0) |
|
|
|
cv2.imwrite(output_path, result_usm) |
|
|
|
|
|
if __name__ == '__main__': |
|
arg = argparse.ArgumentParser() |
|
arg.add_argument('--image_name', type=str, default='274351Tex1_adjusted060518_2_221.jpg') |
|
arg.add_argument('--image_name_new', type=str, default='274351Tex1_adjusted060518_2_221_999999.jpg') |
|
arg.add_argument('--in_dir', type=str, default='/data/datasets_20t/fsdownload/image_color_timing/output/') |
|
arg.add_argument('--out_dir', type=str, default='/data/datasets_20t/fsdownload/image_color_timing/shadow_up/') |
|
args = arg.parse_args() |
|
os.makedirs(args.out_dir,exist_ok=True) |
|
input_path = os.path.join(args.in_dir,args.image_name) |
|
output_path = os.path.join(args.out_dir,args.image_name_new) |
|
photoshop_actions_emulation(input_path, output_path) |
|
|
|
|
|
|
|
|