You can do this by analyzing each isolated object in the compositor after rendering, via an object ID mask.
The compositor gives you access to the pixels of the viewer node, which can be manipulated quickly in Numpy.
This is the rendered image:
I gave each of the objects a different pass index, so we generate a mask that isolates each in turn with the ID mask node.
By changing the index in the ID mask node you can iterate between the objects. Here's object 1's mask:
Now, to get the bounding box coordinates all you need to do is run this script (partially based on this nice method for producing a bounding box):
import numpy as np
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 for pixel in row ] for row in pixels ] )
bbox = np.argwhere( pixels )
(ystart, xstart), (ystop, xstop) = bbox.min(0), bbox.max(0) + 1
Output I got for this image: