pythonpython-imaging-libraryimage-augmentation

How to remove part of a PNG image in Python to make it Opaque


I have a script that is creating occlusions on PNG images. The data augmentation occlusion technique from https://arxiv.org/pdf/2001.04086.pdf.

The script runs works well for creating occlusions but they are drawn over the PNG as black rectangles.

What I would like them to do is cut these rectangles out of the PNG and make them part of the alpha layer of the PNG so that when pasted on top of a background, the background shows through the rectangles. Essentially make the black rectangles transparent.

Current implemenation output looks like this:

enter image description here

The complete script below, but area that needs work here:

    # Draw each box onto the image
    for index, row in boxes.iterrows():
        shape = [(row['topleftcornerxpx'], row['topleftcornerypx']), (row['topleftcornerxpx'] + row['sizepx'], row['topleftcornerypx'] + row['sizepx'])]
        img1 = ImageDraw.Draw(img)
        img1.rectangle(shape, fill = "black")

Have tried adding an alpha mask, but this removes the alpha channel from the background and keeps the rectangles black.

    # Draw each box onto the image
    for index, row in boxes.iterrows():
        shape = [(row['topleftcornerxpx'], row['topleftcornerypx']), (row['topleftcornerxpx'] + row['sizepx'], row['topleftcornerypx'] + row['sizepx'])]
        mask = Image.new('L', img.size, color = 255)
        draw=ImageDraw.Draw(mask)
        img1 = ImageDraw.Draw(img)
        img1.rectangle(shape, fill=0)
        img.putalpha(mask)

Complete script (someone might like it):

import os
from pandas import DataFrame
from PIL import Image, ImageDraw
import numpy as np

#set directories
directory = str("C:/GIT/Temp/Test/test/")
target_directory = str("C:/GIT/Temp/Test/test/occluded/")
occlusion_scales = [.20, .125, .08] #Percent of image the occlusions will cover. Add or remove as many as required.  
image_padding = int(5)

existing_files = os.listdir(target_directory)
#print(existing_files)

#Get files
for filename in os.listdir(directory):
  if filename.endswith('.png'):
      # Process for each occlusion scale in the list. 
      for scales in occlusion_scales:
        img = Image.open(directory + filename)
        # Get image dimensions
        imgwidth, imgheight = img.size
        # Get smallest value out of x & y to scale box size by. 
        box1sizepx = round(min(imgwidth,imgheight) * scales)
        print(filename)
        
        #Dont process files already processed, can comment out for replace. 
        if (filename.replace('.png','') + '_occluded_' + str(scales)+'.png') not in existing_files: 
            #print(filename + ' not in list')
            
            # Calculate number of boxes accross and down required
            boxesaccross = round((imgwidth/2) / box1sizepx)
            boxesdown = round((imgheight/2) / box1sizepx)

            # Create dataframe for boxes 
            boxes = DataFrame(columns=['sizepx','topleftcornerxpx','topleftcornerypx'])
            # Set row counter for loop.
            boxrow = 0

            #Draw a box for each row and within that each column
            while boxesdown >= 1:
                boxesdown = boxesdown -1
                boxcolumn = 0

                while boxesaccross >= 1: 
                    boxesaccross = boxesaccross -1
                    new_box = {'sizepx':box1sizepx,'topleftcornerxpx':round(box1sizepx*.8) + (box1sizepx * boxcolumn),'topleftcornerypx':round(box1sizepx*.8) + (box1sizepx * boxrow)}
                    boxes = boxes.append(new_box, ignore_index=True)
                    boxcolumn = boxcolumn + 2

                boxrow = boxrow + 2
                boxesaccross = round((imgwidth/2) / box1sizepx)

            # Draw each box onto the image
            for index, row in boxes.iterrows():
                shape = [(row['topleftcornerxpx'], row['topleftcornerypx']), (row['topleftcornerxpx'] + row['sizepx'], row['topleftcornerypx'] + row['sizepx'])]
                img1 = ImageDraw.Draw(img)
                img1.rectangle(shape, fill = "black")

            #Save the image 
            print(target_directory + filename.replace('.png','') + '_occluded_' + str(scales)+'.png')

            #Crop the image with some padding
            cropped_object = img.crop(((0 - image_padding), (0 - image_padding), (imgwidth + image_padding), (imgheight + image_padding)))
            cropped_object.save(target_directory + filename.replace('.png','') + '_occluded_' + str(scales)+'.png')

Solution

  • Solved thanks @furas, set fill to 0 not "black". Updated script if anyone wants it.

    import os
    from pandas import DataFrame
    from PIL import Image, ImageDraw
    import numpy as np
    
    #set directories
    directory = str("C:/GIT/Temp/Test/test/")
    target_directory = str("C:/GIT/Temp/Test/test/occluded/")
    occlusion_scales = [.20, .125, .08] #Percent of image the occlusions will cover. Add or remove as many as required.  
    image_padding = int(5)
    
    existing_files = os.listdir(target_directory)
    #print(existing_files)
    
    #Get files
    for filename in os.listdir(directory):
      if filename.endswith('.png'):
          # Process for each occlusion scale in the list. 
          #pixdata = filename.load()
          #print(pixdata)
          for scales in occlusion_scales:
            img = Image.open(directory + filename)
            # Get image dimensions
            imgwidth, imgheight = img.size
            # Get smallest value out of x & y to scale box size by. 
            box1sizepx = round(min(imgwidth,imgheight) * scales)
            print(filename)
            
            #Dont process files already processed, can comment out for replace. 
            if (filename.replace('.png','') + '_occluded_' + str(scales)+'.png') not in existing_files: 
                #print(filename + ' not in list')
                
                # Calculate number of boxes accross and down required
                boxesaccross = round((imgwidth/2) / box1sizepx)
                boxesdown = round((imgheight/2) / box1sizepx)
    
                # Create dataframe for boxes 
                boxes = DataFrame(columns=['sizepx','topleftcornerxpx','topleftcornerypx'])
                # Set row counter for loop.
                boxrow = 0
    
                #Draw a box for each row and within that each column
                while boxesdown >= 1:
                    boxesdown = boxesdown -1
                    boxcolumn = 0
    
                    while boxesaccross >= 1: 
                        boxesaccross = boxesaccross -1
                        new_box = {'sizepx':box1sizepx,'topleftcornerxpx':round(box1sizepx*.8) + (box1sizepx * boxcolumn),'topleftcornerypx':round(box1sizepx*.8) + (box1sizepx * boxrow)}
                        boxes = boxes.append(new_box, ignore_index=True)
                        boxcolumn = boxcolumn + 2
    
                    boxrow = boxrow + 2
                    boxesaccross = round((imgwidth/2) / box1sizepx)
    
                # Draw each box onto the image
                for index, row in boxes.iterrows():
                    shape = [(row['topleftcornerxpx'], row['topleftcornerypx']), (row['topleftcornerxpx'] + row['sizepx'], row['topleftcornerypx'] + row['sizepx'])]
                    img1 = ImageDraw.Draw(img)
                    img1.rectangle(shape, fill = 0)
    
                #Save the image 
                print(target_directory + filename.replace('.png','') + '_occluded_' + str(scales)+'.png')
    
                #Crop the image with some padding
                cropped_object = img.crop(((0 - image_padding), (0 - image_padding), (imgwidth + image_padding), (imgheight + image_padding)))
                cropped_object.save(target_directory + filename.replace('.png','') + '_occluded_' + str(scales)+'.png')