Highlight the changes or areas of discrepancy between the two images

140 Views Asked by At

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

2

There are 2 best solutions below

2
Christoph Rackwitz On BEST ANSWER

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

0
pippo1980 On

My attempt using 1.jpg and 2.jpg as input files, so I had to trim your code concerning the convert pdf to image , and I added two funcs I copied somewhere here on SO to better visualize the results on my enviroment , code :

# from pdf2image import convert_from_path
import cv2
import numpy as np
from PIL import Image, ImageOps
# from IPython.display import display
import matplotlib.pyplot as plt

def ResizeWithAspectRatio(image, width=None, height=None, inter=cv2.INTER_AREA):
    dim = None
    (h, w) = image.shape[:2]

    if width is None and height is None:
        return image
    if width is None:
        r = height / float(h)
        dim = (int(w * r), height)
    else:
        r = width / float(w)
        dim = (width, int(h * r))

    return cv2.resize(image, dim, interpolation=inter)


draw_windows = True  ## change fo False for no windows only calc


def drawWindow(window_name, image):
    if draw_windows:
        
        resize = ResizeWithAspectRatio(image, width= 500)
        
        cv2.imshow(window_name, resize)
        
        cv2.moveWindow(window_name, 600, 200)
        
        cv2.waitKey(0)
        cv2.destroyAllWindows()

# 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

##READ IMAGE 1
image1 = cv2.imread('1.jpg', cv2.IMREAD_UNCHANGED)


image1_gray = image1.copy()


## https://stackoverflow.com/questions/39058177/how-to-change-the-black-color-to-red-with-opencv-python
ret, mask = cv2.threshold(image1_gray, 0, 255, cv2.THRESH_BINARY_INV |cv2.THRESH_OTSU)

image1 = cv2.cvtColor(image1, cv2.COLOR_GRAY2RGB)

image1_copy = image1.copy()

print('mask : ', mask.shape , mask.size, mask.ndim , np.min(mask) , np.max(mask), len(np.unique(mask)))

image1[mask == 255] = [0, 0, 255]

drawWindow('image1', image1)

cv2.imwrite('image1_red.png' , image1)

print('image1 : ', image1.shape , image1.size, image1.ndim , np.min(image1) 
                  
                          , np.max(image1), len(np.unique(image1)))



##READ IMAGE 2
image2 = cv2.imread('2.jpg', cv2.IMREAD_UNCHANGED)

image2_gray = image2.copy()

ret, mask = cv2.threshold(image2_gray, 0, 255,cv2.THRESH_BINARY_INV |cv2.THRESH_OTSU)

image2 = cv2.cvtColor(image2, cv2.COLOR_GRAY2RGB)

image2_copy = image2.copy()

image2[mask == 255] = [255, 0, 0]

drawWindow('image2', image2)

cv2.imwrite('image2_blue.png' , image2)


if image1.shape == image2.shape:
    
    overlay = cv2.addWeighted(image1, 0.5, image2, 0.5, 0) 
       
difference = overlay


print('difference : ', difference.shape , difference.size, difference.ndim ,
      
                  np.min(difference) , np.max(difference), len(np.unique(difference)))


drawWindow('difference', difference)


cv2.imwrite('difference.png' , difference)


difference2 = np.where((image1_copy == image2_copy) , image1_copy , difference)


print('difference2 : ', difference2.shape , difference2.size, difference2.ndim ,
      
                  np.min(difference2) , np.max(difference2), len(np.unique(difference2)))


drawWindow('difference2', difference2)


cv2.imwrite('difference2.png' , difference2)



diffs = difference2 - difference

print('diffs : ', diffs.shape , diffs.size, diffs.ndim ,
      
                  np.min(diffs) , np.max(diffs), len(np.unique(diffs)))


drawWindow('diffs', diffs)


cv2.imwrite('diffs.png' , diffs)


plt.imshow(difference2)
plt.axis('off')
plt.show()

have a look at difference.png :

enter image description here

and difference2.png see the upper corner at pixel magnification to get differences :

enter image description here

last just differences between difference.png and difference2.png ; diffs.png

adding this two lines instead of diffs = difference2 - difference

zero = np.ones((difference.shape[0],difference.shape[1],difference.shape[2])).astype('uint8')*255

diffs = np.where(difference2 == difference , zero , difference2)

would have got a better looking picture with white background :

enter image description here

Wasnt able to get exactly your output , something is eluding my logic, but this is my best effort, let us know if you find a better faster solution. I capitalized on How to change black color to Red with OpenCV Python? to get the red and blue images, don't know if there is a way to find a better solution that could ease the identification of just the different parts in the output images where my :

difference2 = np.where((image1_copy == image2_copy) , image1_copy , difference)

fails to accomplish the expected result