Archive for May, 2011

Webcam reflection map

Saturday, May 28th, 2011

I experimented today with using the webcam’s video as a texture for a reflection map in OpenGL. The easiest, most satisfying result was to use the video as a sphere map. You have to reflect it the right way to look like a mirror image, and I give it a little barrel distortion to help with the effect. I also tried trying to fold the video feed into a cube map, but it was hard to determine a mapping that was smooth across faces. Here’s a video of the teapot fully reflective using the live webcam feed as a sphere map.


Download mp4 version

Update: Here’s the source of a small self-compiling GLUT demo using openCV and openGL:

#!/bin/bash
/*/../bin/ls > /dev/null
# BEGIN BASH SCRIPT
printf "//" | cat - $0 | g++ -o .main -framework AppKit -framework OpenGL -framework GLUT -lAntTweakBar -I/opt/local/include -L/opt/local/lib/ -lopencv_highgui -lopencv_core -lopencv_features2d -x c++ - -lopencv_imgproc && ./.main \
rm -f .main
# END BASH SCRIPT
exit
*/

#include <AntTweakBar.h>
#include <cstdio>
#include <opencv2/opencv.hpp> 
#ifdef __APPLE__
#   include <GLUT/glut.h>
#else
#   include <GL/glut.h>
#endif

// This example displays one of the following shapes
typedef enum { SHAPE_TEAPOT=1, SHAPE_TORUS, SHAPE_CONE, SHAPE_SPHERE, SHAPE_CUBE } Shape;
#define NUM_SHAPES 5
Shape g_CurrentShape = SHAPE_TEAPOT;
// Shapes scale
float g_Zoom = 1.0f;
// Shape orientation (stored as a quaternion)
float g_Rotation[] = { 0.0f, 0.0f, 0.0f, 1.0f };
// Auto rotate
int g_AutoRotate = 0;
int g_RotateTime = 0;
float g_RotateStart[] = { 0.0f, 0.0f, 0.0f, 1.0f };
// Shapes material
float g_MatAmbient[] = { 0.1f, 0.1f, 0.1f, 1.0f };
float g_MatDiffuse[] = { 0.9f, 0.9f, 0.9f, 1.0f };
// Light parameter
float g_LightMultiplier = 1.0f;
float g_LightDirection[] = { -0.57735f, -0.57735f, -0.57735f };

// type of wrapping 
int wrap = GL_CLAMP_TO_EDGE;
void update_cube_map(void);

// Webcam
bool webcam_texture = false;
void TW_CALL SetWebcamTextureCB(const void *value, void *clientData);
void TW_CALL GetWebcamTextureCB(void *value, void *clientData);

CvCapture* g_Capture;
int frame_height,frame_width;

void TW_CALL SetWebcamTextureCB(const void *value, void *clientData)
{
  webcam_texture = *((bool *) value);
  // initialize webcam
  if(webcam_texture)
  {
    // Create OpenCV camera capture
    // If multiple cameras are installed, this takes "first" one
    g_Capture = cvCaptureFromCAM(0);
    assert(g_Capture);
    // capture properties
    frame_height = 
      (int)cvGetCaptureProperty(g_Capture, CV_CAP_PROP_FRAME_HEIGHT);
    frame_width  = 
      (int)cvGetCaptureProperty(g_Capture, CV_CAP_PROP_FRAME_WIDTH);
    printf("%d %d\n",frame_width,frame_height);
  // uninitialize webcam
  }else
  {
    cvReleaseCapture(&g_Capture);
  }
  // remake cube map
  update_cube_map();
}
void TW_CALL GetWebcamTextureCB(void *value, void *clientData)
{
  bool * v = (bool *) value;
  *v= webcam_texture;
}

// Routine to set a quaternion from a rotation axis and angle
// ( input axis = float[3] angle = float  output: quat = float[4] )
void SetQuaternionFromAxisAngle(const float *axis, float angle, float *quat)
{
    float sina2, norm;
    sina2 = (float)sin(0.5f * angle);
    norm = (float)sqrt(axis[0]*axis[0] + axis[1]*axis[1] + axis[2]*axis[2]);
    quat[0] = sina2 * axis[0] / norm;
    quat[1] = sina2 * axis[1] / norm;
    quat[2] = sina2 * axis[2] / norm;
    quat[3] = (float)cos(0.5f * angle);

}


// Routine to convert a quaternion to a 4x4 matrix
// ( input: quat = float[4]  output: mat = float[4*4] )
void ConvertQuaternionToMatrix(const float *quat, float *mat)
{
    float yy2 = 2.0f * quat[1] * quat[1];
    float xy2 = 2.0f * quat[0] * quat[1];
    float xz2 = 2.0f * quat[0] * quat[2];
    float yz2 = 2.0f * quat[1] * quat[2];
    float zz2 = 2.0f * quat[2] * quat[2];
    float wz2 = 2.0f * quat[3] * quat[2];
    float wy2 = 2.0f * quat[3] * quat[1];
    float wx2 = 2.0f * quat[3] * quat[0];
    float xx2 = 2.0f * quat[0] * quat[0];
    mat[0*4+0] = - yy2 - zz2 + 1.0f;
    mat[0*4+1] = xy2 + wz2;
    mat[0*4+2] = xz2 - wy2;
    mat[0*4+3] = 0;
    mat[1*4+0] = xy2 - wz2;
    mat[1*4+1] = - xx2 - zz2 + 1.0f;
    mat[1*4+2] = yz2 + wx2;
    mat[1*4+3] = 0;
    mat[2*4+0] = xz2 + wy2;
    mat[2*4+1] = yz2 - wx2;
    mat[2*4+2] = - xx2 - yy2 + 1.0f;
    mat[2*4+3] = 0;
    mat[3*4+0] = mat[3*4+1] = mat[3*4+2] = 0;
    mat[3*4+3] = 1;
}


// Routine to multiply 2 quaternions (ie, compose rotations)
// ( input q1 = float[4] q2 = float[4]  output: qout = float[4] )
void MultiplyQuaternions(const float *q1, const float *q2, float *qout)
{
    float qr[4];
	qr[0] = q1[3]*q2[0] + q1[0]*q2[3] + q1[1]*q2[2] - q1[2]*q2[1];
	qr[1] = q1[3]*q2[1] + q1[1]*q2[3] + q1[2]*q2[0] - q1[0]*q2[2];
	qr[2] = q1[3]*q2[2] + q1[2]*q2[3] + q1[0]*q2[1] - q1[1]*q2[0];
	qr[3]  = q1[3]*q2[3] - (q1[0]*q2[0] + q1[1]*q2[1] + q1[2]*q2[2]);
    qout[0] = qr[0]; qout[1] = qr[1]; qout[2] = qr[2]; qout[3] = qr[3];
}

void sphere_webcam_map(void)
{
  // load webcam frame
  // Capture next frame
  // in the framerate, my webcam only gets ~15 fps
  IplImage * image = cvQueryFrame(g_Capture);
  cvFlip(image, NULL, -1);

  // Create Texture
  glPixelStorei(GL_UNPACK_ROW_LENGTH,image->widthStep/image->nChannels);
  glTexImage2D(
    GL_TEXTURE_2D,
    0,
    GL_RGB,
    image->width,
    image->height,
    0,
    GL_BGR,
    GL_UNSIGNED_BYTE,
    image->imageData);

}

void update_cube_map(void)
{
  if(webcam_texture)
  {
    //only_front_webcam_map();
    //cutout_cross_webcam_map();
    //linear_fold_webcam_map();
    sphere_webcam_map();
  }

}

void Idle(void)
{
  if(webcam_texture)
  {
    update_cube_map();
  }
}

// Callback function called by GLUT to render screen
void Display(void)
{

    float v[4]; // will be used to set light paramters
    float mat[4*4]; // rotation matrix

    // Clear frame buffer
    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glEnable(GL_NORMALIZE);

    // reflection map
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    if(webcam_texture)
    {
      glEnable(GL_TEXTURE_2D);
    }else
    {
      glDisable(GL_TEXTURE_2D);
    }
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);


    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_TEXTURE_GEN_R);

    // Set light
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    v[0] = v[1] = v[2] = g_LightMultiplier*0.4f; v[3] = 1.0f;
    glLightfv(GL_LIGHT0, GL_AMBIENT, v);
    v[0] = v[1] = v[2] = g_LightMultiplier*0.8f; v[3] = 1.0f;
    glLightfv(GL_LIGHT0, GL_DIFFUSE, v);
    v[0] = -g_LightDirection[0]; v[1] = -g_LightDirection[1]; v[2] = -g_LightDirection[2]; v[3] = 0.0f;
    glLightfv(GL_LIGHT0, GL_POSITION, v);

    // Set material
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, g_MatAmbient);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, g_MatDiffuse);

    // Rotate and draw shape
    glPushMatrix();
    //glTranslatef(0.5f, -0.3f, 0.0f);
    if( g_AutoRotate ) 
    {
        float axis[3] = { 0, 1, 0 };
        float angle = (float)(glutGet(GLUT_ELAPSED_TIME)-g_RotateTime)/1000.0f;
        float quat[4];
        SetQuaternionFromAxisAngle(axis, angle, quat);
        MultiplyQuaternions(g_RotateStart, quat, g_Rotation);
    }
    ConvertQuaternionToMatrix(g_Rotation, mat);
    glMultMatrixf(mat);
    glScalef(g_Zoom, g_Zoom, g_Zoom);
    glCallList(g_CurrentShape);
    glPopMatrix();

    glDisable(GL_TEXTURE_CUBE_MAP);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glDisable(GL_TEXTURE_GEN_R);
    // Draw tweak bars
    TwDraw();

    // Present frame buffer
    glutSwapBuffers();

    // Recall Display at next frame
    glutPostRedisplay();
}


// Callback function called by GLUT when window size changes
void Reshape(int width, int height)
{
    // Set OpenGL viewport and camera
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(40, (double)width/height, 1, 10);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0,0,5, 0,0,0, 0,1,0);
    //glTranslatef(0, 0.6f, -1);

    // Send the new window size to AntTweakBar
    TwWindowSize(width, height);
}


// Function called at exit
void Terminate(void)
{ 
    cvReleaseCapture(&g_Capture);

    glDeleteLists(SHAPE_TEAPOT, NUM_SHAPES);

    TwTerminate();
}


//  Callback function called when the 'AutoRotate' variable value of the tweak bar has changed
void TW_CALL SetAutoRotateCB(const void *value, void *clientData)
{
    (void)clientData; // unused

    g_AutoRotate = *(const int *)(value); // copy value to g_AutoRotate
    if( g_AutoRotate!=0 ) 
    {
        // init rotation
        g_RotateTime = glutGet(GLUT_ELAPSED_TIME);
        g_RotateStart[0] = g_Rotation[0];
        g_RotateStart[1] = g_Rotation[1];
        g_RotateStart[2] = g_Rotation[2];
        g_RotateStart[3] = g_Rotation[3];

        // make Rotation variable read-only
        TwDefine(" TweakBar/ObjRotation readonly ");
    }
    else
        // make Rotation variable read-write
        TwDefine(" TweakBar/ObjRotation readwrite ");
}


//  Callback function called by the tweak bar to get the 'AutoRotate' value
void TW_CALL GetAutoRotateCB(void *value, void *clientData)
{
    (void)clientData; // unused
    *(int *)(value) = g_AutoRotate; // copy g_AutoRotate to value
}

 
// Main
int main(int argc, char *argv[])
{
  // initialize constants

    TwBar *bar; // Pointer to the tweak bar
    float axis[] = { 0.7f, 0.7f, 0.0f }; // initial model rotation
    float angle = 0.8f;

    // Initialize AntTweakBar
    // (note that AntTweakBar could also be intialized after GLUT, no matter)
    if( !TwInit(TW_OPENGL, NULL) )
    {
        // A fatal error occured    
        fprintf(stderr, "AntTweakBar initialization failed: %s\n", TwGetLastError());
        return 1;
    }

    // Initialize GLUT
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(640, 480);
    glutCreateWindow("AntTweakBar simple example using GLUT");
    glutCreateMenu(NULL);

    // Set GLUT callbacks
    glutDisplayFunc(Display);
    glutIdleFunc(Idle);
    glutReshapeFunc(Reshape);
    atexit(Terminate);  // Called after glutMainLoop ends

    // Set GLUT event callbacks
    // - Directly redirect GLUT mouse button events to AntTweakBar
    glutMouseFunc((GLUTmousebuttonfun)TwEventMouseButtonGLUT);
    // - Directly redirect GLUT mouse motion events to AntTweakBar
    glutMotionFunc((GLUTmousemotionfun)TwEventMouseMotionGLUT);
    // - Directly redirect GLUT mouse "passive" motion events to AntTweakBar (same as MouseMotion)
    glutPassiveMotionFunc((GLUTmousemotionfun)TwEventMouseMotionGLUT);
    // - Directly redirect GLUT key events to AntTweakBar
    glutKeyboardFunc((GLUTkeyboardfun)TwEventKeyboardGLUT);
    // - Directly redirect GLUT special key events to AntTweakBar
    glutSpecialFunc((GLUTspecialfun)TwEventSpecialGLUT);
    // - Send 'glutGetModifers' function pointer to AntTweakBar;
    //   required because the GLUT key event functions do not report key modifiers states.
    TwGLUTModifiersFunc(glutGetModifiers);

    // Create some 3D objects (stored in display lists)
    glNewList(SHAPE_TEAPOT, GL_COMPILE);
    glutSolidTeapot(1.0);
    glEndList();
    glNewList(SHAPE_TORUS, GL_COMPILE);
    glutSolidTorus(0.3, 1.0, 16, 32);
    glEndList();
    glNewList(SHAPE_CONE, GL_COMPILE);
    glutSolidCone(1.0, 1.5, 64, 4);
    glEndList();
    glNewList(SHAPE_SPHERE, GL_COMPILE);
    glutSolidSphere(1.0, 64, 64);
    glEndList();
    glNewList(SHAPE_CUBE, GL_COMPILE);
    glutSolidCube(1.0);
    glEndList();

    // Create a tweak bar
    bar = TwNewBar("TweakBar");
    TwDefine(" GLOBAL help='This example shows how to integrate AntTweakBar with GLUT and OpenGL.' "); // Message added to the help bar.
    TwDefine(" TweakBar size='200 400' color='96 216 224' "); // change default tweak bar size and color

    // Add 'g_Zoom' to 'bar': this is a modifable (RW) variable of type TW_TYPE_FLOAT. Its key shortcuts are [z] and [Z].
    TwAddVarRW(bar, "Zoom", TW_TYPE_FLOAT, &g_Zoom, 
               " min=0.01 max=2.5 step=0.01 keyIncr=z keyDecr=Z help='Scale the object (1=original size).' ");

    // Add 'g_Rotation' to 'bar': this is a variable of type TW_TYPE_QUAT4F which defines the object's orientation
    TwAddVarRW(bar, "ObjRotation", TW_TYPE_QUAT4F, &g_Rotation, 
               " label='Object rotation' open help='Change the object orientation.' ");

    // Add callback to toggle auto-rotate mode (callback functions are defined above).
    TwAddVarCB(bar, "AutoRotate", TW_TYPE_BOOLCPP, SetAutoRotateCB, GetAutoRotateCB, NULL, 
               " label='Auto-rotate' key=space help='Toggle auto-rotate mode.' ");

    // Add 'g_LightMultiplier' to 'bar': this is a variable of type TW_TYPE_FLOAT. Its key shortcuts are [+] and [-].
    TwAddVarRW(bar, "Multiplier", TW_TYPE_FLOAT, &g_LightMultiplier, 
               " label='Light booster' min=0.1 max=4 step=0.02 keyIncr='+' keyDecr='-' help='Increase/decrease the light power.' ");

    // Add 'g_LightDirection' to 'bar': this is a variable of type TW_TYPE_DIR3F which defines the light direction
    TwAddVarRW(bar, "LightDir", TW_TYPE_DIR3F, &g_LightDirection, 
               " label='Light direction' open help='Change the light direction.' ");

    // Add 'g_MatAmbient' to 'bar': this is a variable of type TW_TYPE_COLOR3F (3 floats color, alpha is ignored)
    // and is inserted into a group named 'Material'.
    TwAddVarRW(bar, "Ambient", TW_TYPE_COLOR3F, &g_MatAmbient, " group='Material' ");

    // Add 'g_MatDiffuse' to 'bar': this is a variable of type TW_TYPE_COLOR3F (3 floats color, alpha is ignored)
    // and is inserted into group 'Material'.
    TwAddVarRW(bar, "Diffuse", TW_TYPE_COLOR3F, &g_MatDiffuse, " group='Material' ");

    // Add the enum variable 'g_CurrentShape' to 'bar'
    // (before adding an enum variable, its enum type must be declared to AntTweakBar as follow)
    {
        // ShapeEV associates Shape enum values with labels that will be displayed instead of enum values
        TwEnumVal shapeEV[NUM_SHAPES] = { 
          {SHAPE_TEAPOT, "Teapot"},
          {SHAPE_TORUS, "Torus"}, 
          {SHAPE_CONE, "Cone"}, 
          {SHAPE_SPHERE,"Sphere"},
          {SHAPE_CUBE,"Cube"}};
        // Create a type for the enum shapeEV
        TwType shapeType = TwDefineEnum("ShapeType", shapeEV, NUM_SHAPES);
        // add 'g_CurrentShape' to 'bar': this is a variable of type ShapeType. Its key shortcuts are [<] and [>].
        TwAddVarRW(bar, "Shape", shapeType, &g_CurrentShape, " keyIncr='<' keyDecr='>' help='Change object shape.' ");
    }

    TwAddVarCB(
      bar, 
      "webcam_texture", 
      TW_TYPE_BOOLCPP, 
      SetWebcamTextureCB,
      GetWebcamTextureCB,
      NULL,
      "group=Texture key=W help='Use webcam as ref map texture.' ");

    // Store time
    g_RotateTime = glutGet(GLUT_ELAPSED_TIME);
    // Init rotation
    SetQuaternionFromAxisAngle(axis, angle, g_Rotation);
    SetQuaternionFromAxisAngle(axis, angle, g_RotateStart);

    // Call the GLUT main loop
    glutMainLoop();

    return 0;
}

Using webcam video as texture in OpenGL

Saturday, May 28th, 2011

video textures with opengl and opencv screen capture

In experimenting with using video input I was very happy to find out how easy openCV makes accessing a computer’s webcam. In my first test program I just wanted to feed the webcam video to a texture on a rectangle that fits the entire screen. I modified code that does this for a windows app, but had a number of problems on mac. Here’s my version, save it in VideoTexture.cpp:


//////////////////////////////////////////////////////////////////////////////
// Modified from "Video Texture" code
// Copyright (C) 2009  Arsalan Malik (arsalank2@hotmail.com)
//                                                                            
// On Mac OS X, compile with:
// g++ -o VideoTexture VideoTexture.cpp -framework OpenGL -framework Glut -I
// /usr/local/include/opencv/ $(pkg-config --libs opencv)
//////////////////////////////////////////////////////////////////////////////


// Open CV includes
#include <cv.h>
#include <highgui.h>

// Standard includes
#include <stdio.h>
#include <string.h>
#include <assert.h>

// OpenGL/Glut includes
#ifdef __APPLE__
#  include <GLUT/glut.h>
#else
#  include <GL/glut.h>
#endif

// Timing includes
#include <sys/time.h>

#define KEY_ESCAPE 27


CvCapture* g_Capture;
GLint g_hWindow;

// Frame size
int frame_width  = 640;
int frame_height = 480;

// current frames per second, slightly smoothed over time
double fps;
// show mirror image
bool mirror = true;

// Return current time in seconds
double current_time_in_seconds();
// Initialize glut window
GLvoid init_glut();
// Glut display callback, draws a single rectangle using video buffer as
// texture
GLvoid display();
// Glut reshape callback
GLvoid reshape(GLint w, GLint h);
// Glut keyboard callback
GLvoid key_press (unsigned char key, GLint x, GLint y);
// Glut idle callback, fetches next video frame
GLvoid idle();

double current_time_in_seconds()
{
  timeval timer;
  gettimeofday(&timer,NULL);
  double seconds = 1e-6 * timer.tv_usec + timer.tv_sec;
  return seconds;
}


GLvoid init_glut()
{  

  glClearColor (0.0, 0.0, 0.0, 0.0);

  // Set up callbacks
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(key_press);
  glutIdleFunc(idle);
}

GLvoid display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glEnable(GL_TEXTURE_2D);
  // These are necessary if using glTexImage2D instead of gluBuild2DMipmaps
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

  // Set Projection Matrix
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(0, frame_width, frame_height, 0);

  // Switch to Model View Matrix
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  // Draw a textured quad
  glBegin(GL_QUADS);
  glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f);
  glTexCoord2f(1.0f, 0.0f); glVertex2f(frame_width, 0.0f);
  glTexCoord2f(1.0f, 1.0f); glVertex2f(frame_width, frame_height);
  glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f, frame_height);
  glEnd();

  glFlush();
  glutSwapBuffers();
}


GLvoid reshape(GLint w, GLint h)
{
  glViewport(0, 0, w, h);
}

GLvoid key_press(unsigned char key, int x, int y)
{
  switch (key)
  {
    case 'f':
      printf("fps: %g\n",fps);
      break;
    case 'm':
      mirror = !mirror;
      break;
    case KEY_ESCAPE:
      cvReleaseCapture(&g_Capture);
      glutDestroyWindow(g_hWindow);
      exit(0);
      break;
  }
  glutPostRedisplay();
}


GLvoid idle()
{
  // start timer
  double start_seconds = current_time_in_seconds();

  // Capture next frame, this will almost always be the limiting factor in the
  // framerate, my webcam only gets ~15 fps
  IplImage * image = cvQueryFrame(g_Capture);

  // Of course there are faster ways to do this with just opengl but this is to
  // demonstrate filtering the video before making the texture
  if(mirror)
  {
    cvFlip(image, NULL, 1);
  }

  // Image is memory aligned which means we there may be extra space at the end
  // of each row. gluBuild2DMipmaps needs contiguous data, so we buffer it here
  char * buffer = new char[image->width*image->height*image->nChannels];
  int step     = image->widthStep;
  int height   = image->height;
  int width    = image->width;
  int channels = image->nChannels;
  char * data  = (char *)image->imageData;
  // memcpy version below seems slightly faster
  //for(int i=0;i<height;i++)
  //for(int j=0;j<width;j++)
  //for(int k=0;k<channels;k++)
  //{
  //  buffer[i*width*channels+j*channels+k] = data[i*step+j*channels+k];
  //}
  for(int i=0;i<height;i++)
  {
    memcpy(&buffer[i*width*channels],&(data[i*step]),width*channels);
  }

  // Create Texture
  glTexImage2D(
    GL_TEXTURE_2D,
    0,
    GL_RGB,
    image->width,
    image->height,
    0,
    GL_BGR,
    GL_UNSIGNED_BYTE,
    buffer);


  // Clean up buffer
  delete[] buffer;

  // Update display
  glutPostRedisplay();

  double stop_seconds = current_time_in_seconds();
  fps = 0.9*fps + 0.1*1.0/(stop_seconds-start_seconds);
}

int main(int argc, char* argv[])
{

  // Create OpenCV camera capture
  // If multiple cameras are installed, this takes "first" one
  g_Capture = cvCaptureFromCAM(0);
  assert(g_Capture);
  // capture properties
  frame_height = (int)cvGetCaptureProperty(g_Capture, CV_CAP_PROP_FRAME_HEIGHT);
  frame_width  = (int)cvGetCaptureProperty(g_Capture, CV_CAP_PROP_FRAME_WIDTH);

  // Create GLUT Window
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(frame_width, frame_height);

  g_hWindow = glutCreateWindow("Video Texture");

  // Initialize OpenGL
  init_glut();

  glutMainLoop();

  return 0;
}

Which on my mac I compile with:


g++ -o VideoTexture VideoTexture.cpp -framework OpenGL -framework Glut -I /usr/local/include/opencv/ $(pkg-config --libs opencv) -Os

Notice the little bit that grabs all of the opencv libraries for me:


pkg-config --libs opencv

Update: Will Patera has generously allowed me to post his python port of the above code. To run it you’ll need the python bindings for openGL and openCV and the numpy library.


import cv
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import numpy as np
import sys


#window dimensions
width = 1280
height = 720
nRange = 1.0

global capture
capture = None

def cv2array(im): 
  depth2dtype = { 
    cv.IPL_DEPTH_8U: 'uint8', 
    cv.IPL_DEPTH_8S: 'int8', 
    cv.IPL_DEPTH_16U: 'uint16', 
    cv.IPL_DEPTH_16S: 'int16', 
    cv.IPL_DEPTH_32S: 'int32', 
    cv.IPL_DEPTH_32F: 'float32', 
    cv.IPL_DEPTH_64F: 'float64', 
    } 

  arrdtype=im.depth 
  a = np.fromstring( 
     im.tostring(), 
     dtype=depth2dtype[im.depth], 
     count=im.width*im.height*im.nChannels) 
  a.shape = (im.height,im.width,im.nChannels) 
  return a

def init():
  #glclearcolor (r, g, b, alpha)
  glClearColor(0.0, 0.0, 0.0, 1.0)
  
  glutDisplayFunc(display)
  glutReshapeFunc(reshape)
  glutKeyboardFunc(keyboard)
  glutIdleFunc(idle)  
  
def idle():
  #capture next frame
  
  global capture
  image = cv.QueryFrame(capture)
  image_size = cv.GetSize(image)
  cv.Flip(image, None, 0)
  cv.Flip(image, None, 1)
  cv.CvtColor(image, image, cv.CV_BGR2RGB)
  #you must convert the image to array for glTexImage2D to work
  #maybe there is a faster way that I don't know about yet...
  image_arr = cv2array(image)
  #print image_arr

  
  # Create Texture
  glTexImage2D(GL_TEXTURE_2D, 
    0, 
    GL_RGB, 
    image_size[0], 
    image_size[1], 
    0,
    GL_RGB, 
    GL_UNSIGNED_BYTE, 
    image_arr)
  
  glutPostRedisplay()

def display():
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
  glEnable(GL_TEXTURE_2D)
  #glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
  #glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
  #glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)
  #this one is necessary with texture2d for some reason
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
  
  # Set Projection Matrix
  glMatrixMode(GL_PROJECTION)
  glLoadIdentity()
  gluOrtho2D(0, width, 0, height)
  
  # Switch to Model View Matrix
  glMatrixMode(GL_MODELVIEW)
  glLoadIdentity()
  
  # Draw textured Quads
  glBegin(GL_QUADS)
  glTexCoord2f(0.0, 0.0)
  glVertex2f(0.0, 0.0)
  glTexCoord2f(1.0, 0.0)
  glVertex2f(width, 0.0)
  glTexCoord2f(1.0, 1.0)
  glVertex2f(width, height)
  glTexCoord2f(0.0, 1.0)
  glVertex2f(0.0, height)
  glEnd()
    
  glFlush()
  glutSwapBuffers()
  
def reshape(w, h):
  if h == 0:
    h = 1
  
  glViewport(0, 0, w, h)
  glMatrixMode(GL_PROJECTION)
  
  glLoadIdentity()
  # allows for reshaping the window without distoring shape
  
  if w <= h:
    glOrtho(-nRange, nRange, -nRange*h/w, nRange*h/w, -nRange, nRange)
  else:
    glOrtho(-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange)
  
  glMatrixMode(GL_MODELVIEW)
  glLoadIdentity()
  
def keyboard(key, x, y):
  global anim
  if key == chr(27):
    sys.exit()
  
def main():
  global capture
  #start openCV capturefromCAM
  capture = cv.CaptureFromCAM(0)
  print capture
  cv.SetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH, width)
  cv.SetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT, height)
  
  glutInit(sys.argv)
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
  glutInitWindowSize(width, height)
  glutInitWindowPosition(100, 100)
  glutCreateWindow("OpenGL + OpenCV")
  
  init()
  glutMainLoop()
  
main()

Seamless cube map with opengl

Friday, May 27th, 2011

I was experimenting with cube maps today using opengl’s reflection map functions. At first I was very annoyed by the seams I was getting between the sides of my cubes reflected on the shape’s in the scene. Turns out I was using the wrong wrapping setting. You can set the texture wrapping via:


glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, wrap);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, wrap);

where wrap is a valid wrap enum value. For seamless reflection mapping use either GL_CLAMP_TO_EDGE or GL_MIRRORED_REPEAT. Here’s a comparison of the results of each valid setting:
comparison of gl_texture_wrap settings

Convert .vert/.tri pairs into obj with simple bash script

Friday, May 13th, 2011

Here’s a tiny bash script you can save in verttri2obj.sh to convert .vert/.tri pairs like those of the TOSCA mesh repository:


#!/bin/bash
cat $1 | sed -e "s/^/v /g" > $3
cat $2 | sed -e "s/^/f /g" >> $3

And call it with:


./verttri2obj.sh mesh.vert mesh.tri mesh.obj

Update: Here’s a little bash oneliner that uses the script above to convert all the .vert/.tri pairs in a directory to objs:


ls -1 *.vert | sed -e "s/\(.*\).vert/~\/path\/to\/verttri2obj.sh \1.vert \1.tri \1.obj/g" | bash

Converting the entire TASCO repository above took about a minute and a half on my computer.

Hide spill over when thick line meets up with thin line corner in illustrator

Friday, May 13th, 2011

There are at least two ways to prevent the sort of spill over that happens in illustrator when you have a thick line meeting up at the corner of a thin path:
thick line in corner wrong
First, you can select the thin line path and add an extra stroke which will hide the spill over. Do these by choosing Appearance > Add New Stroke then change the color of the new stroke to the background color and make the stroke width large enough to hide the spillover. In the stroke dialog be sure to choose Align stroke to outside:
add extra stroke to hide-spill.png

The other way is to make a copy of the thin line path and then select the copy and the thick line and choose Object > Clipping Mask > Make. You’ll probably have to rearrange the layers so that your original thin line path come out on top.

Both produce a nice result:
thick line in corner correct

Vim sometimes only partially syntax-highlighting for .tex files

Thursday, May 12th, 2011

Editting .tex files with vim I noticed that sometimes I got full syntax highlighting, but other times only certain keywords were markes and the colors were a bit different. Turns out this was because vim was occasionally recognizing files (based on keywords) as plaintex rather than tex. You can see what filetype vim thinks you have open by issuing:


:set ft

For a bit I was fixing this on a case-by-case basis, if vim’s highlighting was wrong I would issue:


:set ft=tex

and that would fix it for at least the current file and session.

The real permanent fix was to add the following to my ~/.vimrc file:


let g:tex_flavor = "latex"

Now all my .tex files are recognized as tex and not plaintex.

The million dollar question

Thursday, May 12th, 2011

How is an eyeball different than a belly button?

Printf/scanf with size_t

Thursday, May 12th, 2011

I was recently bamboozled by my own code when I switched from compiling for 32-bits (i386) to 64-bits (x86_64). I had a bunch of scanf/printf lines reading and printing size_t variables.

Here’s a test program in C++ that demonstrates the correct usage of the z option for reading in size_t:


#include <cstdio>
#include <limits>


int main(int argc,char * argv[])
{
  // Show size of size_t
  printf("sizeof(size_t): %lu\n",(long unsigned int)(sizeof(size_t)));
  // Show max size_t value: (2^8)^sizeof(size_t)
  long unsigned int max_size_t = std::numeric_limits<std::size_t>::max();
  printf("max_size_t: %lu\n",max_size_t);
  // read into size_t
  size_t a;
  int nr = sscanf(argv[1],"%zu",&a);
  // print size_t
  printf("argv[1]: %zu\n",a);
  return 0;
}

Meshlab scrolling shortcuts

Tuesday, May 10th, 2011

I always forget these:

action
scroll: zoom out zoom in
shift+scroll: perspective orthographic
cmd+scroll: bring in near clipping plane to camera send near clipping plane from camera
cmd+shift+scroll: send far clipping plane from camera bring far clipping plane to camera
drag: trackball
alt+drag: push object in z direction pull object in z direction
if you find the far clipping plane you can use this to “manipulate” it.
cmd+drag: pan in x y
shift+drag: downscale? upscale?
cmd+shift+drag: trackball on light direction

meshlab filter console output missing

Tuesday, May 10th, 2011

I upgraded to MeshLab version 1.3 and was frustrated that when I issued filters on a mesh like “Remove Duplicate Vertices” there was no longer a console output listing the results of the filter ( in this case how many vertices were removed).

The console apparently only appears if you have the Layers dialog open. So go to View > Show Layer Dialog, the console shows up at the bottom.

meshlab filter console

What’s really annoying is that when you change the focus away from mesh lab and then back to mesh lab the Layers Dialog conveniently closes itself. So I have to keep opening and reopening it.

Update: It seems that the bug that makes the Layers Dialog disappear only happens if the Layers Dialog is locked to the right of the main window. If you drag the top of the dialog completely outside of the main meshlab window and leave it dangling some place then it doesn’t disappear.