A partial solution, due to some approximation in ray cast (or some bug in my script?).
This solution uses 'world_to_camera_view' from bpy_extras.object_utils module. 'world_to_camera_view' returns the projection of a vertex in camera's coordinates, which means that the vertex is inside the camera view if the projected coordinates are between 0 and 1.
From that, the script is testing if the corresponding vertex is visible from the camera (and not hidden by another part of the mesh). To do that, it's using a ray cast from the camera location to the vertex.
Unfortunately, some of these ray casts fail... but this script is close to your need and that's why I provide it as an answer.
Hope that can help though.
import bpy
from mathutils import Vector
from mathutils.bvhtree import BVHTree
from bpy_extras.object_utils import world_to_camera_view
# Create a BVH tree and return bvh and vertices in world coordinates
def BVHTreeAndVerticesInWorldFromObj( obj ):
mWorld = obj.matrix_world
vertsInWorld = [mWorld * v.co for v in obj.data.vertices]
bvh = BVHTree.FromPolygons( vertsInWorld, [p.vertices for p in obj.data.polygons] )
return bvh, vertsInWorld
# Deselect mesh polygons and vertices
def DeselectEdgesAndPolygons( obj ):
for p in obj.data.polygons:
p.select = False
for e in obj.data.edges:
e.select = False
# Get context elements: scene, camera and mesh
scene = bpy.context.scene
cam = bpy.data.objects['Camera']
obj = bpy.data.objects['Cube']
# Threshold to test if ray cast corresponds to the original vertex
limit = 0.0001
# Deselect mesh elements
DeselectEdgesAndPolygons( obj )
# In world coordinates, get a bvh tree and vertices
bvh, vertices = BVHTreeAndVerticesInWorldFromObj( obj )
print( '-------------------' )
for i, v in enumerate( vertices ):
# Get the 2D projection of the vertex
co2D = world_to_camera_view( scene, cam, v )
# By default, deselect it
obj.data.vertices[i].select = False
# If inside the camera view
if 0.0 <= co2D.x <= 1.0 and 0.0 <= co2D.y <= 1.0:
# Try a ray cast, in order to test the vertex visibility from the camera
location, normal, index, distance = bvh.ray_cast( cam.location, (v - cam.location).normalized() )
# If the ray hits something and if this hit is close to the vertex, we assume this is the vertex
if location and (v - location).length < limit:
obj.data.vertices[i].select = True
del bvh

