color_transfer / enhence_reinhard.py
JamesL404's picture
Upload 8 files
0c50ae6 verified
import argparse
import sys
import numpy as np
import os
from PIL import Image
import cv2
import skimage
METHODS = ('lhm', 'pccm', 'reinhard')
img_dir = 'C:/Users/joshua.lin/Desktop/AdaIN/pytorch-AdaIN-master/input/'
def transfer_lhm(content, reference):
"""Transfers colors from a reference image to a content image using the
Linear Histogram Matching.
content: NumPy array (HxWxC)
reference: NumPy array (HxWxC)
"""
# Convert HxWxC image to a (H*W)xC matrix.
shape = content.shape
assert len(shape) == 3
content = content.reshape(-1, shape[-1]).astype(np.float32)
reference = reference.reshape(-1, shape[-1]).astype(np.float32)
def matrix_sqrt(X):
eig_val, eig_vec = np.linalg.eig(X)
return eig_vec.dot(np.diag(np.sqrt(eig_val))).dot(eig_vec.T)
#
mu_content = np.mean(content, axis=0)
#
mu_reference = np.mean(reference, axis=0)
cov_content = np.cov(content, rowvar=False)
cov_reference = np.cov(reference, rowvar=False)
#
result = matrix_sqrt(cov_reference)
#
result = result.dot(np.linalg.inv(matrix_sqrt(cov_content)))
#
result = result.dot((content - mu_content).T).T
#result = result.dot((content*1 - mu_content*0.5).T).T*3
#
result = result + mu_reference
# Restore image dimensions.
result = result.reshape(shape).clip(0, 255).round().astype(np.uint8)
return result
def transfer_pccm(content, reference):
"""Transfers colors from a reference image to a content image using
Principal Component Color Matching.
content: NumPy array (HxWxC)
reference: NumPy array (HxWxC)
"""
# Convert HxWxC image to a (H*W)xC matrix.
shape = content.shape
assert len(shape) == 3
content = content.reshape(-1, shape[-1]).astype(np.float32)
reference = reference.reshape(-1, shape[-1]).astype(np.float32)
mu_content = np.mean(content, axis=0)
mu_reference = np.mean(reference, axis=0)
cov_content = np.cov(content, rowvar=False)
cov_reference = np.cov(reference, rowvar=False)
eigval_content, eigvec_content = np.linalg.eig(cov_content)
eigval_reference, eigvec_reference = np.linalg.eig(cov_reference)
scaling = np.diag(np.sqrt(eigval_reference / eigval_content))
transform = eigvec_reference.dot(scaling).dot(eigvec_content.T)
result = (content - mu_content).dot(transform.T) + mu_reference
# Restore image dimensions.
result = result.reshape(shape).clip(0, 255).round().astype(np.uint8)
return result
def transfer_reinhard(content, reference):
"""Transfers colors from a reference image to a content image using the
technique from Reinhard et al.
content: NumPy array (HxWxC)
reference: NumPy array (HxWxC)
"""
# Convert HxWxC image to a (H*W)xC matrix.
shape = content.shape
assert len(shape) == 3
content = content.reshape(-1, shape[-1]).astype(np.float32)
reference = reference.reshape(-1, shape[-1]).astype(np.float32)
m1 = np.array([
[0.3811, 0.1967, 0.0241],
[0.5783, 0.7244, 0.1288],
[0.0402, 0.0782, 0.8444],
])
m2 = np.array([
[0.5774, 0.4082, 0.7071],
[0.5774, 0.4082, -0.7071],
[0.5774, -0.8165, 0.0000],
])
m3 = np.array([
[0.5774, 0.5774, 0.5774],
[0.4082, 0.4082, -0.8165],
[0.7071, -0.7071, 0.0000],
])
m4 = np.array([
[4.4679, -1.2186, 0.0497],
[-3.5873, 2.3809, -0.2439],
[0.1193, -0.1624, 1.2045],
])
# Avoid log of 0. Clipping is used instead of adding epsilon, to avoid
# taking a log of a small number whose very low output distorts the results.
# WARN: This differs from the Reinhard paper, where no adjustment is made.
lab_content = np.log10(np.maximum(1.0, content.dot(m1))).dot(m2)
lab_reference = np.log10(np.maximum(1.0, reference.dot(m1))).dot(m2)
mu_content = lab_content.mean(axis=0) # shape=3
mu_reference = lab_reference.mean(axis=0)
std_source = np.std(content, axis=0)
std_target = np.std(reference, axis=0)
#variable percentage for mu and std
result = lab_content - mu_content
result *= std_target
result /= std_source
result += mu_reference
result = (10 ** result.dot(m3)).dot(m4)
# Restore image dimensions.
result = result.reshape(shape).clip(0, 255).round().astype(np.uint8)
return result
# ===================================================================================
def parse_args(argv):
parser = argparse.ArgumentParser(
prog='colortrans',
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
# Optional arguments
parser.add_argument(
'--method', default='lhm', choices=METHODS,
help='Algorithm to use for color transfer.')
# Required arguments
parser.add_argument('content', help='Path to content image (qualitative appearance).')
parser.add_argument('reference', help='Path to reference image (desired colors).')
parser.add_argument('output', help='Path to output image.')
args = parser.parse_args(argv[1:])
return args
def main(argv=sys.argv):
args = parse_args(argv)
content_img = Image.open(args.content).convert('RGB')
# The slicing is to remove transparency channels if they exist.
content = np.array(content_img)[:, :, :3]
reference_img = Image.open(args.reference).convert('RGB')
reference = np.array(reference_img)[:, :, :3]
transfer = globals()[f'transfer_{args.method}']
output = transfer(content, reference)
Image.fromarray(output).save(args.output)
# ==================================================================================
def test_reinhard():
content_path = img_dir + 'content/brad_pitt.jpg'
style_path = 'output/brad_pitt_stylized_Neon_City.jpg'
content_img = Image.open(content_path).convert('RGB')
content = np.array(content_img)[:, :, :3]
style_img = Image.open(style_path).convert('RGB')
style = np.array(style_img)[:, :, :3]
output = transfer_lhm(content, style)
Image.fromarray(output).save('output/processed.jpg')
def test1():
img_path = img_dir + '2.jpg'
img = skimage.io.imread(img_path)
sk_imgf = skimage.util.img_as_float32(img)
cv_img = skimage.img_as_ubyte(img)
print('')
# ==============================================================
if __name__ == '__main__':
test_reinhard()
# test1()