1
$\begingroup$

I followed the answer of this question in order to get a bounding box of visible area of an object in a rendered image. Since I have multiple objects that I add in the scene dynamically through script, and assign each of them a different pass_index, I use the following function (again, adapted from here) to get the bounding box of each object:

def getVisibleBoundingBox(objectPassIndex):

    S = bpy.context.scene
    width  = int( S.render.resolution_x * S.render.resolution_percentage / 100 )
    height = int( S.render.resolution_y * S.render.resolution_percentage / 100 )
    depth  = 4
    #set the index to the object's pass index
    bpy.context.scene.node_tree.nodes['ID Mask'].index = objectPassIndex   

    #render.render is very slow, try to find alternative to update the Viewer Node
    #bpy.ops.render.render()

    pixels = np.array( bpy.data.images['Viewer Node'].pixels[:] ).reshape( [height, width, depth] )
    # Keep only one value for each pixel (white pixels have 1 in all RGBA channels anyway), thus converting the image to black and white
    pixels = np.array( [ [ pixel[0] for pixel in row ] for row in pixels ] )

    bbox = np.argwhere( pixels )
    (ystart, xstart), (ystop, xstop) = bbox.min(0), bbox.max(0) + 1
    return xstart, xstop, height - ystart, height - ystop 

The problem is that the Viewer Node image data does not update when I change the index of the ID Mask node with:

 bpy.context.scene.node_tree.nodes['ID Mask'].index = objectPassIndex

and it contains stale data from another object's pass index, so I get the same result all the time for different objectPassIndex when I try to take the pixels with:

pixels = np.array( [ [ pixel[0] for pixel in row ] for row in pixels ] )

This is not the case if I change the pass index in the GUI, where the Viewer node updates immediately. A workaround I found is to call bpy.ops.render.render() after changing the index and before taking the pixels, however, this operation is very slow and therefore not applicable for my use case. I also tried calling bpy.data.images['Viewer Node'].reload() without success.

So, the question is, is there a faster way to refresh the data of the Viewer node?

If not, another alternative that might work for me is to get a combined image containing in each pixel the pass_index of the particular object. I noticed that this image can be generated in the GUI by rendering and selecting the pass to IndexOB (image attached). But I do not know if it is possible to change the pass to IndexOB from python and grab the rendered image.

When pass is set to IndexOB, the resulting image contains the index of the object as a value at each pixel position where the object is visible

So the alternative question is if there is a way to get access to the pixels of this image in python?

$\endgroup$

2 Answers 2

2
$\begingroup$

The answer to this question is the answer to the alternative question: "How to get the image where all pixel values are set to the pass_index of the particular object". Instead of using "ID Mask" node, the IndexOB output of "Render Layers" node can be connected directly to the Image input of "Viewer" node. Then the function can be modified like this to get the bounding box for particular objectPassIndex:

def getVisibleBoundingBox(objectPassIndex):

    S = bpy.context.scene
    width  = int( S.render.resolution_x * S.render.resolution_percentage / 100 )
    height = int( S.render.resolution_y * S.render.resolution_percentage / 100 )
    depth  = 4

    pixels = np.array( bpy.data.images['Viewer Node'].pixels[:] ).reshape( [height, width, depth] )
    # Keep only one value for each pixel (white pixels have 1 in all RGBA channels anyway), thus converting the image to black and white
    pixels = np.array( [ [ pixel[0] for pixel in row ] for row in pixels ] )

    bbox = np.argwhere( pixels == objectPassIndex )
    (ystart, xstart), (ystop, xstop) = bbox.min(0), bbox.max(0) + 1
    return xstart, xstop, height - ystart, height - ystop 
$\endgroup$
1
  • $\begingroup$ Sadly this wont work when running blender as background process $\endgroup$ Commented May 7, 2020 at 15:09
0
$\begingroup$

I finally got the answer after long hours of research!
Just add:

for vl in self.scene.view_layers:
        vl.use_pass_object_index = True

For a more complete solution:

for vl in self.scene.view_layers:
    vl.use_pass_object_index = True
index = 2
mask_outputs = []
for obj in object_to_mask:
    obj.pass_index = index
    maskid = node_tree.nodes.new('CompositorNodeIDMask')
    maskid.index = index
    node_tree.links.new(renderNode.outputs['IndexOB'], maskid.inputs[0])
    node_tree.links.new(output, self.outputNode.inputs[mask_name])
    index += 1
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .