1
$\begingroup$

How do I get the X/Y locations and the height/width of an object (in pixels) for every frame of animation and is it possible to export it to a text file?

I did find something similar to this but didn't work properly when the object was not in the center of the frame.

This is code that I found that almost works: from https://blender.stackexchange.com/a/7203/15543

import bpy
from mathutils import Vector

class Box:

    dim_x = 1
    dim_y = 1

    def __init__(self, min_x, min_y, max_x, max_y, dim_x=dim_x, dim_y=dim_y):
        self.min_x = min_x
        self.min_y = min_y
        self.max_x = max_x
        self.max_y = max_y
        self.dim_x = dim_x
        self.dim_y = dim_y

    @property
    def x(self):
        return round(self.min_x * self.dim_x)

    @property
    def y(self):
        return round(self.dim_y - self.max_y * self.dim_y)

    @property
    def width(self):
        return round((self.max_x - self.min_x) * self.dim_x)

    @property
    def height(self):
        return round((self.max_y - self.min_y) * self.dim_y)

    def __str__(self):
        return "<Box, x=%i, y=%i, width=%i, height=%i>" % \
               (self.x, self.y, self.width, self.height)

    def to_tuple(self):
        if self.width == 0 or self.height == 0:
            return (0, 0, 0, 0)
        return (self.x, self.y, self.width, self.height)

def camera_view_bounds_2d(scene, cam_ob, me_ob):
    """
    Returns camera space bounding box of mesh object.

    Negative 'z' value means the point is behind the camera.

    Takes shift-x/y, lens angle and sensor size into account
    as well as perspective/ortho projections.

    :arg scene: Scene to use for frame size.
    :type scene: :class:`bpy.types.Scene`
    :arg obj: Camera object.
    :type obj: :class:`bpy.types.Object`
    :arg me: Untransformed Mesh.
    :type me: :class:`bpy.types.Mesh´
    :return: a Box object (call its to_tuple() method to get x, y, width and height)
    :rtype: :class:`Box`
    """

    mat = cam_ob.matrix_world.normalized().inverted()
    me = me_ob.to_mesh(scene, True, 'PREVIEW')
    me.transform(me_ob.matrix_world)
    me.transform(mat)

    camera = cam_ob.data
    frame = [-v for v in camera.view_frame(scene=scene)[:3]]
    camera_persp = camera.type != 'ORTHO'

    lx = []
    ly = []

    for v in me.vertices:
        co_local = v.co
        z = -co_local.z

        if camera_persp:
            if z == 0.0:
                lx.append(0.5)
                ly.append(0.5)
            # Does it make any sense to drop these?
            #if z <= 0.0:
            #    continue
            else:
                frame = [(v / (v.z / z)) for v in frame]

        min_x, max_x = frame[1].x, frame[2].x
        min_y, max_y = frame[0].y, frame[1].y

        x = (co_local.x - min_x) / (max_x - min_x)
        y = (co_local.y - min_y) / (max_y - min_y)

        lx.append(x)
        ly.append(y)

    min_x = clamp(min(lx), 0.0, 1.0)
    max_x = clamp(max(lx), 0.0, 1.0)
    min_y = clamp(min(ly), 0.0, 1.0)
    max_y = clamp(max(ly), 0.0, 1.0)

    bpy.data.meshes.remove(me)

    r = scene.render
    fac = r.resolution_percentage * 0.01
    dim_x = r.resolution_x * fac
    dim_y = r.resolution_y * fac

    return Box(min_x, min_y, max_x, max_y, dim_x, dim_y)

def clamp(x, minimum, maximum):
    return max(minimum, min(x, maximum))

def write_bounds_2d(filepath, scene, cam_ob, me_ob, frame_start, frame_end):

    with open(filepath, "w") as file: 
        for frame in range(frame_start, frame_end + 1):
            bpy.context.scene.frame_set(frame)
            file.write("%i %i %i %i\n" % camera_view_bounds_2d(scene, cam_ob, me_ob).to_tuple())

def main(context):

    filepath = r"D:\temp\bounds_2d.txt"

    scene = context.scene
    cam_ob = scene.camera
    me_ob = context.object

    frame_current = scene.frame_current
    frame_start = scene.frame_start
    frame_end = scene.frame_end

    write_bounds_2d(filepath, scene, cam_ob, me_ob, frame_start, frame_end)

    scene.frame_set(frame_current)

main(bpy.context)
$\endgroup$
5
  • $\begingroup$ Have a look at blender.stackexchange.com/questions/6377/… this will give the camera bounds, the object bounds can be gleaned using location_3d_to_region_2d from the bounding box of the object. $\endgroup$
    – batFINGER
    Commented Jun 7, 2016 at 2:50
  • $\begingroup$ are there any examples of how to use it or how to calculate it in pixels ? $\endgroup$
    – lowman
    Commented Jun 7, 2016 at 7:02
  • $\begingroup$ I have made the assumption you want render image pixel coords and not area pixel coords. Put the view in camera view. The object bounding box has 8 3d coords (cube corners) which can be converted to region coords using location_3d_to_region_2d and a 2d box formed using your min max approach for x and y to give h & w. Same for object location, just plug ob.location into location_3d_to_region_2d The camera bounds rectangle will have the same dimensions ratio as your scene.render.resolution_x(&y) use this to map the region coords to the image pixel coords. $\endgroup$
    – batFINGER
    Commented Jun 7, 2016 at 7:34
  • $\begingroup$ isn't there a simple way to get the X,Y position ? imgur.com/yikbDAG $\endgroup$
    – lowman
    Commented Jun 7, 2016 at 14:15
  • $\begingroup$ The camera bounds is the hard bit. $\endgroup$
    – batFINGER
    Commented Jun 7, 2016 at 18:07

2 Answers 2

1
$\begingroup$

Here is the code from https://blender.stackexchange.com/a/14746/15543 slightly modified to make the region and region_3d global and put the area into camera view.

import bpy

from bpy_extras.view3d_utils import location_3d_to_region_2d

def view3d_find():
    # returns first 3d view, normally we get from context
    for area in bpy.context.window.screen.areas:
        if area.type == 'VIEW_3D':
            v3d = area.spaces[0]
            rv3d = v3d.region_3d
            for region in area.regions:
                if region.type == 'WINDOW':
                    return region, rv3d
    return None, None

region, rv3d = view3d_find()
# put the region into camera perspective
rv3d.view_perspective = 'CAMERA'

def view3d_camera_border(scene):
    obj = scene.camera
    cam = obj.data
    frame = cam.view_frame(scene)
    # move into object space
    frame = [obj.matrix_world * v for v in frame]
    # move into pixelspace
    frame_px = [location_3d_to_region_2d(region, rv3d, v) for v in frame]
    return frame_px

frame_px = view3d_camera_border(bpy.context.scene)
print("Camera frame:", frame_px)

This gives us the 2d region coordinates of what will be the rendered scene. If we take out the smallest of these coords we are mapping to "region camera coords" only vectors in (0, 0) to max(cambounds) will be in rendered image.

# this is the camera bounds
blc = min(frame_px)
cambounds = [v - blc for v in frame_px]
print("camera is on screen as :", max(cambounds))

I get as a test case: my render resolution (scene.render.resolution_x, .. resolution_y) is 1920 x 1080 (the ratio x/y = 1.777)

camera is on screen as : <Vector (328.3195, 184.6790)>

ie the camera rectangle you see in view3d window has size 328 x 184 which has the same ratio. So all vectors below, like for instance objloc can be multiplied by (1920 / 328.3195) to return them as pixel coords of the rendered image. (nb not taking into account resolution_percentage and non square pixels)

Similarly for object location.

# object location
obj = bpy.context.object # the context object.
objloc = location_3d_to_region_2d(
                      region,
                      rv3d,
                      obj.matrix_world.to_translation()) - blc
print(objloc)

And a quick object bounds using bounding box, get the 2d coords from the 8 3d corners of the bounding box.

bounding_box = [v[:] for v in obj.bound_box]
bbox_px = [location_3d_to_region_2d(region, rv3d, v) 
                         - blc for v in bounding_box]

min_x = min(v.x for v in bbox_px)
max_x = max(v.x for v in bbox_px)
bbox_width = max_x - min_x 

#... etc to get the coords of bbox.

or this could be done by looping thru the verts of the object

mw = obj.matrix_world
#global vert locs
verts = [mw * v.co for v in obj.data.vertices]
# vert locations in "region camera coords"
verts_px = [location_3d_to_region_2d(region, rv3d, v) 
                         - blc for v in verts]

and finding the min max for x, y as above for bounding box.

Note haven't put in code to loop thru frames and write to output file as that is trivial, it's the camera bounds that is difficult, but fortunately handed to us via ideasman42's answer.

$\endgroup$
0
$\begingroup$

To complete with the anwser of batFINGER. If you want to print the locations in a file at each frame, you need to add an handlers when the frame change (i.e. a function that is executed either before or after the frame changes ). To do so you need to append the function writecoords that write the X/Y coordinates to a file. Note that writecoords takes only one argument. Here an example of such a function:

bpy.app.handlers.frame_change_post.append(writecoords).
def myfunction(context):
    f = open('coords.txt','w+')
    x, y = get_XYcoordinates();
    f.write(x,y)
    f.close();

Cheers

$\endgroup$

You must log in to answer this question.

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