Reputation: 65
I have a series of images and was wondering if there is a possibility to write something in python to apply a contrast and brightness curve Like in the images below.
Upvotes: 2
Views: 2507
Reputation: 207465
As stated by Fred in the comments, you probably want to create a Catmull-Rom spline from your points and then apply that using a LUT within OpenCV.
#!/usr/bin/env python3
# from https://splines.readthedocs.io/en/latest/euclidean/piecewise-monotone.html#Examples
import matplotlib.pyplot as plt
import numpy as np
import splines
import cv2
# Just helper code for plotting - you don't need this
def grid_lines(x=None, y=None, ax=None):
if ax is None:
ax = plt.gca()
if x is not None:
ax.set_xticks(x)
ax.xaxis.grid(True)
if y is not None:
ax.set_yticks(y)
ax.yaxis.grid(True)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
# Just helper code for plotting - you don't need this
def plot_spline_1d(spline, samples=100, **kwargs):
'Plot a one-dimensional spline.'
ax = plt.gca()
times = np.linspace(spline.grid[0], spline.grid[-1], samples)
ax.plot(times, spline.evaluate(times), **kwargs)
ax.scatter(spline.grid, spline.evaluate(spline.grid))
The following simulates an S-shaped contrast curve similar to your 5 points:
xvals = 0, 64, 128, 192, 255
yvals = 0, 32, 128, 206, 255
# Just code for plotting the curve - you don't need this
plot_spline_1d(splines.CatmullRom(yvals, xvals), label='Catmull–Rom')
plt.legend()
grid_lines(xvals)
plt.show()
The actual code you need only really starts here:
# Derive Catmull-Rom spline for our X,Y
res = splines.CatmullRom(yvals, xvals)
# Make LUT (Lookup Table) from spline
LUT = np.uint8(res.evaluate(range(0,256)))
# Load ramp image as greyscale
im = cv2.imread('ramp.png', cv2.IMREAD_GRAYSCALE)
# Apply LUT to image
stretched = cv2.LUT(im, LUT)
# Save result
cv2.imwrite('result.png', stretched)
That turns this ramp:
into this:
Note that I artificially added a thin red border so you can see the extent of the image on StackOverflow's annoying background.
As regards reading a Photoshop Curves file (ACV or .acv
), I wrote some code a while back that parses them but I haven't integrated that with the code above - it shouldn't be too hard - you basically save the points from the ACV file and use them to generate the spline for the above code. I leave it below for anyone interested to play with:
"""
################################################################################
fauxtoshop - does some things like Adobe Photoshop
Mark Setchell ([email protected])
Reads, interprets and possibly applies Photoshop files:
- Curves files (*.acv)
- Levels files (*.alv)
- Filter Kernel files (*.acf)
- Hue Saturation files (*.ahu)
################################################################################
"""
import sys
import numpy
from struct import unpack
def loadFilter(filename):
if filename.lower().endswith('.acv'):
loadCurvesFilter(filename)
return
if filename.lower().endswith('.alv'):
loadLevelsFilter(filename)
return
if filename.lower().endswith('.acf'):
loadKernelFilter(filename)
return
if filename.lower().endswith('.ahu'):
loadHSLFilter(filename)
return
sys.exit(f'ERROR: Unknown file extension {filename}')
def loadCurvesFilter(filename):
with open(filename, 'rb') as f:
version, ncurves = unpack('>HH', f.read(4))
print(f'File: {filename}')
print(f'Version: {version}')
if version != 4:
sys.exit('ERROR: Cowardly refusing to read version other than 4')
print(f'Curve count: {ncurves}')
curves = []
for c in range(ncurves):
npoints, = unpack('>H', f.read(2))
print(f'Curve: {c}, {npoints} points follow:')
curve = []
for p in range(npoints):
y, x = unpack('>HH', f.read(4))
print(f'Curve: {c}, point: {p}, x={x}, y={y}')
curve.append((x,y))
curves.append(curve)
return curves
def loadLevelsFilter(filename):
sys.exit("ERROR: Levels filter not yet implemeted")
def loadKernelFilter(filename):
sys.exit("ERROR: Kernel filter not yet implemeted")
def loadHSLFilter(filename):
with open(filename, 'rb') as f:
version, usage, pad = unpack('>HBB', f.read(4))
print(f'File: {filename}')
print(f'Version: {version}')
if version != 2:
sys.exit('ERROR: Cowardly refusing to read version other than 2')
if usage == 0:
print('Usage: Hue adjustment')
else:
print('Usage: Colorization')
sys.exit(f'ERROR: Cowardly refusing to apply colorization rather than Hue adjustment')
MasterHue, MasterSaturation, MasterLightness = unpack('>HHH', f.read(6))
print(f'Master Hue: {MasterHue}')
print(f'Master Saturation: {MasterSaturation}')
print(f'Master Lightness: {MasterLightness}')
# There follow 6 hextants, each with 4 range values and 3 settings values
for h in range(6):
ranges = unpack('>HHHH',f.read(8))
settings = unpack('>HHH', f.read(6))
print(f'Hextant: {h}, ranges: {ranges}, settings: {settings}')
################################################################################
# main
################################################################################
if __name__ == '__main__':
if len(sys.argv) not in set([2,3]):
print('Usage: {sys.argv[0]} filter.[acv|ahu|alv|acf] [image]', file=sys.stderr)
sys.exit(1)
if len(sys.argv) == 2:
loadFilter(sys.argv[1])
Note also that you can apply a Photoshop Curves (ACV) file using ffmpeg
like this:
ffmpeg -i input.jpg -vf curves=psfile="curves.acv" output.jpg
Upvotes: 5
Reputation: 189
You should refer to, for instance, the OpenCV website (you can learn a lot of interesting methods and have a lot of examples on CV problems), here you have some documentation to check:
I hope it's helpful.
Upvotes: 2