Search code examples
pythonarraysnumpyimage-processingblender

Saving numpy array to image shrinks image parts to wrong size


I've face this problem when figuring out how to export external images in blender script. But I guess this is not related straight to blender anymore, more to numpy and how to handle arrays. Here is post about first problem.

So the problem is that when saving numpy array to image it will distorted and there is multiple same images. Look below image for a better understanding.

The goal is trying to figure out how to make this work with numpy and python using the blender's own pixel data. So avoiding to use libraries like PIL or cv2 that do not include in blender python.

enter image description here

When saving data where is images that all is final size works correctly. And when trying to merge 4 smaller pieces to final larger image it not exported correctly.

I've done example script with python in blender to demonstrate the problem:

# Example script to show how to merge external images in Blender
# using numpy. In this example we use 4 images (2x2) that should
# be merged to one actual final image. 
# Regular (not cropped render borders) seems to work fine but
# how to merge cropped images properly???
#
# Usage: Just run script and it will export image named "MERGED_IMAGE"
# to root of this project folder and you'll see what's the problem.

import bpy, os
import numpy as np

ctx = bpy.context
scn = ctx.scene

print('START')

# Get all image files
def get_files_in_folder(path):
    path = bpy.path.abspath(path)
    render_files = []
    for root, dirs, files in os.walk(path):
        for file in files:
            if (file.lower().endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif'))):
                render_files.append(file)
    return render_files

def merge_images(image_files, image_cropped = True):

    image_pixels = []
    final_image_pixels = 0

    print(image_files)

    for file in image_files:
        if image_cropped is True:
            filepath = bpy.path.abspath('//Cropped\\' + file)
        else:
            filepath = bpy.path.abspath('//Regular\\' + file)
        loaded_pixels = bpy.data.images.load(filepath, check_existing=True).pixels
        image_pixels.append(loaded_pixels)

    np_array = np.array(image_pixels)

    # Merge images
    if image_cropped:
        final_image_pixels = np_array
        # HOW MERGE PROPERLY WHEN USING CROPPED IMAGES???
    else:
        for arr in np_array:
            final_image_pixels += arr

    # Save output image
    output_image = bpy.data.images.new('MERGED_IMAGE', alpha=True, width=256, height=256)
    output_image.file_format = 'PNG'
    output_image.alpha_mode = 'STRAIGHT'
    output_image.pixels = final_image_pixels.ravel()
    output_image.filepath_raw = bpy.path.abspath("//MERGED_IMAGE.png")
    output_image.save()   

images_cropped = get_files_in_folder("//Cropped")
images_regular = get_files_in_folder('//Regular')

# Change between these to get different example
merge_images(images_cropped)
#merge_images(images_regular, False)

print('END')

So I guess the problem is related to how to handle image pixel data and arrays with numpy.

Here is project folder in zip file that contains working test script example, where you can test how this works in blender. https://drive.google.com/file/d/1R4G_fubEzFWbHZMLtAAES-QsRhKyLKWb/view?usp=sharing


Solution

  • Since all of your images are the same dimension of 128x128, and since OpenCV images are Numpy arrays, here are three methods. You can save the image using cv2.imwrite.

    Input images:

    enter image description here enter image description here enter image description here enter image description here

    Method #1: np.hstack + np.vstack

    hstack1 = np.hstack((image1, image2))
    hstack2 = np.hstack((image3, image4))
    hstack_result = np.vstack((hstack1, hstack2))
    

    Method #2: np.concatenate

    concatenate1 = np.concatenate((image1, image2), axis=1)
    concatenate2 = np.concatenate((image3, image4), axis=1)
    concatenate_result = np.concatenate((concatenate1, concatenate2), axis=0) 
    

    Method #3: cv2.hconcat + cv2.vconcat

    hconcat1 = cv2.hconcat([image1, image2])
    hconcat2 = cv2.hconcat([image3, image4])
    hconcat_result = cv2.vconcat([hconcat1, hconcat2])
    

    Result should be the same for all methods

    enter image description here

    Full code

    import cv2
    import numpy as np
    
    # Load images
    image1 = cv2.imread('art_1_2.png')
    image2 = cv2.imread('art_2_2.png')
    image3 = cv2.imread('art_1_1.png')
    image4 = cv2.imread('art_2_1.png')
    
    # Method #1
    hstack1 = np.hstack((image1, image2))
    hstack2 = np.hstack((image3, image4))
    hstack_result = np.vstack((hstack1, hstack2))
    
    # Method #2
    concatenate1 = np.concatenate((image1, image2), axis=1)
    concatenate2 = np.concatenate((image3, image4), axis=1)
    concatenate_result = np.concatenate((concatenate1, concatenate2), axis=0) 
    
    # Method #3
    hconcat1 = cv2.hconcat([image1, image2])
    hconcat2 = cv2.hconcat([image3, image4])
    hconcat_result = cv2.vconcat([hconcat1, hconcat2])
    
    # Display
    cv2.imshow('concatenate_result', concatenate_result)
    cv2.imshow('hstack_result', hstack_result)
    cv2.imshow('hconcat_result', hconcat_result)
    cv2.waitKey()