1

Im trying to insert one picture(transparent .png) into another on certain coordinates. While the solution from How to add an image over another image using x,y coordinates? frame[y: y+insert_size[1], x: x+insert_size[0]] = image (where insert_size - width and height of inserted picture) works, i also dont want black pixels(thats how opencv represents transparent pixels) on the final image.

I wrote a function that iterates pixel by pixel, and while it works - it is horribly slow(it completes about 2 image inserts per second), code:

def insert_image(frame, image, insert_coordinates, masked_value):
    img_height = len(image)
    img_width = len(image[0])
    mask = np.ndarray((3,), np.uint8, buffer=np.array(masked_value))
    y_diff = 0 #current vertical position in insert picture
    for y, line in enumerate(frame):
        if y_diff == img_height-1:
            continue #interested until last row
        if y < insert_coordinates[1] or y > insert_coordinates[1]+img_height:
            continue #interested only in rows that will be changed
        else:
            x_diff = 0 #current horizontal position in insert picture
            for x, col in enumerate(line):
                if x_diff == img_width-1:
                    continue #interested until last column
                if x < insert_coordinates[0] or x > insert_coordinates[0]+img_width:
                    continue #interested only in columns that will be changed
                else:
                    if (image[y_diff][x_diff] != mask).all():
                        frame[y][x] = image[y_diff][x_diff]  #setting pixel value if its not of masked value
                    x_diff += 1
        y_diff += 1
    return frame

maybe there is a smarter way to do so? opencv version 4.5.0 numpy version 1.20.0rc1

UPDATE: By "insert" i do mean assign a pixel value from image to some pixel of frame. i added data and code for reproducible example(also modified function so its a bit faster):

  1. "frame" - original picture, that image will be added to to, has red square sized (500,500) at (100,100) coordinates
  2. "image" - transparent .png, sized (500,500) that will be "inserted" into original frame
  3. "result1" - result, where red pixels were replaced with black "transparent" pixels from inserted image
  4. "result2" - desired result

original frame, that image will be "inserted" into arbitrary image, that will be "inserted" into original frame result1 - with black transparent pixels from inserted image result2 - desired result

code, requires opencv-python and numpy modules: example.py

import cv2
import numpy as np
import copy


def insert_image_v2(frame, image, insert_coordinates, masked_value):
    img_height = len(image)
    img_width = len(image[0])
    mask = np.ndarray((3,), np.uint8, buffer=np.array(masked_value))
    y_diff = 0
    for y in range(insert_coordinates[1], insert_coordinates[1]+img_height, 1):
        x_diff = 0
        for x in range(insert_coordinates[0], insert_coordinates[0]+img_width, 1):
            if (image[y_diff][x_diff] != mask).all():
                frame[y][x] = image[y_diff][x_diff]
            x_diff += 1
        y_diff += 1
    return frame


if __name__ == "__main__":
    frame = cv2.imread('frame.png')
    image = cv2.imread('image.png')
    insert_size = (image.shape[0], image.shape[1])
    insert_coordinates = (100, 100)
    x = insert_coordinates[0]
    y = insert_coordinates[1]
    result1 = copy.deepcopy(frame)
    result1[y: y+insert_size[1], x: x+insert_size[0]] = image
    result2 = insert_image_v2(frame, image, insert_coordinates, [0,0,0])
    cv2.imshow('result1', result1)
    cv2.imshow('result2', result2)
    cv2.imwrite('result1.jpg', result1)
    cv2.imwrite('result2.jpg', result2)
    print()
Sergey S
  • 11
  • 3
  • Just to be clear, by `insert` you mean assign a pixel value from `image` to some pixel of `frame`? So the conditional action is `frame[y, x] = image[y_diff, x_diff]`. I ask because sometimes `insert` means to expand an array's size (e.g. `np.insert`). – hpaulj Dec 07 '20 at 03:04
  • 1
    post your data please. I think you're implying some things but I'd rather not guess. also describe the goal in more detail. or better yet, use pictures to show what you mean. – Christoph Rackwitz Dec 07 '20 at 05:44
  • Try to make it a [mcve] – hpaulj Dec 07 '20 at 05:46
  • i added data and code examples, also answered @hpaulj question - by "insert" i do mean assign a pixel value from image to some pixel of frame – Sergey S Dec 07 '20 at 18:40

1 Answers1

0

Found a solution in Image alpha composite with OpenCV, it is about 20 times faster than what i had. code:

import cv2
import numpy as np
import time


def insert_image_v2(frame, image, insert_coordinates):
    x = insert_coordinates[0]
    y = insert_coordinates[1]
    insert_size = (image.shape[1], image.shape[0])
    background = frame[y: y+insert_size[1], x: x+insert_size[0]]
    foreground = image
    kernel = np.ones((5,5), np.uint8)
    image_gray = cv2.cvtColor(foreground, cv2.COLOR_BGR2GRAY)
    ret, mask = cv2.threshold(image_gray, 1, 255, cv2.THRESH_BINARY_INV)
    opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    output = np.zeros(foreground.shape, dtype=foreground.dtype)
    for i in range(3):
        output[:,:,i] = background[:,:,i] * (opening/255)+foreground[:,:,i]*(1-opening/255)
    frame[y: y+insert_size[1], x: x+insert_size[0]] = output
    return frame


if __name__ == "__main__":
    frame = cv2.imread('frame.png')
    image = cv2.imread('image_1.png')
    insert_size = (image.shape[0], image.shape[1])
    insert_coordinates = (100, 100)
    t1 = time.time()
    frame = insert_image_v2(frame, image, insert_coordinates)
    t2 = time.time()
    print(f"{t2-t1}")
    cv2.imshow('img', frame)
    cv2.waitKey(0)
Sergey S
  • 11
  • 3