bpy_extras.object_utils.world_to_camera_view

Use the world to camera view method to map to image coordinates, similar to a UV with 0, 0 the bottom left hand corner, 1, 1 the top right hand corner.
The z component is mapped to 0 and the width scaled such that the projection image has width 1.
Using the half angle tangent of the camera angle, moving such that the width is one, maps the projection onto the rostrum (face or whatever it's called) of the camera.
>>> from bpy_extras.object_utils import world_to_camera_view
>>> world_to_camera_view(
world_to_camera_view(scene, obj, coord)
Returns the camera space coords for a 3d point.
(also known as: normalized device coordinates - NDC).
Where (0, 0) is the bottom left and (1, 1)
is the top right of the camera frame.
values outside 0-1 are also supported.
A 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 coord: World space location.
:type coord: :class:`mathutils.Vector`
:return: a vector where X and Y map to the view plane and
Z is the depth on the view axis.
:rtype: :class:`mathutils.Vector`
To use the script below select whichever mesh objects you wish to project onto the scene camera plane.
import bpy
from bpy import context
from mathutils import Matrix, Vector
from math import tan, radians
from bpy_extras.object_utils import world_to_camera_view as w2cv
def tocam(scene, obs):
cam = scene.camera
cam_vec = cam.matrix_world.to_3x3() @ Vector((0, 0, -1))
R = cam_vec.to_track_quat('-Z', 'Y').to_matrix().to_4x4()
s = Vector((1, (scene.render.resolution_y / scene.render.resolution_x), 1, 1))
# scale based on resolution
S = Matrix.Diagonal(s)
# translate such that origin is middle point of image (and hence cam)
T = Matrix.Translation((-0.5, -0.5, 0))
for ob in obs:
ob.data.transform(ob.matrix_world)
ob.matrix_world = Matrix()
for v in ob.data.vertices:
vec = w2cv(scene, cam, v.co)
v.co = vec.x, vec.y, 0
ob.data.transform(S @ T)
ob.matrix_world = R
angle_x = cam.data.angle_x
x = (0.5 / tan(angle_x / 2)) * cam_vec.normalized()
ob.matrix_world.translation = cam.matrix_world.translation + x
if cam.data.type == 'ORTHO':
ob.scale *= cam.data.ortho_scale
bpy.ops.object.duplicate()
tocam(context.scene, context.selected_objects)
EDIT: fix for ortho camera.