A couple of days ago, I was faced with a project that demanded removing the white background of sketches when they’re dropped into a certain folder. This was all happening in a hardware scanner.
Here’s an example of a sketch:
The first step is to install dependencies for this project, which are listed below. We will also be using Python 3.7.
opencv_python==18.104.22.168 pip install opencv-python numpy==1.16.4 pip install numpy
After that, we will begin by importing all the required modules for the project:
import cv2 import os import string import random from os import listdir from os.path import isfile, join, splitext import time import sys import numpy as np import argparse
We then create three different variables: the name of the folder to watch, the name of the folder where the images will end up after being processed, and the polling time when watching the folder (i.e. how frequently it checks for changes in the folder — one second in our case)
watch_folder = ‘toprocess’ processed_folder = ‘processed’ poll_time = 1
The folders ‘toprocess’, and ‘processed’ will be in the same directory of our python script for now.
We will then move to our main function, which will watch our ‘toprocess’ directory, and if any changes happen, will process the image dropped into that folder.
before = dict([(f, None) for f in os.listdir(watch_folder)]) while 1: time.sleep(poll_time) after = dict([(f, None) for f in os.listdir(watch_folder)]) added = [f for f in after if not f in before] removed = [f for f in before if not f in after] if added: print(“Added “, “, “.join(added)) if added is not None: processImage(added) if removed: print(“Removed “, “, “.join(removed)) before = after
This code runs in an infinite loop, until the script is killed. When initiated, it stores the files in the directory inside a dictionary called “before”. Next, the steps in the infinite loop are broken down below in pseudo code:
Now for the
processImage function, which is the heart of the program. This is where the OpenCV background removal magic happens. The code is explained the comments below (Basic OpenCV knowledge is required):
def processImage(fileName): # Load in the image using the typical imread function using our watch_folder path, and the fileName passed in, then set the final output image to our current image for now image = cv2.imread(watch_folder + ‘/’ + fileName) output = image # Set thresholds. Here, we are using the Hue, Saturation, Value color space model. We will be using these values to decide what values to show in the ranges using a minimum and maximum value. THESE VALUES CAN BE PLAYED AROUND FOR DIFFERENT COLORS hMin = 29 # Hue minimum sMin = 30 # Saturation minimum vMin = 0 # Value minimum (Also referred to as brightness) hMax = 179 # Hue maximum sMax = 255 # Saturation maximum vMax = 255 # Value maximum # Set the minimum and max HSV values to display in the output image using numpys' array function. We need the numpy array since OpenCVs' inRange function will use those. lower = np.array([hMin, sMin, vMin]) upper = np.array([hMax, sMax, vMax]) # Create HSV Image and threshold it into the proper range. hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # Converting color space from BGR to HSV mask = cv2.inRange(hsv, lower, upper) # Create a mask based on the lower and upper range, using the new HSV image # Create the output image, using the mask created above. This will perform the removal of all unneeded colors, but will keep a black background. output = cv2.bitwise_and(image, image, mask=mask) # Add an alpha channel, and update the output image variable *_, alpha = cv2.split(output) dst = cv2.merge((output, alpha)) output = dst # Resize the image to 512, 512 (This can be put into a variable for more flexibility), and update the output image variable. dim = (512, 512) output = cv2.resize(output, dim) # Generate a random file name using a mini helper function called randomString to write the image data to, and then save it in the processed_folder path, using the generated filename. file_name = randomString(5) + ‘.png’ cv2.imwrite(processed_folder + ‘/’ + file_name, output)
That was a pretty straightforward function and it does the job properly. Again, playing around with the thresholds can give even better results. One last thing we need to discuss is the mini helper function that generates a random string for the filename.
def randomString(length): letters = string.ascii_lowercase return ‘’.join(random.choice(letters) for i in range(length))
This is a simple function. It gets letters using the “string” library, then joins a random choice of characters based on the length you passed in. Passing in a length of 5 will generate a string of 5 characters.
The result of the processing is shown below.
Of course, this result can be improved by playing around with the values and having better quality scans.
Thank you for sticking to the end of this piece. I hope it helps you in your upcoming projects.