Search code examples
pythonopencvphotoshop

Just keep color range area in images using python


I have high number of JPG images in specific folder that I want to keep only #c7d296 color in my images and fill all other remaining areas in images with white color.
for this I can't use Photoshop because I have high number of JPG images and it get me a lot of time! (about 29000 JPG images).
for this I should use color range tool in python script.

my images are like following sample:
enter image description here

I wrote following script for this process:

import cv2
import os
import numpy as np
import keyboard

def keep_color_only(input_file, output_directory, color_range, fuzziness):
    try:
        # Read the input image
        img = cv2.imread(input_file)

        # Convert image to HSV color space
        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

        # Define lower and upper bounds for the color range
        lower_color = np.array(color_range[0])
        upper_color = np.array(color_range[1])

        # Threshold the HSV image to get only desired colors
        mask = cv2.inRange(hsv, lower_color, upper_color)

        # Invert the mask
        mask_inv = cv2.bitwise_not(mask)

        # Create a white background image
        white_background = np.full_like(img, (255, 255, 255), dtype=np.uint8)

        # Combine the original image with the white background using the mask
        result = cv2.bitwise_and(img, img, mask=mask)
        result = cv2.bitwise_or(result, white_background, mask=mask_inv)

        # Output file path
        output_file = os.path.join(output_directory, os.path.basename(input_file))

        # Save the resulting image
        cv2.imwrite(output_file, result)

    except Exception as e:
        print(f"Error processing {input_file}: {str(e)}")

def process_images(input_directory, output_directory, color_range, fuzziness):
    # Create output directory if it doesn't exist
    if not os.path.exists(output_directory):
        os.makedirs(output_directory)

    # Process each JPG file in the input directory
    for filename in os.listdir(input_directory):
        if filename.lower().endswith('.jpg'):
            input_file = os.path.join(input_directory, filename)
            keep_color_only(input_file, output_directory, color_range, fuzziness)

            # Check for 'F7' key press to stop the process
            if keyboard.is_pressed('F7'):
                print("Process stopped by user.")
                return

def main():
    input_directory = r'E:\Desktop\inf\CROP'
    output_directory = r'E:\Desktop\inf\OUTPUT'

    # Color range in HSV format
    color_range = [(75, 90, 160), (95, 255, 255)]  # Lower and upper bounds for HSV color range
    fuzziness = 80

    process_images(input_directory, output_directory, color_range, fuzziness)
    print("Color removal completed.")

if __name__ == "__main__":
    main()

Note that fuzziness of color range must set on 80
script working good but whole of output images fill by white color. this mean output images just have a empty white screen and no any #c7d296 color area keep!

where is my script problem?


Solution

  • I know you tagged with python but this is miles simpler with ImageMagick which can be installed on macOS, Linux and Windows for free.

    So, basically you want a command like this:

    magick 3KPrGAEl.jpg -fill white -fuzz 8% +opaque "#c7d296" result.jpg
    

    That says... "take your input image, and fill with white, anything that is not within 8% of your green colour in the RGB colour cube, save the result as result.jpg"

    enter image description here

    In case anyone is struggling with what 8% means, it works like this... The RGB colour cube has sides of length 256. So its diagonal, I mean the line running from pure black to pure white, has length 256 * √3 and that is considered 100% as it is the longest possible distance between two colours. Anything else is a percentage of that length. So, with this method you are using an ice-cream scoop, to scoop out a ball (or sphere) of the radius you desire (as specified by the fuzz) from the RGB colour cube centred on your #c7d296.


    Unfortunately, JPEG is a really poor choice for blocky, computer-generated images and it blurs colours and details and makes a heck of a mess, leaving artefacts everywhere. If you could get the images as PNG, you would do better.

    Alternatively, you could run a 3x3 median filter to remove small artefacts:

    magick 3KPrGAEl.jpg -fill white -fuzz 8% +opaque '#c7d296'  -statistic median 3 result.png
    

    enter image description here

    If you need to remove larger artefacts, change the 3 to a 5 or 7. Note though that you will start to lose the edges of your yellow-greenish shape if you do that.


    Once you have that command working, you can then adapt it to do all your images, using mogrify. Let's assume you want the output files in a directory called OUTPUT, you can do all your images in one go with:

    mkdir OUTPUT
    magick mogrify -path OUTPUT -fill white -fuzz 8% +opaque "#c7d296" *.jpg
    

    If you want to speed things up, you should be using all those CPU cores you paid Intel so handsomely for. In general, that means using multi-processing. The simplest way to do that without hand-crafting any code is to use GNU Parallel.

    The command would look like this:

    parallel -N 10 'magick mogrify -path OUTPUT -fill white -fuzz 8% +opaque "#c7d296"' ::: *.jpg
    

    If that causes errors with too many files, you would need a pipe:

    find . -name "*.jpg" -print0 | parallel -0 -N 10 'magick mogrify -path OUTPUT -fill white -fuzz 8% +opaque "#c7d296"'
    

    On Windows, you will need to install MSYS2 or WSL to use GNU Parallel.

    Or, if your files are named numerically, you can simply do, say 10, parallel processes by picking filenames. For example, if your files are numbered 0..29000.jpg You could do all the files ending in 0 in one job, all the files ending in 1 in another job and so on, running the jobs in parallel:

    magick mogrify -path OUTPUT -fill white -fuzz 8% +opaque "#c7d296" *0.jpg &
    
    magick mogrify -path OUTPUT -fill white -fuzz 8% +opaque "#c7d296" *1.jpg &
    
    magick mogrify -path OUTPUT -fill white -fuzz 8% +opaque "#c7d296" *2.jpg &
    

    Note you should consider multiprocessing with your Python solution anyway. Example here.