Search code examples
pythonimageopencvimage-processingimage-editing

Highlight the changes or areas of discrepancy between the two images


image1

image2

These are my images, I want to

  • Convert the PDF documents (file_1.pdf and file_2.pdf) into images.
  • Compare the images to detect any differences between them.
  • Highlight the changes or areas of discrepancy between the two images.

This is my code

from pdf2image import convert_from_path
import cv2
import numpy as np
from PIL import Image, ImageOps
from IPython.display import display`

def process_and_display_image(pdf_path, target_size=(800, 600),save_path='processed_image.jpeg'):
    images = convert_from_path(pdf_path)
    image = images[0]
    image = ImageOps.exif_transpose(image)
    image.thumbnail(target_size, Image.Resampling.LANCZOS)
    image_np = np.array(image)
    image_np = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY)
    image_processed = Image.fromarray(image_np)
    display(image_processed)
    image_processed.save(save_path, 'JPEG')
    print(f"Image saved as {save_path}")

# Display image from PDF
process_and_display_image("file_1.pdf",save_path='file_1.jpeg')
process_and_display_image("file_2.pdf",save_path='file_2.jpeg')

import matplotlib.pyplot as plt
image1 = cv2.imread('file_1.jpeg', cv2.IMREAD_UNCHANGED)
image1 = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)
image2 = cv2.imread('file_2.jpeg', cv2.IMREAD_UNCHANGED)
image2 = cv2.cvtColor(image2, cv2.COLOR_BGR2RGB)

if image1.shape == image2.shape:
    difference = cv2.absdiff(image1,image2)
difference = 255 - difference

if image1.shape == image2.shape:
    
    overlay = cv2.addWeighted(image1, 0.5, image2, 0.5, 0) 
       
difference = overlay
plt.imshow(difference)
plt.axis('off')
plt.show()

My output is -> My_output

Expected output -> Expected output


Solution

  • I'd like to present some simple operations to get exactly the colors you want, without thresholding.

    im1 and im2 (grayscale):

    im1 im2

    # assuming white sheet of paper, ink is the inverse
    # positive = added
    # negative = deleted
    signed_difference = (255 - im2).astype(np.int16) - (255 - im1).astype(np.int16)
    

    signed_difference

    (white = added, black = removed. If you need to look at it, imshow("window", signed_difference / (255*2) + 0.5))

    Starting from a version that contains only "ink" that's in both pictures, I'll add colored "ink" for the additions and deletions. The reshape and expand_dims stuff helps numpy do the right thing.

    canvas = np.maximum(im1, im2) # same ink in both images
    canvas = cv.cvtColor(canvas, cv.COLOR_GRAY2BGR)
    
    # add colored ink: subtract the inverse of those colors to "ink" the page
    
    add_color = np.array([255, 128, 0]).reshape((1, 1, 3)) # additions blue
    del_color = np.array([0, 0, 255]).reshape((1, 1, 3)) # deletions red
    
    strength = np.expand_dims(np.abs(signed_difference) / 255, axis=2)
    
    # those are just for knowing *what pixels* are affected at all.
    # results are all grayscale, no thresholding.
    add_mask = np.expand_dims(signed_difference > 0, axis=2)
    del_mask = np.expand_dims(signed_difference < 0, axis=2)
    
    canvas = np.where(add_mask, canvas - (255 - add_color) * strength, canvas)
    canvas = np.where(del_mask, canvas - (255 - del_color) * strength, canvas)
    canvas = np.clip(canvas, 0, 255).astype(np.uint8)
    

    canvas

    And that's it.


    You'll want to make sure your pictures are aligned well, even sub-pixel accurately. You can use findTransformECC() for sub-pixel refinement.

    The worse the initial alignment, the more blurring (gaussFiltSize) you'll need. All kinds of "fixed borders" will ruin this process. Make sure to only feed the "art" in, not any framing.

    The core of that operation:

    H = np.eye(3).astype(np.float32)
    
    (rv, H) = cv.findTransformECC(
        templateImage=im1,
        inputImage=im2,
        warpMatrix=H[:2],
        motionType=cv.MOTION_AFFINE,
        criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 20, -1), # -1 to ignore eps and use all iterations
        inputMask=None,
        gaussFiltSize=5
    )
    
    im2_aligned = cv.warpAffine(im2, H, (im1.shape[1], im1.shape[0]), flags=cv.INTER_CUBIC + cv.WARP_INVERSE_MAP)
    

    And this is what your input looks like, aligned and colored, without borders:

    enter image description here


    You'll notice that it also colors gray-to-gray differences. Your reference picture does not show that. Your reference picture was probably made by the original CAD program. The CAD program is aware of the geometry and colors it according to flexible styles. I can only work on the image data. Sure, it's possible to exclude "gray" from the coloring but then you'll also see the gray pixels near black pixels (fuzzy lines) not-colored.

    Here's a complete gray-to-gray display:

    im1 im2

    signed_difference result