LinuxQuestions.org
View the Most Wanted LQ Wiki articles.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices

Reply
 
LinkBack Search this Thread
Old 06-05-2012, 07:34 AM   #1
mreff555
Member
 
Registered: Sep 2011
Location: Philly
Distribution: Gentoo
Posts: 435

Rep: Reputation: Disabled
OpenCV libraries warp perspective will this work


First, I realize opencv has a forum but I have posted multiple times without any useful responses. I like this board better anyway.

I'm just warming up to openCV and I'm trying to accomplish at least what I think are some difficult manipulations. Currently I have a very large single channel black and white tif file.

There are many black dots which create a large symmetrical ring
with a white background. What I need to do is turn that ring in to a straight line. In doing so the inner dots would become much longer. I am trying to use opencv's cvWarpPerspective() to accomplish this with no luck. does anyone have any other recommendations?
 
Old 06-05-2012, 08:17 AM   #2
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
This sounds interesting. If I understand you correctly, the image is some sort of a hemispherical view (maybe using a fisheye lens), with the ring being horizontal, and you wish to transform it into a cylindrical projection (a panorama). In other words, turn a fisheye view into a panorama. Is this correct?

I don't use OpenCV, so I cannot tell you how to do the transform using OpenCV, but it is not difficult to accomplish in basic mathematical terms, for example using your own code.
 
Old 06-05-2012, 09:01 AM   #3
mreff555
Member
 
Registered: Sep 2011
Location: Philly
Distribution: Gentoo
Posts: 435

Original Poster
Rep: Reputation: Disabled
Not exactly. here is my current output Image
https://www.dropbox.com/s/8p3rvu94qifv6sd/out.tif

Here is a rough idea of what it eventually needs to look like
https://www.dropbox.com/s/2bplc9lh88...ith%20GS60.TIF

In this case the source image is different and there are obviously other Image transformations applied. I think the can figure those out.
Straightening out the Image is what is really confusing me.
 
Old 06-05-2012, 12:46 PM   #4
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
Quote:
Originally Posted by mreff555 View Post
Not exactly.
The basic mapping is
Code:
   angle:         angle of target image left edge in source image, radians
   center_x, 
   center_y:      centerpoint in source image
   top_radius:    radius in source image that corresponds to target image top edge
   bottom_radius: radius in source image that corresponds to target image bottom edge

   a = angle + 2 * Pi * target_x / target_width
   r = function( target_y / target_height ) * (bottom_radius - top_radius) + top_radius
   source_x = center_x + r * cos(angle + 2 * Pi * target_x / target_width)
   source_y = center_y + r * sin(angle + 2 * Pi * target_x / target_width)
where function depends on the projection. In your case, simple r = target_y / target_height * (bottom_radius - top_radius) + top_radiusshould work well, although circles will not stay exactly circles. You can use a different function to compensate, but then details will be relatively larger the closer they are to the center of the ring.

Here is a simple C program I cobbled together. It consumes a PGM image, and produces one:
Code:
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include <errno.h>

#define PI          3.1415926535897932384626434
#define PI_PER_180  0.0174532925199432957692369

struct pgm {
    int             width;
    int             height;
    size_t          stride;
    uint8_t         pixel[];
};

/* Helper function: Skips blanks and PNM comments.
*/
static inline int pnm_next(FILE *const in, int c)
{
    while (1) {

        /* Skip blanks. */
        while (c == '\t' || c == '\n' || c == '\v' ||
               c == '\f' || c == '\r' || c == ' ')
            c = getc(in);

        /* Not a comment? */
        if (c != '#')
            return c;

        /* Skip comment line. */
        while (c != EOF && c != '\n' && c != '\r')
            c = getc(in);
    }
}

/* Helper function: Parse nonnegative integer.
*/
static inline int pnm_int(FILE *const in, int c, int *const result)
{
    int val = 0;

    /* Prime result with invalid value. */
    if (result)
        *result = -1;

    /* Parse decimal digits, checking for overflow. */
    while (c >= '0' && c <= '9') {
        const int old = val;
        val = 10 * val + c - '0';
        if (val < old)
            return EOF;
        c = getc(in);
    }

    /* Save result. */
    if (result)
        *result = val;

    return c;
}

/* Allocate a new, uninitialized pgm image.
 * Pixel data is pgm->pixel[x + y*PGM->stride].
*/
struct pgm *pgm_new(const int width, const int height)
{
    struct pgm *image;

    if (width < 1 || height < 1) {
        errno = EINVAL;
        return NULL;
    }

    image = malloc(sizeof (struct pgm) + (size_t)width * (size_t)height * (size_t)sizeof (uint8_t));
    if (!image) {
        errno = ENOMEM;
        return NULL;
    }

    image->width  = width;
    image->height = height;
    image->stride = (size_t)width;

    return image;
}

/* Save image as a 8-bit PGM file.
*/
int pgm_save(const struct pgm *const image, FILE *const out)
{
    int y;

    if (!image || !image->stride || !out)
        return errno = EINVAL;

    if (fprintf(out, "P5\n%d %d\n255\n", image->width, image->height) < 0)
        return errno = EIO;

    for (y = 0; y < image->height; y++)
        if (fwrite(image->pixel + (size_t)y * image->stride, (size_t)image->width, 1, out) != 1)
            return errno = EIO;

    if (fflush(out))
        return errno = EIO;

    return 0;
}

/* Load 8-bit PGM file as an image.
 * Note: Input samples are rescaled to 0..255.
*/
struct pgm *pgm_load(FILE *const in)
{
    struct pgm *image;
    size_t         size;
    int            width, height, max, c, x, y;

    if (!in) {
        errno = EINVAL;
        return NULL;
    }

    /* Only binary pgm files are supported. */
    if (getc(in) != 'P') {
        errno = EEXIST;
        return NULL;
    }
    if (getc(in) != '5') {
        errno = EEXIST;
        return NULL;
    }

    /* Skip whitespace, parse width. */
    c = pnm_next(in, getc(in));
    c = pnm_int(in, c, &width);
    if (c == EOF || width < 1) {
        errno = EEXIST;
        return NULL;
    }

    /* Skip whitespace, parse height. */
    c = pnm_next(in, c);
    c = pnm_int(in, c, &height);
    if (c == EOF || height < 1) {
        errno = EEXIST;
        return NULL;
    }

    /* Skip whitespace, parse maximum component value. */
    c = pnm_next(in, c);
    c = pnm_int(in, c, &max);
    if (c == EOF || max < 1 || max > 255) {
        errno = EEXIST;
        return NULL;
    }

    /* Calculate size needed for the graymap data. Check for overflow. */
    size = (size_t)width * (size_t)height;
    if (size / (size_t)width != (size_t)height) {
        errno = ENOMEM;
        return NULL;
    }
    if (size > (size_t)(size + sizeof (struct pgm))) {
        errno = ENOMEM;
        return NULL;
    }

    /* Allocate image, and fill in the fields. */
    image = malloc(sizeof (struct pgm) + size);
    if (!image) {
        errno = ENOMEM;
        return NULL;
    }
    image->width  = width;
    image->height = height;
    image->stride = (size_t)width;

    /* Read image data. */
    for (y = 0; y < height; y++)
        if (fread(image->pixel + (size_t)y * image->stride, (size_t)width, 1, in) != 1) {
            free(image);
            errno = EIO;
            return NULL;
        }

    /* Rescale 0..max to 0..255 */
    if (max > 0 && max < 255)
        for (y = 0; y < height; y++) {
            uint8_t *const row = image->pixel + (size_t)y * image->stride;
            for (x = 0; x < width; x++)
                row[x] = (255 * (int)(row[x])) / max;
        }

    /* Success. */
    return image;
}

/* Map a ring from source image into the target image.
 * Center of the ring is at (centerx, centery),
 * top (y=0) at radius rtop, bottom at radius rbottom,
 * with left edge (x=0) at angle (angle).
 * (For angle, 360 is full circle, 0 and 360 towards +x,
 *  90 towards +y.)
 * Note: This uses nearest-neighbor; no antialiasing.
*/
void pgm_ring(struct pgm *const target,
              const struct pgm *const source,
              const float centerx, const float centery,
              const float angle,
              const float rtop, const float rbottom,
              const uint8_t outside)
{
    const float a0 = angle * PI_PER_180;
    const float da = PI / (float)target->width;
    const float dr = (rbottom - rtop) / (float)(target->height - 1);
    int         x, y;

    for (y = 0; y < target->height; y++) {
        const float r = rtop + dr * (float)y;
        for (x = 0; x < target->width; x++) {
            const float a = a0 + da * (float)x;
            int         xc = (int)(0.5f + centerx + r * cos(a));
            int         yc = (int)(0.5f + centery + r * sin(a));
            if (xc >= 0 && xc < source->width &&
                yc >= 0 && yc < source->height)
                target->pixel[y * target->stride + x] = source->pixel[yc * source->stride + xc];
            else
                target->pixel[y * target->stride + x] = outside;
        }
    }
}


int main(void)
{
    struct pgm  *in;
    struct pgm  *out;
    int          radius;

    /* Read source image from standard input. */
    in = pgm_load(stdin);
    if (!in) {
        fprintf(stderr, "Standard input is not a valid PGM image.\n");
        return 1;
    }
    fprintf(stderr, "Read %dx%d PGM image.\n", in->width, in->height);
    fflush(stderr);

    /* Radius depends on the shorter edge length. */
    if (in->width < in->height)
        radius = in->width / 2;
    else
        radius = in->height / 2;

    /* Create an image about 2*radius in width, radius/4 in height. */
    out = pgm_new((int)(2.0943951f * radius), (int)(0.25f * radius));
    if (!out) {
        fprintf(stderr, "Failed to create a new PGM image.\n");
        return 1;
    }

    /* Map the source image (radius .. radius/2). */
    pgm_ring(out, in,
             0.5f * in->width, 0.5f * in->height,
             90.0f,
             1.00f * radius, 0.667f * radius,
             (uint8_t)255);

    /* Save the result to standard output. */
    if (pgm_save(out, stdout)) {
        fprintf(stderr, "Error writing PGM image to standard output.\n");
        return 1;
    }

    fprintf(stderr, "Successfully saved %dx%d PGM image to standard output.\n", out->width, out->height);

    free(out);
    free(in);

    return 0;
}
If you save it as pgmring.c, you can compile it using
Code:
gcc pgmring.c -Wall -O3 -o pgmring
It takes in a PGM image, and outputs the result as another, so to run with your TIFF images, install the netpbm package (for PBM tools), and run
Code:
tifftopnm out.tif | ./pgmring | pnmtotiff > result.tif
Attached is the resulting image converted to PNG instead (for smaller size).

The key function is very simple:
Code:
struct pgm {
    int             width;
    int             height;
    size_t          stride;
    uint8_t         pixel[];
};

void pgm_ring(struct pgm *const target,
              const struct pgm *const source,
              const float centerx, const float centery,
              const float angle,
              const float rtop, const float rbottom,
              const uint8_t outside)
{
    const float a0 = angle * PI_PER_180;
    const float da = PI / (float)target->width;
    const float dr = (rbottom - rtop) / (float)(target->height - 1);
    int         x, y;

    for (y = 0; y < target->height; y++) {
        const float r = rtop + dr * (float)y;
        for (x = 0; x < target->width; x++) {
            const float a = a0 + da * (float)x;
            int         xc = (int)(0.5f + centerx + r * cos(a));
            int         yc = (int)(0.5f + centery + r * sin(a));
            if (xc >= 0 && xc < source->width &&
                yc >= 0 && yc < source->height)
                target->pixel[y * target->stride + x] = source->pixel[yc * source->stride + xc];
            else
                target->pixel[y * target->stride + x] = outside;
        }
    }
}
Here, r is a linear function of target image y coordinate; from rtop to rbottom. If you need specific features, like keeping the M5 dots circular, this is the function you'll need to modify. (I think r(y) = Y1 + Y2 / (y + Y3) should yield circular dots, detail size increasing as you go closer to center, but I'm too lazy to calculate how the constants Y1, Y2, Y3 depend on the target image height and top and bottom radii.)

Source x and y coordinates (xc and yc) are calculated in polar coordinates around the specified centerpoint, with angle increasing from +x to +y axes, and left edge of target image being at angle angle .

The function does not do any kind of interpolation; it uses the nearest neighbor algorithm. You can speed it up by precalculating the sin/cos table, and perhaps the r table as well; you don't even really need floating-point support.

For interpolation, the simplest option is to estimate the size and shape of the region in the source image that affects each target pixel -- remember, pixels are mathematically samples, not rectangular dots -- then sample that region a few times randomly to obtain the sample in the target image. I recommend using Xorshift to generate the random numbers; it works very well for this, and is quite fast. Although mathematically one should vary the target image coordinates using something like a normal distribution, random sampling in the tetragonal area defined by the target image pixel "corners" works quite well in practice. You can extend the areas a bit to soften the image; you'll lose any crisp edges anyway, so a few percent overlap will typically enhance the observed image quality, although it does blur it very slightly.
Attached Images
File Type: png result.png (30.8 KB, 3 views)
 
Old 06-05-2012, 01:55 PM   #5
mreff555
Member
 
Registered: Sep 2011
Location: Philly
Distribution: Gentoo
Posts: 435

Original Poster
Rep: Reputation: Disabled
It works great! Thanks.
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off
Trackbacks are Off
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
OpenCV - Pixel format of incoming image is unsupported by OpenCV golmschenk Programming 1 04-01-2011 12:32 PM
WARP installation in F8... Sanjib_Sur Linux - Software 2 05-01-2009 01:15 PM
LXer: Perspective is as Perspective Does LXer Syndicated Linux News 0 07-21-2006 08:03 AM
Reinstall Grub after OS/2 warp 4 install ap0ll0 Linux - Software 6 02-15-2006 04:59 PM
OS/2 Warp RWild General 9 03-20-2003 05:59 PM


All times are GMT -5. The time now is 01:47 AM.

Main Menu
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration