Panda3D
openCVTexture.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file openCVTexture.cxx
10  * @author zacpavlov
11  * @date 2005-08-19
12  */
13 
14 #include "pandabase.h"
15 
16 #ifdef HAVE_OPENCV
17 #include "openCVTexture.h"
18 #include "clockObject.h"
19 #include "config_gobj.h"
20 #include "config_vision.h"
21 #include "bamReader.h"
22 #include "bamCacheRecord.h"
23 
24 // This symbol is predefined by the Panda3D build system to select whether we
25 // are using the OpenCV 3.x or later interface.
26 #if defined(OPENCV_VER_3)
27 
28 #include <opencv2/core.hpp>
29 #include <opencv2/videoio/videoio_c.h>
30 
31 // This checks for 2.3 or later.
32 #elif defined(OPENCV_VER_23)
33 
34 #include <opencv2/core/core.hpp>
35 #include <opencv2/highgui/highgui.hpp>
36 
37 // If neither of those are predefined, assume 1.x.
38 #else
39 
40 #include <cv.h>
41 #include <cxcore.h>
42 #include <highgui.h>
43 
44 #endif // OPENCV_VER_3
45 
46 TypeHandle OpenCVTexture::_type_handle;
47 
48 /**
49  * Sets up the texture to read frames from a camera
50  */
51 OpenCVTexture::
52 OpenCVTexture(const std::string &name) :
53  VideoTexture(name)
54 {
55 }
56 
57 /**
58  *
59  */
60 OpenCVTexture::
61 ~OpenCVTexture() {
62 }
63 
64 /**
65  * Calls update_frame() if the current frame has changed.
66  */
67 void OpenCVTexture::
68 consider_update() {
69  int this_frame = ClockObject::get_global_clock()->get_frame_count();
70  if (this_frame != _last_frame_update) {
71  int frame = get_frame();
72  if (_current_frame != frame) {
73  Texture::CDWriter cdata(Texture::_cycler, false);
74  do_update_frame(cdata, frame);
75  _current_frame = frame;
76  } else {
77  // Loop through the pages to see if there's any camera stream to update.
78  Texture::CDWriter cdata(Texture::_cycler, false);
79  int max_z = std::max(cdata->_z_size, (int)_pages.size());
80  for (int z = 0; z < max_z; ++z) {
81  VideoPage &page = _pages[z];
82  if (!page._color.is_from_file() || !page._alpha.is_from_file()) {
83  do_update_frame(cdata, frame, z);
84  }
85  }
86  }
87  _last_frame_update = this_frame;
88  }
89 }
90 
91 /**
92  * Returns a new copy of the same Texture. This copy, if applied to geometry,
93  * will be copied into texture as a separate texture from the original, so it
94  * will be duplicated in texture memory (and may be independently modified if
95  * desired).
96  *
97  * If the Texture is an OpenCVTexture, the resulting duplicate may be animated
98  * independently of the original.
99  */
100 PT(Texture) OpenCVTexture::
101 make_copy_impl() const {
102  Texture::CDReader cdata_tex(Texture::_cycler);
103  PT(OpenCVTexture) copy = new OpenCVTexture(get_name());
104  Texture::CDWriter cdata_copy_tex(copy->Texture::_cycler, true);
105  copy->do_assign(cdata_copy_tex, this, cdata_tex);
106 
107  return copy;
108 }
109 
110 /**
111  * Implements make_copy().
112  */
113 void OpenCVTexture::
114 do_assign(Texture::CData *cdata_tex, const OpenCVTexture *copy,
115  const Texture::CData *cdata_copy_tex) {
116  VideoTexture::do_assign(cdata_tex, copy, cdata_copy_tex);
117  _pages = copy->_pages;
118 }
119 
120 /**
121  * Sets up the OpenCVTexture (or the indicated page, if z is specified) to
122  * accept its input from the camera with the given index number, or the
123  * default camera if the index number is -1 or unspecified.
124  *
125  * If alpha_file_channel is 0, then the camera image becomes a normal RGB
126  * texture. If it is 1, 2, or 3, then the camera image becomes an alpha
127  * texture, using the indicated channel of the source.
128  */
129 bool OpenCVTexture::
130 from_camera(int camera_index, int z, int alpha_file_channel,
131  const LoaderOptions &options) {
132  Texture::CDWriter cdata(Texture::_cycler, true);
133  if (!do_reconsider_z_size(cdata, z, options)) {
134  return false;
135  }
136  nassertr(z >= 0 && z < cdata->_z_size, false);
137 
138  cdata->_alpha_file_channel = alpha_file_channel;
139 
140  VideoPage &page = do_modify_page(cdata, z);
141  if (alpha_file_channel == 0) {
142  // A normal RGB texture.
143  page._alpha.clear();
144  if (!page._color.from_camera(camera_index)) {
145  return false;
146  }
147 
148  if (!do_reconsider_video_properties(cdata, page._color, 3, z, options)) {
149  page._color.clear();
150  return false;
151  }
152  } else {
153  // An alpha texture.
154  page._color.clear();
155  if (!page._alpha.from_camera(camera_index)) {
156  return false;
157  }
158 
159  if (!do_reconsider_video_properties(cdata, page._alpha, 1, z, options)) {
160  page._alpha.clear();
161  return false;
162  }
163  do_set_format(cdata, F_alpha);
164  }
165 
166  cdata->_loaded_from_image = true;
167  clear_current_frame();
168  do_update_frame(cdata, 0);
169  return true;
170 }
171 
172 /**
173  * Returns a reference to the zth VideoPage (level) of the texture. In the
174  * case of a 2-d texture, there is only one page, level 0; but cube maps and
175  * 3-d textures have more.
176  */
177 OpenCVTexture::VideoPage &OpenCVTexture::
178 do_modify_page(const Texture::CData *cdata, int z) {
179  nassertr(z < cdata->_z_size, _pages[0]);
180  while (z >= (int)_pages.size()) {
181  _pages.push_back(VideoPage());
182  }
183  return _pages[z];
184 }
185 
186 /**
187  * Resets the internal Texture properties when a new video file is loaded.
188  * Returns true if the new image is valid, false otherwise.
189  */
190 bool OpenCVTexture::
191 do_reconsider_video_properties(Texture::CData *cdata,
192  const OpenCVTexture::VideoStream &stream,
193  int num_components, int z,
194  const LoaderOptions &options) {
195  double frame_rate = 0.0f;
196  int num_frames = 0;
197 
198  if (stream.is_from_file()) {
199  frame_rate = cvGetCaptureProperty(stream._capture, CV_CAP_PROP_FPS);
200  num_frames = (int)cvGetCaptureProperty(stream._capture, CV_CAP_PROP_FRAME_COUNT);
201  if (vision_cat.is_debug()) {
202  vision_cat.debug()
203  << "Loaded " << stream._filename << ", " << num_frames << " frames at "
204  << frame_rate << " fps\n";
205  }
206  } else {
207  // In this case, we don't have a specific frame rate or number of frames.
208  // Let both values remain at 0.
209  if (vision_cat.is_debug()) {
210  vision_cat.debug()
211  << "Loaded camera stream\n";
212  }
213  }
214 
215  int width = (int)cvGetCaptureProperty(stream._capture, CV_CAP_PROP_FRAME_WIDTH);
216  int height = (int)cvGetCaptureProperty(stream._capture, CV_CAP_PROP_FRAME_HEIGHT);
217 
218  int x_size = width;
219  int y_size = height;
220  do_adjust_this_size(cdata, x_size, y_size, get_name(), true);
221 
222  if (vision_cat.is_debug()) {
223  vision_cat.debug()
224  << "Video stream is " << width << " by " << height
225  << " pixels; fitting in texture " << x_size << " by "
226  << y_size << " texels.\n";
227  }
228 
229  if (!do_reconsider_image_properties(cdata, x_size, y_size, num_components,
230  T_unsigned_byte, z, options)) {
231  return false;
232  }
233 
234  if (cdata->_loaded_from_image &&
235  (get_video_width() != width || get_video_height() != height ||
236  get_num_frames() != num_frames || get_frame_rate() != frame_rate)) {
237  vision_cat.error()
238  << "Video properties have changed for texture " << get_name()
239  << " level " << z << ".\n";
240  return false;
241  }
242 
243  set_frame_rate(frame_rate);
244  set_num_frames(num_frames);
245  set_video_size(width, height);
246 
247  // By default, the newly-loaded video stream will immediately start looping.
248  loop(true);
249 
250  return true;
251 }
252 
253 /**
254  * A factory function to make a new OpenCVTexture, used to pass to the
255  * TexturePool.
256  */
257 PT(Texture) OpenCVTexture::
258 make_texture() {
259  return new OpenCVTexture;
260 }
261 
262 /**
263  * Called once per frame, as needed, to load the new image contents.
264  */
265 void OpenCVTexture::
266 do_update_frame(Texture::CData *cdata, int frame) {
267  int max_z = std::max(cdata->_z_size, (int)_pages.size());
268  for (int z = 0; z < max_z; ++z) {
269  do_update_frame(cdata, frame, z);
270  }
271 }
272 
273 /**
274  * This variant of update_frame updates the indicated page only.
275  */
276 void OpenCVTexture::
277 do_update_frame(Texture::CData *cdata, int frame, int z) {
278  if (vision_cat.is_spam()) {
279  vision_cat.spam()
280  << "Updating OpenCVTexture page " << z << "\n";
281  }
282 
283  VideoPage &page = _pages[z];
284  if (page._color.is_valid() || page._alpha.is_valid()) {
285  do_modify_ram_image(cdata);
286  ++(cdata->_image_modified);
287  }
288  int dest_x_pitch = cdata->_num_components * cdata->_component_width;
289  int dest_y_pitch = cdata->_x_size * dest_x_pitch;
290 
291  if (page._color.is_valid()) {
292  nassertv(get_num_components() >= 3 && get_component_width() == 1);
293 
294  const unsigned char *r, *g, *b;
295  int x_pitch, y_pitch;
296  if (page._color.get_frame_data(frame, r, g, b, x_pitch, y_pitch)) {
297  nassertv(get_video_width() <= cdata->_x_size && get_video_height() <= cdata->_y_size);
298  nassertv(!cdata->_ram_images.empty())
299  unsigned char *dest = cdata->_ram_images[0]._image.p() + do_get_expected_ram_page_size(cdata) * z;
300 
301  if (cdata->_num_components == 3 && x_pitch == 3) {
302  // The easy case--copy the whole thing in, row by row.
303  int copy_bytes = get_video_width() * dest_x_pitch;
304  nassertv(copy_bytes <= dest_y_pitch && copy_bytes <= abs(y_pitch));
305 
306  for (int y = 0; y < get_video_height(); ++y) {
307  memcpy(dest, r, copy_bytes);
308  dest += dest_y_pitch;
309  r += y_pitch;
310  }
311 
312  } else {
313  // The harder case--interleave in the color channels, pixel by pixel,
314  // possibly leaving room for alpha.
315 
316  for (int y = 0; y < get_video_height(); ++y) {
317  int dx = 0;
318  int sx = 0;
319  for (int x = 0; x < get_video_width(); ++x) {
320  dest[dx] = r[sx];
321  dest[dx + 1] = g[sx];
322  dest[dx + 2] = b[sx];
323  dx += dest_x_pitch;
324  sx += x_pitch;
325  }
326  dest += dest_y_pitch;
327  r += y_pitch;
328  g += y_pitch;
329  b += y_pitch;
330  }
331  }
332  }
333  }
334  if (page._alpha.is_valid()) {
335  nassertv(get_component_width() == 1);
336 
337  const unsigned char *source[3];
338  int x_pitch, y_pitch;
339  if (page._alpha.get_frame_data(frame, source[0], source[1], source[2],
340  x_pitch, y_pitch)) {
341  nassertv(get_video_width() <= cdata->_x_size && get_video_height() <= cdata->_y_size);
342  nassertv(!cdata->_ram_images.empty())
343  unsigned char *dest = cdata->_ram_images[0]._image.p() + do_get_expected_ram_page_size(cdata) * z;
344 
345  // Interleave the alpha in with the color, pixel by pixel. Even though
346  // the alpha will probably be a grayscale video, the OpenCV library
347  // presents it as RGB.
348  const unsigned char *sch = source[0];
349  if (cdata->_alpha_file_channel >= 1 && cdata->_alpha_file_channel <= 3) {
350  sch = source[cdata->_alpha_file_channel - 1];
351  }
352 
353  for (int y = 0; y < get_video_height(); ++y) {
354  // Start dx at _num_components - 1, which writes to the last channel,
355  // i.e. the alpha channel.
356  int dx = (cdata->_num_components - 1) * cdata->_component_width;
357  int sx = 0;
358  for (int x = 0; x < get_video_width(); ++x) {
359  dest[dx] = sch[sx];
360  dx += dest_x_pitch;
361  sx += x_pitch;
362  }
363  dest += dest_y_pitch;
364  sch += y_pitch;
365  }
366  }
367  }
368 }
369 
370 /**
371  * Combines a color and alpha video image from the two indicated filenames.
372  * Both must be the same kind of video with similar properties.
373  */
374 bool OpenCVTexture::
375 do_read_one(Texture::CData *cdata,
376  const Filename &fullpath, const Filename &alpha_fullpath,
377  int z, int n, int primary_file_num_channels, int alpha_file_channel,
378  const LoaderOptions &options,
379  bool header_only, BamCacheRecord *record) {
380  if (record != nullptr) {
381  record->add_dependent_file(fullpath);
382  }
383 
384  nassertr(n == 0, false);
385  nassertr(z >= 0 && z < cdata->_z_size, false);
386 
387  VideoPage &page = do_modify_page(cdata, z);
388  if (!page._color.read(fullpath)) {
389  vision_cat.error()
390  << "OpenCV couldn't read " << fullpath << " as video.\n";
391  return false;
392  }
393  if (!alpha_fullpath.empty()) {
394  if (!page._alpha.read(alpha_fullpath)) {
395  vision_cat.error()
396  << "OpenCV couldn't read " << alpha_fullpath << " as video.\n";
397  page._color.clear();
398  return false;
399  }
400  }
401 
402  if (z == 0) {
403  if (!has_name()) {
404  set_name(fullpath.get_basename_wo_extension());
405  }
406  // Don't use has_filename() here, it will cause a deadlock
407  if (cdata->_filename.empty()) {
408  cdata->_filename = fullpath;
409  cdata->_alpha_filename = alpha_fullpath;
410  }
411 
412  cdata->_fullpath = fullpath;
413  cdata->_alpha_fullpath = alpha_fullpath;
414  }
415 
416  cdata->_primary_file_num_channels = 3;
417  cdata->_alpha_file_channel = 0;
418 
419  if (alpha_fullpath.empty()) {
420  // Only one RGB movie.
421  if (!do_reconsider_video_properties(cdata, page._color, 3, z, options)) {
422  page._color.clear();
423  return false;
424  }
425 
426  } else {
427  // An RGB movie combined with an alpha movie.
428  cdata->_alpha_file_channel = alpha_file_channel;
429 
430  if (!do_reconsider_video_properties(cdata, page._color, 4, z, options)) {
431  page._color.clear();
432  page._alpha.clear();
433  return false;
434  }
435 
436  if (!do_reconsider_video_properties(cdata, page._alpha, 4, z, options)) {
437  page._color.clear();
438  page._alpha.clear();
439  return false;
440  }
441  }
442 
443  set_loaded_from_image();
444  clear_current_frame();
445  do_update_frame(cdata, 0);
446  return true;
447 }
448 
449 /**
450  * Resets the texture (or the particular level of the texture) to the
451  * indicated static image.
452  */
453 bool OpenCVTexture::
454 do_load_one(Texture::CData *cdata,
455  const PNMImage &pnmimage, const std::string &name,
456  int z, int n, const LoaderOptions &options) {
457  if (z <= (int)_pages.size()) {
458  VideoPage &page = do_modify_page(cdata, z);
459  page._color.clear();
460  page._alpha.clear();
461  }
462 
463  return Texture::do_load_one(cdata, pnmimage, name, z, n, options);
464 }
465 
466 /**
467  * Factory method to generate a Texture object
468  */
469 void OpenCVTexture::
470 register_with_read_factory() {
471  // Since Texture is such a funny object that is reloaded from the
472  // TexturePool each time, instead of actually being read fully from the bam
473  // file, and since the VideoTexture and OpenCVTexture classes don't really
474  // add any useful data to the bam record, we don't need to define
475  // make_from_bam(), fillin(), or write_datagram() in this class--we just
476  // inherit the same functions from Texture.
477 
478  // We do, however, have to register this class with the BamReader, to avoid
479  // warnings about creating the wrong kind of object from the bam file.
480  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
481 }
482 
483 /**
484  *
485  */
486 OpenCVTexture::VideoStream::
487 VideoStream() :
488  _capture(nullptr),
489  _camera_index(-1),
490  _next_frame(0)
491 {
492 }
493 
494 /**
495  *
496  */
497 OpenCVTexture::VideoStream::
498 VideoStream(const OpenCVTexture::VideoStream &copy) :
499  _capture(nullptr),
500  _camera_index(-1)
501 {
502  // Rather than copying the _capture pointer, we must open a new stream that
503  // references the same file.
504  if (copy.is_valid()) {
505  if (copy.is_from_file()) {
506  read(copy._filename);
507  } else {
508  from_camera(copy._camera_index);
509  }
510  }
511 }
512 
513 /**
514  *
515  */
516 OpenCVTexture::VideoStream::
517 ~VideoStream() {
518  clear();
519 }
520 
521 /**
522  * Gets the data needed to traverse through the decompressed buffer for the
523  * indicated frame number. It is most efficient to call this in increasing
524  * order of frame number. Returns true on success, false on failure.
525  *
526  * In the case of a success indication (true return value), the three pointers
527  * r, g, b are loaded with the addresses of the three components of the
528  * bottom-left pixel of the image. (They will be adjacent in memory in the
529  * case of an interleaved image, and separated in the case of a separate-
530  * channel image.) The x_pitch value is filled with the amount to add to each
531  * pointer to advance to the pixel to the right; and the y_pitch value is
532  * filled with the amount to add to each pointer to advance to the pixel
533  * above. Note that these values may be negative (particularly in the case of
534  * a top-down image).
535  */
536 bool OpenCVTexture::VideoStream::
537 get_frame_data(int frame,
538  const unsigned char *&r,
539  const unsigned char *&g,
540  const unsigned char *&b,
541  int &x_pitch, int &y_pitch) {
542  nassertr(is_valid(), false);
543 
544  if (is_from_file() && _next_frame != frame) {
545  cvSetCaptureProperty(_capture, CV_CAP_PROP_POS_FRAMES, frame);
546  }
547 
548  _next_frame = frame + 1;
549  IplImage *image = cvQueryFrame(_capture);
550  if (image == nullptr) {
551  return false;
552  }
553 
554  r = (const unsigned char *)image->imageData;
555  g = r + 1;
556  b = g + 1;
557  x_pitch = 3;
558  y_pitch = image->widthStep;
559 
560  if (image->dataOrder == 1) {
561  // Separate channel images. That means a block of r, followed by a block
562  // of g, followed by a block of b.
563  x_pitch = 1;
564  g = r + image->height * y_pitch;
565  b = g + image->height * y_pitch;
566  }
567 
568  if (image->origin == 0) {
569  // The image data starts with the top row and ends with the bottom row--
570  // the opposite of Texture::_ram_data's storage convention. Therefore, we
571  // must increment the initial pointers to the last row, and count
572  // backwards.
573  r += (image->height - 1) * y_pitch;
574  g += (image->height - 1) * y_pitch;
575  b += (image->height - 1) * y_pitch;
576  y_pitch = -y_pitch;
577  }
578 
579  return true;
580 }
581 
582 /**
583  * Sets up the stream to read the indicated file. Returns true on success,
584  * false on failure.
585  */
586 bool OpenCVTexture::VideoStream::
587 read(const Filename &filename) {
588  clear();
589 
590  std::string os_specific = filename.to_os_specific();
591  _capture = cvCaptureFromFile(os_specific.c_str());
592  if (_capture == nullptr) {
593  return false;
594  }
595  _filename = filename;
596  return true;
597 }
598 
599 /**
600  * Sets up the stream to display the indicated camera. Returns true on
601  * success, false on failure.
602  */
603 bool OpenCVTexture::VideoStream::
604 from_camera(int camera_index) {
605  clear();
606 
607  _capture = cvCaptureFromCAM(camera_index);
608  if (_capture == nullptr) {
609  return false;
610  }
611  _camera_index = camera_index;
612  return true;
613 }
614 
615 /**
616  * Stops the video playback and frees the associated resources.
617  */
618 void OpenCVTexture::VideoStream::
619 clear() {
620  if (_capture != nullptr) {
621  cvReleaseCapture(&_capture);
622  _capture = nullptr;
623  }
624  _filename = Filename();
625  _camera_index = -1;
626  _next_frame = 0;
627 }
628 
629 #endif // HAVE_OPENCV
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Specifies parameters that may be passed to the loader.
Definition: loaderOptions.h:23
std::string get_basename_wo_extension() const
Returns the basename part of the filename, without the file extension.
Definition: filename.I:386
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The base class for a family of animated Textures that take their input from a video source,...
Definition: videoTexture.h:28
This template class calls PipelineCycler::read_unlocked(), and then provides a transparent read-only ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition: clockObject.h:94
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
An instance of this class is written to the front of a Bam or Txo file to make the file a cached inst...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void register_factory(TypeHandle handle, CreateFunc *func, void *user_data=nullptr)
Registers a new kind of thing the Factory will be able to create.
Definition: factory.I:73
This template class calls PipelineCycler::write() in the constructor and PipelineCycler::release_writ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
void add_dependent_file(const Filename &pathname)
Adds the indicated file to the list of files that will be loaded to generate the data in this record.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1123