Search code examples
pythonopencvdata-augmentation

How to augment scanned document image with creases, folds and wrinkles?


I am creating a synthetic dataset to train a model that needs to find documents in an image. the documents will be far from perfect, i.e they were folded, creased and wrinkled crinkled.

I could find a few ways of doing it in photoshop but I was wondering if someone has a better idea of doing this augmentation in opencv without trying to reverse engineer the photoshop process.

for example (from https://www.photoshopessentials.com/photo-effects/folds-creases/): folds to: enter image description here

or crinkles (from https://www.myjanee.com/tuts/crumple/crumple.htm): crinkles


Solution

  • I have tried to put all your distortions together in one script in Python/Opencv.

    Input:

    enter image description here

    Wrinkles:

    enter image description here

    import cv2
    import numpy as np
    import math
    import skimage.exposure
    
    # read desert car image and convert to float in range 0 to 1
    img = cv2.imread('desert_car.png').astype("float32") / 255.0
    hh, ww = img.shape[:2]
    
    # read wrinkle image as grayscale and convert to float in range 0 to 1
    wrinkles = cv2.imread('wrinkles.jpg',0).astype("float32") / 255.0
    
    # resize wrinkles to same size as desert car image
    wrinkles = cv2.resize(wrinkles, (ww,hh), fx=0, fy=0)
    
    # apply linear transform to stretch wrinkles to make shading darker
    #wrinkles = skimage.exposure.rescale_intensity(wrinkles, in_range=(0,1), out_range=(0,1)).astype(np.float32)
    
    # shift image brightness so mean is (near) mid gray
    mean = np.mean(wrinkles)
    shift = mean - 0.4
    wrinkles = cv2.subtract(wrinkles, shift)
    
    # create folds image as diagonal grayscale gradient as float as plus and minus equal amount
    hh1 = math.ceil(hh/2)
    ww1 = math.ceil(ww/3)
    val = math.sqrt(0.2)
    grady = np.linspace(-val, val, hh1, dtype=np.float32)
    gradx = np.linspace(-val, val, ww1, dtype=np.float32)
    grad1 = np.outer(grady, gradx)
    
    # flip grad in different directions
    grad2 = cv2.flip(grad1, 0)
    grad3 = cv2.flip(grad1, 1)
    grad4 = cv2.flip(grad1, -1)
    
    # concatenate to form folds image
    foldx1 = np.hstack([grad1-0.1,grad2,grad3])
    foldx2 = np.hstack([grad2+0.1,grad3,grad1+0.2])
    folds = np.vstack([foldx1,foldx2])
    #folds = (1-val)*folds[0:hh, 0:ww]
    folds = folds[0:hh, 0:ww]
    
    # add the folds image to the wrinkles image
    wrinkle_folds = cv2.add(wrinkles, folds)
    
    # draw creases as blurred lines on black background
    creases = np.full((hh,ww), 0, dtype=np.float32)
    ww2 = 2*ww1
    cv2.line(creases, (0,hh1), (ww-1,hh1), 0.25, 1)
    cv2.line(creases, (ww1,0), (ww1,hh-1),  0.25, 1)
    cv2.line(creases, (ww2,0), (ww2,hh-1),  0.25, 1)
    
    # blur crease image
    creases = cv2.GaussianBlur(creases, (3,3), 0)
    
    # add crease to wrinkles_fold image
    wrinkle_folds_creases = cv2.add(wrinkle_folds, creases)
    
    # threshold wrinkles and invert
    thresh = cv2.threshold(wrinkle_folds_creases,0.7,1,cv2.THRESH_BINARY)[1]
    thresh = cv2.cvtColor(thresh,cv2.COLOR_GRAY2BGR) 
    thresh_inv = 1-thresh
    
    # convert from grayscale to bgr 
    wrinkle_folds_creases = cv2.cvtColor(wrinkle_folds_creases,cv2.COLOR_GRAY2BGR) 
    
    # do hard light composite and convert to uint8 in range 0 to 255
    # see CSS specs at https://www.w3.org/TR/compositing-1/#blendinghardlight
    low = 2.0 * img * wrinkle_folds_creases
    high = 1 - 2.0 * (1-img) * (1-wrinkle_folds_creases)
    result = ( 255 * (low * thresh_inv + high * thresh) ).clip(0, 255).astype(np.uint8)
    
    # save results
    cv2.imwrite('desert_car_wrinkles_adjusted.jpg',(255*wrinkles).clip(0,255).astype(np.uint8))
    cv2.imwrite('desert_car_wrinkles_folds.jpg', (255*wrinkle_folds).clip(0,255).astype(np.uint8))
    cv2.imwrite('wrinkle_folds_creases.jpg', (255*wrinkle_folds_creases).clip(0,255).astype(np.uint8))
    cv2.imwrite('desert_car_result.jpg', result)
    
    # show results
    cv2.imshow('wrinkles', wrinkles)
    cv2.imshow('wrinkle_folds', wrinkle_folds)
    cv2.imshow('wrinkle_folds_creases', wrinkle_folds_creases)
    cv2.imshow('thresh', thresh)
    cv2.imshow('result', result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    Wrinkles adjusted:

    enter image description here

    Wrinkles with folds:

    enter image description here

    Wrinkles with folds and creases:

    enter image description here

    Result:

    enter image description here