4

Goal

I have hundreds of images that all look similar to this one here:

Source photo I simply want to use the green screen to create a mask for each image that looks like this one here (the border should preferably be smoothed out a little bit):

mask

Here is the original image if you want to do tests: https://mega.nz/#!0YJnzAJR!GRYI4oNWcsKztHGoK7e4uIv_GvXBjMvyry7cPmyRpRA


What I've tried

I found this post where the user used Imagemagick to achieve chroma keying.

for i in *; do convert $i -colorspace HSV -separate +channel \
  \( -clone 0 -background none -fuzz 3% +transparent grey43 \) \
  \( -clone 1 -background none -fuzz 10% -transparent grey100 \) \
  -delete 0,1 -alpha extract -compose Multiply -composite \
  -negate mask_$i; done;

But no matter how I tweak the numbers, the results are not perfect: result


I feel really dumb, that I cannot find a solution to such a simple problem myself. Also note, that I am using Linux. So no Photoshop or After Effects! :)

But I am sure that there has to be a solution to this problem.

Update 1

I've just tried using this greenscreen script by fmw42 by running ./greenscreen infile.jpg outfile.png and I am rather satisfied with the result. But it takes around 40 seconds to process one image which results in a total 8 hours for all my images (although I have a rather power workstation, see specs below) Maybe this has something to do witch those errors that occur while processing?:

convert-im6.q16: width or height exceeds limit `black' @ error/cache.c/OpenPixelCache/3911.
convert-im6.q16: ImageSequenceRequired `-composite' @ error/mogrify.c/MogrifyImageList/7995.
convert-im6.q16: no images defined `./GREENSCREEN.6799/lut.png' @ error/convert.c/ConvertImageCommand/3258.
convert-im6.q16: unable to open image `./GREENSCREEN.6799/lut.png': No such file or directory @ error/blob.c/OpenBlob/2874.
convert-im6.q16: ImageSequenceRequired `-clut' @ error/mogrify.c/MogrifyImageList/7870.
convert-im6.q16: profile 'icc': 'RGB ': RGB color space not permitted on grayscale PNG `mask.png' @ warning/png.c/MagickPNGWarningHandler/1667.

Workstation specs

  • Memory: 125,8 GiB
  • Processor: AMD® Ryzen 9 3900x 12-core processor × 24
  • Graphics: GeForce GTX 970/PCIe/SSE2 (two of them)
Florian Ludewig
  • 4,338
  • 11
  • 71
  • 137
  • Have you tried this: http://www.fmwconcepts.com/imagemagick/greenscreen/index.php? If this is for a commercial project you will need to pay for using it though. – dlemstra Mar 02 '20 at 22:23
  • Yeah it works quite good, but it takes too long to process images (see my updated question) – Florian Ludewig Mar 03 '20 at 07:23
  • With regard to your error messages from my script, what is your ImageMagick version? – fmw42 Mar 03 '20 at 17:55

3 Answers3

5

We know that the background is green and is distinguishable from the object by its color, so I suggest using color thresholding. For this, I have written a simple OpenCV Python code to demonstrate the results.

First, we need to install OpenCV.

sudo apt update
pip3 install opencv-python
# verify installation
python3 -c "import cv2; print(cv2.__version__)"

Then, we create a script named skull.py in the same directory with the images.

import cv2
import numpy as np

def show_result(winname, img, wait_time):
    scale = 0.2
    disp_img = cv2.resize(img, None, fx=scale, fy=scale)
    cv2.imshow(winname, disp_img)
    cv2.waitKey(wait_time)

img = cv2.imread('skull.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# define range of green color in HSV
lower_green = np.array([70, 200, 100])
upper_green = np.array([90, 255, 255])
# Threshold the HSV image to extract green color
mask = cv2.inRange(hsv, lower_green, upper_green)
mask = cv2.bitwise_not(mask)

#cv2.imwrite('mask.png', mask)
show_result('mask', mask, 0)
cv2.destroyAllWindows()

You can easily find a tutorial about HSV color operations using OpenCV. I will not go over the functions used here, but one part is important. Image operations are generally done in RGB color space, which holds red, green and blue components. However, HSV is more like human vision system which holds hue, saturation and value components. You can find the conversion here. Since we seperate color based on our perception, HSV is more suitable for this task.

The essential part is to choose the threshold values appropriately. I chose by inspection around 80 for hue (which is max. 180), and above 200 and 100 for saturation and value (max. 255), respectively. You can print the values of a particular pixel by the following lines:

rows,cols,channels = hsv.shape
print(hsv[row, column])

Note that the origin is left upper corner.

Here is the result: mask.png

Two things may be needed. One is doing the operation for a set of images, which is trivial using for loops. The other is that if you do not like some portion of the result, you may want to know the pixel location and change the threshold accordingly. This is possible using mouse events.

for i in range(1, 100):
    img = imread(str(i) + '.jpg')
def mouse_callback(event, x, y, flags, params):
    if event == cv2.EVENT_LBUTTONDOWN:
        row = y
        column = x
        print(row, column)

winname = 'img'
cv2.namedWindow(winname)
cv2.setMouseCallback(winname, mouse_callback)

Keep in mind that show_result function resizes the image by scale factor.

If you do not want to deal with pixel positions, rather you want smooth results, you can apply morphological transformations. Especially opening and closing will get the work done.

kernel = np.ones((11,11), np.uint8)
opened = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
closed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

Result with opening (kernel=11x11): opened.png

Burak
  • 2,251
  • 1
  • 16
  • 33
3

I can't really fit this in a comment, so I've put it as an answer. If you want to use Fred's greenscreen script, you can hopefully use GNU Parallel to speed it up.

Say you use the commands:

mkdir out
greenscreen image.png out/image.png

to process one image, and you have thousands, you can do the following to keep all your CPU cores busy in parallel till they are all processed:

mkdir out
parallel greenscreen {} out/{} ::: *.png
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • definitely the way to go for using this script! but I prefer to use the solution by @Burak as it is much faster and the code a lot easier. Thank you anyways! – Florian Ludewig Mar 09 '20 at 19:55
2

If on a Unix-like system, you can try my greenscreen script that makes calls to ImageMagick and is written in Bash Unix. For example:

Input:

enter image description here

greenscreen img.jpg result.png

Result (green turned transparent):

enter image description here

The result has been reduced in size by 50%, just so that StackOverflow will not object to the original result being too large. However, StackOverflow has changed the image from transparent PNG to white background JPG.

Note that other images, may need values for the arguments other than the defaults. You can get my script at http://www.fmwconcepts.com/imagemagick/. Note that for commercial use, you will need to contact me about licensing.

fmw42
  • 46,825
  • 10
  • 62
  • 80
  • That is really good! But unfortunately this is not feasible for me because processing one image took 40 seconds, which means that doing this for all the images will take around 8 hours (although I have a rather powerful workstation) – Florian Ludewig Mar 03 '20 at 07:14
  • Maybe this has something to so with errors that occur while processing?(see my updated question) – Florian Ludewig Mar 03 '20 at 07:19
  • What is your ImageMagick version? It took only 18 sec on my Mac Mini. – fmw42 Mar 03 '20 at 17:57
  • when I run `convert -version` I get this output: `Version: ImageMagick 6.9.10-23 Q16 x86_64 20190101`... I'll probably need to update :) – Florian Ludewig Mar 03 '20 at 18:06
  • But when I run `sudo apt install imagemagick` it tells me: `imagemagick is already the newest version (8:6.9.10.23+dfsg-2.1ubuntu3.1).` – Florian Ludewig Mar 03 '20 at 18:09
  • I was running IM 6.9.10.97 Q16 Mac OSX. I tested on IM 6.9.10.23 and it took 19 sec. My Mac has only two cores. Do you have multiple cores? If so, do you have OpenMP enabled? It would be listed when you do `convert -version` – fmw42 Mar 03 '20 at 18:12
  • Yeah I have many cores. When I run `convert -version` it also tells me: `Features: Cipher DPC Modules OpenMP` – Florian Ludewig Mar 03 '20 at 18:13
  • But it seems that only 1 or 2 cores are working while converting – Florian Ludewig Mar 03 '20 at 18:16
  • try `MAGICK_THREAD_LIMIT=1 greenscreen img.jpg result.png` Does it run any faster? – fmw42 Mar 03 '20 at 18:17
  • Then the only other way I can think to make improvement is for you to limit your threads to 1 and run one image through greenscreen on each separate thread. Each core will process a different image at the same time up to the number of cores that you have. – fmw42 Mar 03 '20 at 18:26
  • Yeah that makes total sense, but honestly I have no idea how to do this without opening 24 terminal windows :D (especially I would need to prevent duplicate processing) – Florian Ludewig Mar 03 '20 at 18:31
  • I am no expert, but if you limit the threads to 1 and create 1 folder for each core dividing your input images into those folders, then you can create a script to loop over each image in each folder and run greenscreen on it. Launch the script for each folder. That can be scripted also. So one script launches a loop for each directory. Since you have limited the threads to 1, each loop should use 1 core each. – fmw42 Mar 03 '20 at 18:35
  • I will try that! But honestly there has to be a better way (I mean OBS is capable of streaming 60 fps while masking green-screen) But I really appreciate your efforts, thanks! And it definitely is a temporary solution – Florian Ludewig Mar 03 '20 at 18:39
  • ImageMagick is not optimally efficient for large images. But you can try to change your policy.xml file to enable more RAM if you have it. See policy.xml at https://imagemagick.org/script/resources.php. Unfortunately, your images are bit large and my script does a lot of processing. – fmw42 Mar 03 '20 at 18:52