How to simply do np.where not includes np.where

207 Views Asked by At

I wrote a program to change the color of the skin in the photo.

First I get a skin mask, then I convert BGR image to HSV. add V channel value in mask. Like this:

hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
H, S, V = cv2.split(hsv)
NV = np.where(skin_mask > 0, V + skin_mask / 255 * 50, V).astype(np.uint8)
NHSV = cv2.cvtColor(cv2.merge([H, S, NV]), cv2.COLOR_HSV2BGR)

But some original white pixel become to black, I think maybe V + skin_mask / 255 * 50 let pixel over 255.

so I try to:

NV = np.where(skin_mask > 0, np.where(V + skin_mask / 255 * 50 > 255, 255, V + skin_mask / 255 * 50), V).astype(np.uint8)

It's work. but ugly.

I want to know how to beautify this writing, don't use np.where includes np.where. Thank you very much!!!

2

There are 2 best solutions below

0
Rotem On BEST ANSWER

It may be more elegant to use skin_mask as a mask, instead of applying arithmetic like skin_mask / 255 * 50.

You may solve it using cv2.add:

NV = V.copy()
cv2.add(NV, np.full_like(V, 50), NV, mask=skin_mask)

Advantages of using cv2.add over NumPy arithmetic:

  • cv2.add supports mask argument (mask element values are usually 0 and 255).
  • cv2.add clips the result to the valid range of uint8 [0, 255], without overflow.

Code I used for testing the solution:

import numpy as np
import cv2

# Build sample skin_mask
skin_mask = np.zeros((100, 80), np.uint8)
skin_mask[30:70, 30:60] = 255

# Build sample V matrix
V = np.full_like(skin_mask, 60)
V[40:80, 40:80] = 220

# Sum using cv2.add
NV = V.copy()
cv2.add(NV, np.full_like(V, 50), NV, mask=skin_mask)

# Sum using NumPy (used as reference for testing).
refNV = np.where(skin_mask > 0, np.minimum(V + skin_mask / 255 * 50, 255), V).astype(np.uint8)  # Reference

if np.any(NV != refNV):
    print('There is a bug: NV != refNV')  # Should not enter here

# Show the images
cv2.imshow('skin_mask', skin_mask)
cv2.imshow('V', V)
cv2.imshow('NV', NV)
cv2.imshow('refNV', refNV)
cv2.waitKey()
cv2.destroyAllWindows()
0
Tawy On

You still need to detect the overflow. A slightly cleaner way consists in using np.minimum or np.clip:

brighter_V = np.minimum(V + skin_mask / 255 * 50, 255)
NV = np.where(skin_mask > 0, brighter_V, V).astype(np.uint8)

The np.clip method is even more generic, in case values can overflow below 0:

brighter_V = np.clip(V + skin_mask / 255 * 50, 0, 255)
NV = np.where(skin_mask > 0, brighter_V, V).astype(np.uint8)

(the break in two lines is just my personal coding preference)