Monday, 6 January 2014

Image Capture on linux

Spontaneous Projects is a chronicle of all of my projects, a reference for me, and a way to prevent others from making the same mistakes. 

For the first part of migrating to using the netbook as a computer vision device, I need to get an image from it.
I used a program called uvccapture as a basis, and modified it heavily, so that it runs in YUY mode all of the time, and has a lot less options, as I plan to use a modified version of this to capture for actual processing later.

The following was developed for a EeePC 701sd, running lucid puppy and compiled using g++ and a make file, the name is still the original, as I haven't fully got my head around how the makefiles work yet, so I kept it simple.
Lucid Puppy, (right) an older image showing both lack of centering and bad colours
(left) a recent pic, taken at the full res of the camera, in worse lighting, but with 1 second for the camera to adjust



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <jpeglib.h>
#include <time.h>
#include <linux/videodev.h>

#include "v4l2uvc.h"

static const char version[] = VERSION;
int run = 1;

void
sigcatch (int sig)
{
  fprintf (stderr, "Exiting...\n");
  run = 0;
}

void
usage (void)
{
  fprintf (stderr, "uvccapture version %s\n", version);
  fprintf (stderr, "Usage is: uvccapture [options]\n");
  fprintf (stderr, "Options:\n");
  //fprintf (stderr, "-v\t\tVerbose\n");
  //fprintf (stderr, "-o<filename>\tOutput filename(default: snap.jpg)\n");
  //fprintf (stderr, "-d<device>\tV4L2 Device(default: /dev/video0)\n");
  //fprintf (stderr,
  //"-x<width>\tImage Width(must be supported by device)(>960 activates YUYV capture)\n");
  //fprintf (stderr,
  //"-y<height>\tImage Height(must be supported by device)(>720 activates YUYV capture)\n");
  //fprintf (stderr,
  //"-c<command>\tCommand to run after each image capture(executed as <command> <output_filename>)\n");
  //fprintf (stderr,
  //"-t<integer>\tTake continuous shots with <integer> seconds between them (0 for single shot)\n");
  //fprintf (stderr,
  //"-q<percentage>\tJPEG Quality Compression Level (activates YUYV capture)\n");
  //fprintf (stderr, "-r\t\tUse read instead of mmap for image capture\n");
  //fprintf (stderr,
  //"-w\t\tWait for capture command to finish before starting next capture\n");
  fprintf (stderr, "-m\t\tToggles capture mode to YUYV capture\n");
  //fprintf (stderr, "Camera Settings:\n");
  //fprintf (stderr, "-B<integer>\tBrightness\n");
  //fprintf (stderr, "-C<integer>\tContrast\n");
  //fprintf (stderr, "-S<integer>\tSaturation\n");
  //fprintf (stderr, "-G<integer>\tGain\n");
  exit (8);
}

int
spawn (char *argv[], int wait, int verbose)
{
  pid_t pid;
  int rv;

  switch (pid = fork ()) {
  case -1:
    return -1;
  case 0:
    // CHILD
    execvp (argv[0], argv);
    fprintf (stderr, "Error executing command '%s'\n", argv[0]);
    exit (1);
  default:
    // PARENT
    if (wait == 1)  
      waitpid (pid, &rv, 0);
 
    else {
      // Clean zombies
      waitpid (-1, &rv, WNOHANG);
      rv = 0;
    }
    break;
  }

  return rv;
}

int
compress_yuyv_to_jpeg (struct vdIn *vd, FILE * file, int quality)
{
  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;
  JSAMPROW row_pointer[1];
  unsigned char *line_buffer, *yuyv;
  int z;
  sleep(1);
  line_buffer = calloc (vd->width * 3, 1);
  yuyv = vd->framebuffer;

  cinfo.err = jpeg_std_error (&jerr);
  jpeg_create_compress (&cinfo);
  jpeg_stdio_dest (&cinfo, file);

  cinfo.image_width = vd->width;
  cinfo.image_height = vd->height;
  cinfo.input_components = 3;
  cinfo.in_color_space = JCS_RGB;

  jpeg_set_defaults (&cinfo);
  jpeg_set_quality (&cinfo, quality, TRUE);

  jpeg_start_compress (&cinfo, TRUE);

  z = 0;
  while (cinfo.next_scanline < cinfo.image_height) {
    int x;
    unsigned char *ptr = line_buffer;

    for (x = 0; x < vd->width; x++) {
      int r, g, b;
      int y, u, v;

      if (!z)
y = yuyv[0] << 8;
      else
y = yuyv[2] << 8;
      u = yuyv[1] - 128;
      v = yuyv[3] - 128;

      r = (y + (359 * v)) >> 8;
      g = (y - (88 * u) - (183 * v)) >> 8;
      b = (y + (454 * u)) >> 8;

      *(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
      *(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
      *(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);

      if (z++) {
z = 0;
yuyv += 4;
      }
    }

    row_pointer[0] = line_buffer;
    jpeg_write_scanlines (&cinfo, row_pointer, 1);
  }

  jpeg_finish_compress (&cinfo);
  jpeg_destroy_compress (&cinfo);

  free (line_buffer);

  return (0);
}

int
main (int argc, char *argv[])
{
  char *videodevice = "/dev/video0";
  char *outputfile = "new.jpg";

  //int format = V4L2_PIX_FMT_MJPEG; // OR
  int format = V4L2_PIX_FMT_YUYV;

  int grabmethod = 1;
  int width = 640;
  int height = 480;
  int brightness = 0, contrast = 0, saturation = 0;
  int delay = 0; // increased, may sort out the wrap issues, results in other issues, but fixes colours
  int quality = 95;
  time_t ref_time;
  struct vdIn *videoIn;
  FILE *file;

  (void) signal (SIGINT, sigcatch);
  (void) signal (SIGQUIT, sigcatch);
  (void) signal (SIGKILL, sigcatch);
  (void) signal (SIGTERM, sigcatch);
  (void) signal (SIGABRT, sigcatch);
  (void) signal (SIGTRAP, sigcatch);



  //Options Parsing (FIXME)
  while ((argc > 1) && (argv[1][0] == '-')) {
    switch (argv[1][1]) {

    case 'm':
       format = V4L2_PIX_FMT_YUYV;
      break;

    default: // this an unknown was entered case, nb -h is that
      fprintf (stderr, "Unknown option %s \n", argv[1]);
      format = V4L2_PIX_FMT_YUYV; // added here
      usage ();
    }
    ++argv;
    --argc;
  }


  videoIn = (struct vdIn *) calloc (1, sizeof (struct vdIn));
  if (init_videoIn
      (videoIn, (char *) videodevice, width, height, format, grabmethod) < 0)
    exit (1);

  //Reset all camera controls

  v4l2ResetControl (videoIn, V4L2_CID_BRIGHTNESS);
  v4l2ResetControl (videoIn, V4L2_CID_CONTRAST);
  v4l2ResetControl (videoIn, V4L2_CID_SATURATION);
  //v4l2ResetControl (videoIn, V4L2_CID_GAIN);

  //Setup Camera Parameters
  if (brightness != 0)
    v4l2SetControl (videoIn, V4L2_CID_BRIGHTNESS, brightness);

  if (contrast != 0)
      v4l2SetControl (videoIn, V4L2_CID_CONTRAST, contrast);

  if (saturation != 0)
     v4l2SetControl (videoIn, V4L2_CID_SATURATION, saturation);
 
  //if (gain != 0)    // It appears that gain does not exist, in this format, on this camera... who knows?
    //v4l2SetControl (videoIn, V4L2_CID_GAIN, gain);
 
 
  ref_time = time (NULL);

  while (run) {
 
    if (uvcGrab (videoIn) < 0) {
      fprintf (stderr, "Error grabbing\n");
      close_v4l2 (videoIn);
      free (videoIn);
      exit (1);
    }

    if ((difftime (time (NULL), ref_time) > delay) || delay == 0) {

      file = fopen (outputfile, "wb");
      if (file != NULL) {
switch (videoIn->formatIn) {
case V4L2_PIX_FMT_YUYV:
 compress_yuyv_to_jpeg (videoIn, file, quality);
 break;
default:
 fwrite (videoIn->tmpbuffer, videoIn->buf.bytesused + DHT_SIZE, 1,
 file);
 break;
}
fclose (file);
videoIn->getPict = 0;
      }
   
      ref_time = time (NULL);
    }
    if (delay == 0)
      break;
  }
  close_v4l2 (videoIn);
  free (videoIn);

  return 0;
}


Some Notes:

  • uvccapture uses some strange settings when initialising the camera, if there is not a delay to allow the camera to choose it's own settings it can be out of alignment, invert the colours, or be over/under saturated.
  • The gain function doesn't seem to exist for this setup, and throws an error 22 bug, once I commented out those lines it fixed itself. I'm not sure if it's hardware, software, or something else
  • There is likely still an amount of this that could be cut down, but until I know how I need the images to use them it will stay as it is. 


Early images, note the wrapping issue, and red appears blue

Recent image, with these issues corrected

No comments:

Post a Comment