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