Sergey S
Sergey S

Reputation: 11

Inserting 2d-array into another 2d-array, while taking values into account

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()

Upvotes: 1

Views: 193

Answers (1)

Sergey S
Sergey S

Reputation: 11

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)

Upvotes: 0

Related Questions