Now we begin to learn the methods of processing video. Values of pixels in the current video frame can be read in a way similar to the one in ofImage
, using access to its pixel array data:
unsigned char *getPixels()
Also, here is the
getPixelRef()
method, which returns a reference to a pixel array of the current frame, represented by a special class ofPixels
.
It is easy to get the pixel color using pixels.getColor( x, y )
, which returns the ofColor
value of pixel ( x,y )
. Compared to directly working with pixels data using getPixels()
, it is a relatively slow operation, but is more simple and convenient. Note, ofPixels
has useful properties such as getWidth()
and getHeight()
that are used for getting the width and height, and also the channels
value, which holds the number of channels in the image (1
, 3
, or 4
).
See the example where we read the colors of center horizontal line pixels and draw vertical lines with the corresponding colors. The project is similar to the previous example, only testApp::draw()
is changed:
void testApp::draw(){ //Getting reference to the pixel array ofPixels &pixels = video.getPixelsRef(); //Define variables w, h equal to frame width and height int w = pixels.getWidth(); int h = pixels.getHeight(); //Scan center horizontal line for (int x=0; x<w; x++) { //Getting color of the center line ofColor color = pixels.getColor( x, h / 2 ); //Draw a vertical line using this color ofSetColor( color ); ofLine( x, 0, x, h ); } }
Running this example, you will see a movie with frames built from a set of colored vertical lines. The color of each line is taken from the central horizontal line of the original movie's frame:
The important thing in the example is using the &
symbol in the line:
ofPixels &pixels = video.getPixelRef();
This symbol means that we are getting just a reference of the pixel array data, and not copying the data itself. So this is an extremely fast operation, just like a pointer assignment. But when the next frame is obtained, we will lose old pixels data and it will be a reference on the new frame, or maybe some memory error can occur. Instead, if we call it another way, without &
:
ofPixels pixels = video.getPixelRef();
The data will be copied to pixels
, and stored there as long as we want. It is safe, but consumes a lot of memory and hence is a consuming operation (that can be noticeable for large frame size).
It is possible to change pixel colors in ofPixels
, using pixels.setColor( x, y, color )
with color value of type ofColor
. But the pixel array obtained from the video frame is not intended for changing its image, so you will not see the changes drawing it with video.draw()
. If you want to draw the changed pixels, create an ofImage
object and load the pixels array into this image, using the setFromPixels()
function.
Let's consider an example where we change the color components of all the framed pixels using a random-generated table and draw the resulting image. Also, the example demonstrates the usage of the video.isFrameNew()
function.
It is based on the emptyExample
project in openFrameworks. Before running it, copy the handsTrees.mov
file into the bin/data
folder of your project.
In the testApp.h
file inside the testApp
class declaration, add the following lines:
ofVideoPlayer video; //Declare video player object ofImage image; //Declare image object int table[16]; //Declare table for color replacing
In the testApp.cpp
file, fill the bodies of the setup()
, update()
, and draw()
functions in the following way:
void testApp::setup(){ video.loadMovie( "handsTrees.mov" ); //Load video file video.play(); //Start video to play //Fill the table by random values from 0 to 255 for ( int i=0; i<16; i++ ) { table[i] = ofRandom( 0, 255 ); } } void testApp::update(){ video.update(); //Decode the new frame if needed //Do computing only if a new frame was obtained if ( video.isFrameNew() ) { //Getting pixels ofPixels pixels = video.getPixelsRef(); //Scan all the pixels for (int y=0; y<pixels.getHeight(); y++) { for (int x=0; x<pixels.getWidth(); x++) { //Getting pixel (x,y) color ofColor col = pixels.getColor( x, y ); //Change color components of col //using table col.r = table[ col.r/16 ]; col.g = table[ col.g/16 ]; col.b = table[ col.b/16 ]; //Set the color back to the pixel (x,y) pixels.setColor( x, y, col ); } } //Set pixel array to the image image.setFromPixels( pixels ); } } void testApp::draw(){ ofBackground( 255, 255, 255 ); //Set white background //Draw the image ofSetColor( 255, 255, 255 ); image.draw(0,0); }
When you start the application, you will see the movie with changed pixel colors. The rule for color changing is fixed during application execution. A color replace table is constructed at startup using random number generator; so each time you run the application, you will obtain a different result. The following screenshot shows us an example:
The rule for color replacing is held in a random-generated table:
for ( int i=0; i<16; i++ ) { table[i] = ofRandom( 0, 255 ); }
And the main operation of the example is changing the color using table
:
col.r = table[ col.r/16 ]; col.g = table[ col.g/16 ]; col.b = table[ col.b/16 ];
Indeed, each color component lies in range [0, 255]
, so col.r/16
, col.g/16
, and col.g/16
lies in range [0, 15]
. Our table has size 16
, so operation is correct. Note that if we create table of size 256, we can use simple operations as follows:
col.r = table[ col.r ]; col.g = table[ col.g ]; col.b = table[ col.b ];
The resultant image will be much more "color-sprayed". (Before running the project with such modification, you need to replace in code all constants from 16
to 256
.)