/src/morse/sensors/video_camera.py

https://github.com/davidhodo/morse · Python · 131 lines · 116 code · 8 blank · 7 comment · 0 complexity · ec5f4033cee7e05f467300300fbcf2ff MD5 · raw file

  1. import logging; logger = logging.getLogger("morse." + __name__)
  2. from morse.core.services import async_service
  3. from morse.core import status
  4. import morse.core.blenderapi
  5. from morse.core import mathutils
  6. import morse.sensors.camera
  7. from morse.helpers.components import add_data
  8. import copy
  9. BLENDER_HORIZONTAL_APERTURE = 32.0
  10. class VideoCamera(morse.sensors.camera.Camera):
  11. """
  12. This sensor emulates a single video camera. It generates a series of
  13. RGBA images. Images are encoded as binary char arrays, with 4 bytes
  14. per pixel.
  15. The cameras make use of Blender's **bge.texture** module, which
  16. requires a graphic card capable of GLSL shading. Also, the 3D view
  17. window in Blender must be set to draw **Textured** objects.
  18. Camera calibration matrix
  19. -------------------------
  20. The camera configuration parameters implicitly define a geometric camera in
  21. blender units. Knowing that the **cam_focal** attribute is a value that
  22. represents the distance in Blender unit at which the largest image dimension is
  23. 32.0 Blender units, the camera intrinsic calibration matrix is defined as
  24. +--------------+-------------+---------+
  25. | **alpha_u** | 0 | **u_0** |
  26. +--------------+-------------+---------+
  27. | 0 | **alpha_v** | **v_0** |
  28. +--------------+-------------+---------+
  29. | 0 | 0 | 1 |
  30. +--------------+-------------+---------+
  31. where:
  32. - **alpha_u** == **alpha_v** = **cam_width** . **cam_focal** / 32 (we suppose
  33. here that **cam_width** > **cam_height**. If not, then use **cam_height** in
  34. the formula)
  35. - **u_0** = **cam_height** / 2
  36. - **v_0** = **cam_width** / 2
  37. """
  38. _name = "Video camera"
  39. _short_desc = "A camera capturing RGBA image"
  40. add_data('image', 'none', 'buffer',
  41. "The data captured by the camera, stored as a Python Buffer \
  42. class object. The data is of size ``(cam_width * cam_height * 4)``\
  43. bytes. The image is stored as RGBA.")
  44. add_data('intrinsic_matrix', 'none', 'mat3<float>',
  45. "The intrinsic calibration matrix, stored as a 3x3 row major Matrix.")
  46. def __init__(self, obj, parent=None):
  47. """ Constructor method.
  48. Receives the reference to the Blender object.
  49. The second parameter should be the name of the object's parent.
  50. """
  51. logger.info('%s initialization' % obj.name)
  52. # Call the constructor of the parent class
  53. super(VideoCamera, self).__init__(obj, parent)
  54. # Prepare the exportable data of this sensor
  55. self.local_data['image'] = ''
  56. # Prepare the intrinsic matrix for this camera.
  57. # Note that the matrix is stored in row major
  58. intrinsic = mathutils.Matrix()
  59. intrinsic.identity()
  60. alpha_u = self.image_width * \
  61. self.image_focal / BLENDER_HORIZONTAL_APERTURE
  62. intrinsic[0][0] = alpha_u
  63. intrinsic[1][1] = alpha_u
  64. intrinsic[0][2] = self.image_width / 2.0
  65. intrinsic[1][2] = self.image_height / 2.0
  66. self.local_data['intrinsic_matrix'] = intrinsic
  67. self.capturing = False
  68. self._n = -1
  69. # Variable to indicate this is a camera
  70. self.camera_tag = True
  71. # Position of the robot where the last shot is taken
  72. self.robot_pose = copy.copy(self.robot_parent.position_3d)
  73. logger.info("Component initialized, runs at %.2f Hz ", self.frequency)
  74. def interrupt(self):
  75. self._n = 0
  76. super(VideoCamera, self).interrupt()
  77. @async_service
  78. def capture(self, n):
  79. """
  80. Capture **n** images
  81. :param n: the number of images to take. A negative number means
  82. take image indefinitely
  83. """
  84. self._n = n
  85. def default_action(self):
  86. """ Update the texture image. """
  87. # Grab an image from the texture
  88. if self.bge_object['capturing'] and (self._n != 0) :
  89. # Call the action of the parent class
  90. super(self.__class__, self).default_action()
  91. # NOTE: Blender returns the image as a binary string
  92. # encoded as RGBA
  93. image_data = morse.core.blenderapi.cameras()[self.name()].source
  94. self.robot_pose = copy.copy(self.robot_parent.position_3d)
  95. # Fill in the exportable data
  96. self.local_data['image'] = image_data
  97. self.capturing = True
  98. if (self._n > 0):
  99. self._n -= 1
  100. if (self._n == 0):
  101. self.completed(status.SUCCESS)
  102. else:
  103. self.capturing = False