ThumbGen: Adding support for background removal
In this post we will see how to remove the background from a given image. This will be a pretty simple article in fact, but at least it could serve as reference later on how to use this new script command.
Youtube video for this article available at:
Initial setup
Reference utilities:
Reference implementation:
Our implementation is available as part of the NervProj project: https://github.com/roche-emmanuel/nervproj
Adding the rembg package in media_env:
media_env: inherit: default_env packages: - moviepy - Pillow - ffmpeg-python - opencv-python - rembg[gpu] - scipy
Adding initial script function to remove background:
def remove_background(self, in_file, out_file, model_name): """Remove the background from an image file""" # Usage infos: https://github.com/roche-emmanuel/rembg/blob/main/USAGE.md if out_file is None: out_file = self.set_path_extension(in_file, "_nobg.png") logger.info("Removing background from %s...", in_file) input_img = Image.open(in_file) # output_img = remove(input_img) # model_name = "u2net" # model_name = "isnet-general-use" session = new_session(model_name) output_img = remove(input_img, session=session, only_mask=True) output_img.save(out_file) logger.info("Done removing background.") return True
Expanding subject mask
I tried to experiment a bit to add support to extend the extracted area with an arbitrary number of pixels:
if max_dist != min_dist: # Get the mask only: mask = remove(input_img, session=session, only_mask=True).convert("L") logger.info("Retrieved foreground mask.") # Compute the distance to foreground: dist = self.compute_distance_to_foreground(mask) # Convert the input image to RGBA: img = input_img.convert("RGBA") img_arr = np.array(img) dist = np.clip(dist, min_dist, max_dist) alpha = 1.0 - (dist - min_dist) / (max_dist - min_dist) img_arr[:, :, 3] = (255 * alpha).astype(np.uint8) # Convert the numpy array back to image: output_img = Image.fromarray(img_arr)
To activate this we need to specify different values for the –mind
and the –maxd
command line arguments:
nvp rembg --mind 5 --maxd 10
Background color replacement
Added support for background color specification:
def update_bg_color(self, img, bg_color): """Replace the background color""" col = [np.float32(el) / 255.0 for el in bg_color.split(",")] arr = np.array(img).astype(np.float32) / 255.0 colarr = np.zeros_like(arr) alpha = arr[:, :, 3] for i in range(4): colarr[:, :, i] = col[i] arr[:, :, i] = arr[:, :, i] * alpha + colarr[:, :, i] * (1.0 - alpha) arr = (arr * 255.0).astype(np.uint8) return Image.fromarray(arr)
Which can then be used as this:
nvp rembg -i 2thumbs.png --mind 1 --maxd 1 --bgcolor 255,255,255,255
Batch processing and output folder specification
Also added support for output folder specification, and using that for batch processing:
if in_file == "all": # iterate on all image files: cur_dir = self.get_cwd() out_dir = self.get_param("out_dir") self.make_folder(out_dir) all_files = self.get_all_files(cur_dir, recursive=False) exts = [".png", ".jpeg", ".jpg"] for fname in all_files: ext = self.get_path_extension(fname).lower() if ext not in exts: continue # Check if we already have the text file: src_file = self.get_path(cur_dir, fname) # out_file = self.set_path_extension(fname, "_nobg.png") out_file = self.set_path_extension(fname, ".png") out_file = self.get_path(out_dir, out_file) if self.file_exists(out_file): continue # Otherwise we process this file: self.remove_background( src_file, out_file, model, min_dist, max_dist, bg_color, contour_size, contour_color ) return True
Adding support for contour display
Added support to fill only the contour area around the subject with a specific color:
def apply_contour(self, arr, mask, dist, contour_size, contour_color): """Apply a contour around the target object""" col = [np.float32(el) / 255.0 for el in contour_color.split(",")] logger.info("Applying contour of size %f, with color %s", contour_size, contour_color) img_arr = arr.astype(np.float32) / 255.0 # Prepare the result image: res = np.copy(img_arr) # Fill with the contour color: idx = dist <= contour_size alpha = np.array(mask).astype(np.float32) / 255.0 for i in range(4): # Fill with the contour color: res[idx, i] = col[i] # Re-add the subject on top of contour: res[:, :, i] = img_arr[:, :, i] * alpha + res[:, :, i] * (1.0 - alpha) return (res * 255.0).astype(np.uint8)
nvp rembg --ctsize 10 -i sideview.png --mind 7 --maxd 10 --model "u2net_human_seg"
Notes on the different models
They will be stored in ${HOME}/.u2net (and will only be downloaded the first time they are used)
We can specify the model to use on the command line:
nvp rembg --bgcolor 255,255,255,255 --out-dir ../nobg --model "u2net_human_seg"
And tested with different models:
- u2net: general usage
- u2net_human_seg: a bit better for human extraction
- isnet-general-use:
- sam ⇒ doesn't work