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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An instance of this class is written to the front of a Bam or Txo file to make the file a cached inst...
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.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition: clockObject.h:94
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
This template class calls PipelineCycler::read_unlocked(), and then provides a transparent read-only ...
This template class calls PipelineCycler::write() in the constructor and PipelineCycler::release_writ...
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
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
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
std::string get_basename_wo_extension() const
Returns the basename part of the filename, without the file extension.
Definition: filename.I:386
Specifies parameters that may be passed to the loader.
Definition: loaderOptions.h:23
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
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
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
The base class for a family of animated Textures that take their input from a video source,...
Definition: videoTexture.h:28
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.