简体   繁体   中英

OpenCV - Splitting and merging alpha channels slow

I am using Python OpenCV to split channels and remove black background like this...

    b_channel, g_channel, r_channel = cv2.split(image_1)
    alpha_channel = np.zeros_like(gray)

    for p in range(alpha_channel.shape[0]):
        for q in range(alpha_channel.shape[1]):
            if b_channel[p][q]!=0 or g_channel[p][q]!=0 or r_channel[p][q]!=0:
                alpha_channel[p][q] = 255

    merged = cv2.merge((b_channel, g_channel, r_channel, alpha_channel))

This is working, but it is taking around 10 seconds to complete on an image that is only 200kb

Is there a more efficient way to do this or is there some speed gains I could make using the code I have?

Iterating over pixels using for loop is literally very slow and inefficient. Also, as per the documentation here ,

cv2.split() is a costly operation (in terms of time). So do it only if you need it. Otherwise go for Numpy indexing.

You can try vectorising and indexing with numpy as below:

# create the image with alpha channel
img_rgba = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)

# mask: elements are True any of the pixel value is 0         
mask = (img[:, :, 0:3] != [0,0,0]).any(2) 
#assign the mask to the last channel of the image
img_rgba[:,:,3]  = (mask*255).astype(np.uint8)

For what you're doing, using cv2.bitwise_or seems to be the fastest method:

image_1 = img
# your method
start_time = time.time()
b_channel, g_channel, r_channel = cv2.split(image_1)
alpha_channel = np.zeros_like(gray)
for p in range(alpha_channel.shape[0]):
    for q in range(alpha_channel.shape[1]):
        if b_channel[p][q]!=0 or g_channel[p][q]!=0 or r_channel[p][q]!=0:
            alpha_channel[p][q] = 255
elapsed_time = time.time() - start_time
print('for cycles:  ' + str(elapsed_time*1000.0) + ' milliseconds')

# my method
start_time = time.time()
b_channel, g_channel, r_channel = cv2.split(image_1)
alpha_channel2 = cv2.bitwise_or(g_channel,r_channel)
alpha_channel2 =  cv2.bitwise_or(alpha_channel2, b_channel)
_,alpha_channel2 = cv2.threshold(alpha_channel2,0,255,cv2.THRESH_BINARY)
elapsed_time2 = time.time() - start_time
print('bitwise + threshold:  '+ str(elapsed_time2*1000.0) + ' milliseconds')

# annubhav's method
start_time = time.time()
img_rgba = cv2.cvtColor(image_1, cv2.COLOR_RGB2RGBA)
# mask: elements are True any of the pixel value is 0         
mask = (img[:, :, 0:3] != [0,0,0]).any(2) 
#assign the mask to the last channel of the image
img_rgba[:,:,3]  = (mask*255).astype(np.uint8)
elapsed_time3 = time.time() - start_time
print('anubhav:  ' + str(elapsed_time3*1000.0) + ' milliseconds')

for cycles: 2146.300792694092 milliseconds

bitwise + threshold: 4.959583282470703 milliseconds

anubhav: 27.924776077270508 milliseconds

Fastest Solution

Let us consider a function that uses cv2.split and we know that it is very inefficient, we can go ahead and resize or crop a certain part of the image and then perform our calculation on that. In my case where I had to calculate the colorfulness of the image using cv2.split I went ahead and resized and cropped the image to make cv2.split work.

  • A faster and more reasonable cv2.split calculation can be performed by Resizing

Code

def image_colorfulness(self,image):
        # split the image into its respective RGB components
        (B, G, R) = cv2.split(image.astype("float"))
        print(f'Split Image to B G R {(B, G, R)}')
        # compute rg = R - G
        rg = np.absolute(R - G)
        print(f'Computed RG to {rg}')
        # compute yb = 0.5 * (R + G) - B
        yb = np.absolute(0.5 * (R + G) - B)
        # compute the mean and standard deviation of both `rg` and `yb`
        print('Performing Absolute')
        (rbMean, rbStd) = (np.mean(rg), np.std(rg))
        (ybMean, ybStd) = (np.mean(yb), np.std(yb))
        # combine the mean and standard deviations
        print('Performing Standard Deviation')
        stdRoot = np.sqrt((rbStd ** 2) + (ybStd ** 2))
        meanRoot = np.sqrt((rbMean ** 2) + (ybMean ** 2))
        # derive the "colorfulness" metric and return it
        return stdRoot + (0.3 * meanRoot)


def crop_square(self, img, size, interpolation=cv2.INTER_AREA):
        h, w = img.shape[:2]
        min_size = np.amin([h,w])

        # Centralize and crop
        crop_img = img[int(h/2-min_size/2):int(h/2+min_size/2), int(w/2-min_size/2):int(w/2+min_size/2)]
        resized = cv2.resize(crop_img, (size, size), interpolation=interpolation)

        return resized


img = cv2.imread(image_path)
resize_img = self.crop_square(img, 300)
## perform your calculation on the resized_img and continue with the original img then 
colorness =  self.image_colorfulness(resize_img)

Resizing Only

If you prefer not to crop and only resize the image, that can be achieved by taking a look at this line of code from the square_crop function.

resized = cv2.resize(crop_img, (size, size), interpolation=interpolation)

Testing Results

Before

  • I tested a 5.0 MB *.PNG Image, before using standard image input in cv2.split it processed in 8 Minutes .

After

After the Image Resizing it was reduced to 0.001 ms on the resized image.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM