Eslam Said Khashb
Eslam Said Khashb

Reputation: 65

Create a curve which adjust the contrast and brightness respectively of an image that is loaded

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.

enter image description here. enter image description here

Upvotes: 2

Views: 2507

Answers (2)

Mark Setchell
Mark Setchell

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()

enter image description here

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:

enter image description here

into this:

enter image description here

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

emichester
emichester

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

Related Questions