@@ -191,6 +191,60 @@ def testCameraSelection(self, coordinates, expected_selection):
191191 selected = camera .select (coordinates )
192192 self .assertEqual (expected_selection , selected [:2 ])
193193
194+ @parameterized .parameters (
195+ dict (camera_id = 'cam0' , height = 200 , width = 300 ),
196+ dict (camera_id = 1 , height = 300 , width = 200 ),
197+ dict (camera_id = - 1 , height = 400 , width = 400 ),
198+ )
199+ def testCameraMatrix (self , camera_id , height , width ):
200+ """Tests the camera_matrix() method.
201+
202+ Creates a model with two cameras and two small geoms. We render the scene
203+ with one of the cameras and check that the geom locations, projected into
204+ pixel space, are correct, using segmenation rendering.
205+ xyz2pixels() shows how the transformation is used. For a description
206+ of the camera matrix see https://en.wikipedia.org/wiki/Camera_matrix.
207+
208+ Args:
209+ camera_id: One of the two cameras. Can be either integer or String.
210+ height: The height of the image (pixels).
211+ width: The width of the image (pixels).
212+ """
213+
214+ def xyz2pixels (x , y , z , camera_matrix ):
215+ """Transforms from world coordinates to pixel coordinates."""
216+ xs , ys , s = camera_matrix .dot (np .array ([x , y , z , 1.0 ]))
217+ return xs / s , ys / s
218+
219+ two_geoms_and_two_cameras = """
220+ <mujoco>
221+ <visual>
222+ <global fovy="55"/>
223+ </visual>
224+ <worldbody>
225+ <light name="top" pos="0 0 1"/>
226+ <geom name="red" pos=".2 0 0" size=".005" rgba="1 0 0 1"/>
227+ <geom name="green" pos=".2 .2 .1" size=".005" rgba="0 1 0 1"/>
228+ <camera name="cam0" pos="1 .5 1" zaxis="1 .5 1" fovy="20"/>
229+ <camera name="cam1" pos=".1 .1 1" xyaxes="1 1 0 -1 0 0"/>
230+ </worldbody>
231+ </mujoco>
232+ """
233+ physics = engine .Physics .from_xml_string (two_geoms_and_two_cameras )
234+ camera = engine .Camera (physics , width = width , height = height ,
235+ camera_id = camera_id )
236+ camera_matrix = camera .matrix # Get camera matrix.
237+ pixels = camera .render (segmentation = True ) # Render a segmentation frame.
238+ for geom_id in [0 , 1 ]:
239+ # Compute the location of the geom in pixel space using the camera matrix.
240+ x , y = xyz2pixels (* physics .data .geom_xpos [geom_id ], camera_matrix )
241+ row = int (round (y ))
242+ column = int (round (x ))
243+ # Compare segmentation values of nearest pixel to corresponding geom.
244+ [obj_id , obj_type ] = pixels [row , column , :]
245+ self .assertEqual (obj_type , enums .mjtObj .mjOBJ_GEOM )
246+ self .assertEqual (obj_id , geom_id )
247+
194248 def testMovableCameraSetGetPose (self ):
195249 height , width = 240 , 320
196250
0 commit comments