LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   OpenCV libraries warp perspective will this work (https://www.linuxquestions.org/questions/programming-9/opencv-libraries-warp-perspective-will-this-work-948610/)

mreff555 06-05-2012 07:34 AM

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?

Nominal Animal 06-05-2012 08:17 AM

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.

mreff555 06-05-2012 09:01 AM

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.

Nominal Animal 06-05-2012 12:46 PM

1 Attachment(s)
Quote:

Originally Posted by mreff555 (Post 4696010)
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.

mreff555 06-05-2012 01:55 PM

It works great! Thanks.


All times are GMT -5. The time now is 08:10 PM.