cimage-processingsubsampling

Subsampling an image in C


I am trying to subsample an image by taking every 2nd pixel, as follows:

void subsample(int height, int width, int colors, struct Image pixels_in, struct Image pixels_out) {
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            for (int k = 0; k < colors; ++k) {
                pixels_out.pixels[(i*width*colors) + (j*colors) + k] = pixels_in.pixels[(i*2*width*colors) + (j*2*colors) + k];
            }
        }
    }
}

with the following struct definition:

(image_struct.h)
    struct Image {
    int x;          // width
    int y;          // height
    int L;          // number of levels
    int rgb_clamp;  // max value of each color channel
    int *pixels;    // image pixels 
};

My ultimate goal is to build a gaussian pyramid, but I am stuck at subsampling because after the 3rd level there are weird artifacts and the images don't look right. See below:

pyramid level 1 level 1

pyramid level 2 level 2

pyramid level 3 - artifact begins here level 3

pyramid level 4 level 4

As you can see the problem begins at level 3 and gets carried on to the next ones. I work with images in .ppm format and when I examine them, there are weird overflow values way above the RGB limit of 255 which makes me think I am not accessing the memory right. I would greatly appreciate any thoughts or suggestions as to why this happens. Thanks in advance!

Edit: minimal reproducible example

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include "image_struct.h"
#define NUM_LEVELS 4

int main() {
    // read PPM image in P3 format
    struct Image img;
    FILE* fp = fopen("./img/cameraman_p3.ppm", "r");
    char header[3];
    int width, height, max_color;
    fscanf(fp,"%s", header);
    fscanf(fp, "%d %d", &img.x, &img.y); // x = width, y = height
    fscanf(fp, "%d", &img.rgb_clamp);
    img.L = 3; // color channels

    img.pixels = (int*)malloc(img.x*img.y*img.L*sizeof(int));
    
    // store image into img.pixels
    for (int i = 0; i < img.y; i++) {
        for (int j = 0; j < img.x; j++) {
            for (int k = 0; k < img.L; k++) {
                fscanf(fp, "%d", &(img.pixels[(i*img.x*img.L) + (j*img.L) + k]));
            }
        }
    }
    fclose(fp); // close input file

    // at this point the image is only in pixels array
    struct Image gauss_pyramid[NUM_LEVELS]; 

    gauss_pyramid[0].pixels = (int*)malloc(img.x*img.y*img.L*sizeof(int)); 
    memcpy(gauss_pyramid[0].pixels, img.pixels, img.x*img.y*img.L*sizeof(int));

    gauss_pyramid[0].x = img.x;
    gauss_pyramid[0].y = img.y;
    gauss_pyramid[0].L = img.L;
    gauss_pyramid[0].rgb_clamp = img.rgb_clamp;

    // build pyramid
    int cur_width, cur_height, prev_height, prev_width;
    for (int p = 1; p < NUM_LEVELS; p++) {
        gauss_pyramid[p].x = gauss_pyramid[p-1].x/2;
        gauss_pyramid[p].y = gauss_pyramid[p-1].y/2;
        gauss_pyramid[p].L = gauss_pyramid[p-1].L;
        gauss_pyramid[p].rgb_clamp = gauss_pyramid[p-1].rgb_clamp;
        
        prev_height = gauss_pyramid[p-1].y;
        prev_width = gauss_pyramid[p-1].x;
        cur_height = gauss_pyramid[p].y;
        cur_width = gauss_pyramid[p].x;

        gauss_pyramid[p].pixels = (int*)malloc(prev_width*prev_height*img.L*sizeof(int));


        subsample(prev_height, prev_width, img.L, gauss_pyramid[p-1], gauss_pyramid[p]);
    
        // save into new file
        char *filename;
        char s1[3];
        sprintf(s1, "%d", p);
        char *s0 = "./img/pyramid_lvl_";
        char *s2 = "_file.ppm";

        filename = malloc(strlen(s0) + strlen(s1) + strlen(s2) + 1);
        filename[0] = '\0';  // make it a string in mem
        strcat(filename, s0);
        strcat(filename, s1);
        strcat(filename, s2);

        if (remove(filename) == 0) {
            printf("Previous out_file deleted\n");
        }

        FILE *out_f = fopen(filename, "w");
        fprintf(out_f, "P3\n");
        fprintf(out_f, "%d %d\n", cur_width, cur_height);
        fprintf(out_f, "%d\n", gauss_pyramid[p].rgb_clamp);

        for (int i = 0; i < cur_height; i++) {
            for (int j = 0; j < cur_width; j++) {
                for (int k = 0; k < 3; k++) {
                    fprintf(out_f, "%d ", gauss_pyramid[p].pixels[(i*prev_width*img.L) + (j*img.L) + k]);
                }
            }
            fprintf(out_f, "\n");
        }
        fclose(out_f); // close file
    }
}

FIXED VERSION

Thanks all for suggestions. In the end I corrected the subsample() as follows:

struct Image subsample(int height, int width, int colors, 
    struct Image pixels_in, struct Image pixels_out) {
        int out_h = height/2, out_w = width/2;
    for (int i = 0; i < out_h; ++i) {
        for (int j = 0; j < out_w; ++j) {
            for (int k = 0; k < colors; ++k) {
                // take every 2nd pixel (2*i, 2*j)
                pixels_out.pixels[(i*width*colors) + (j*colors) + k] = pixels_in.pixels[(i*2*width*colors) + (j*2*colors) + k];
            }
        }
    }
    return pixels_out;
}

And main():


int main() {
    struct Image img;

    FILE* fp = fopen("./img/cameraman_p3.ppm", "r");
    char header[3];
    int width, height, max_color;
    fscanf(fp,"%s", header);             // ppm header, rn works for P3 only
    fscanf(fp, "%d %d", &img.x, &img.y); // x = width, y = height
    fscanf(fp, "%d", &img.rgb_clamp);    // max rgb value
    img.L = 3;                           // number of color channels, does not change

    // check header format for color images
    if (header[0] != 'P' || header[1] != '3') {
        printf("Invalid file format.\n");
        printf("Header = %s\n", header);
        return 1;
    }

    img.pixels = (int*)malloc(img.x*img.y*img.L*sizeof(int));
    int pixels[height][width][3];
    // int ***out = (int***)malloc(height*width*3*sizeof(int));

    // place image into img struct
    for (int i = 0; i < img.y; i++) {
        for (int j = 0; j < img.x; j++) {
            for (int k = 0; k < img.L; k++) {
                fscanf(fp, "%d", &(img.pixels[(i*img.x*img.L) + (j*img.L) + k]));
            }
        }
    }
    fclose(fp); // close input file


    // at this point the image is only in pixels array
    double** kernel = make_gauss_kernel();
    int kr = kernel_size/2;
    struct Image gauss_pyramid[NUM_LEVELS]; 

    // 0th level of pyramid = original image
    gauss_pyramid[0].pixels = (int*)malloc(img.x*img.y*img.L*sizeof(int)); 
    memcpy(gauss_pyramid[0].pixels, img.pixels, img.x*img.y*img.L*sizeof(int));
    gauss_pyramid[0].x = img.x;
    gauss_pyramid[0].y = img.y;
    gauss_pyramid[0].L = img.L;
    gauss_pyramid[0].rgb_clamp = img.rgb_clamp;

    // build pyramid
    int cur_width, cur_height, prev_height, prev_width;
    for (int p = 1; p < NUM_LEVELS; p++) {
        printf("Building pyramid level %d\n", p);
        // instantiate struct
        gauss_pyramid[p].x = gauss_pyramid[p-1].x/2;    
        gauss_pyramid[p].y = gauss_pyramid[p-1].y/2;
        gauss_pyramid[p].L = gauss_pyramid[p-1].L;
        gauss_pyramid[p].rgb_clamp = gauss_pyramid[p-1].rgb_clamp;
        gauss_pyramid[p].pixels = (int*)malloc(img.x*img.y*img.L*sizeof(int));

        gauss_pyramid[p] = subsample(img.y, img.x, img.L, gauss_pyramid[p], gauss_pyramid[p]);
    
        // save into new file
        char *filename;
        char s1[3];
        sprintf(s1, "%d", p);
        char *s0 = "./img/pyramid_lvl_";
        char *s2 = "_file.ppm";

        filename = malloc(strlen(s0) + strlen(s1) + strlen(s2) + 1);
        filename[0] = '\0';  // make it a string in mem
        strcat(filename, s0);
        strcat(filename, s1);
        strcat(filename, s2);

        printf("%s\n", filename);
        if (remove(filename) == 0) {
            printf("Previous out_file deleted\n");
        }

        FILE *out_f = fopen(filename, "w");
        fprintf(out_f, "P3\n");
        fprintf(out_f, "%d %d\n", cur_width, cur_height);
        fprintf(out_f, "%d\n", gauss_pyramid[p].rgb_clamp);

        for (int i = 0; i < cur_height; i++) {
            for (int j = 0; j < cur_width; j++) {
                for (int k = 0; k < 3; k++) {
                    fprintf(out_f, "%d ", gauss_pyramid[p].pixels[(i*img.x*img.L) + (j*img.L) + k]); // can replace img.L
                }
            }
            fprintf(out_f, "\n");
        }
        fclose(out_f); // close file
    }
}

Solution

  • You are passing the previous width and height to subsample, yet, you treat the width and height received as the new one. Here you call subsample:

     subsample(prev_height, prev_width, img.L, gauss_pyramid[p-1], gauss_pyramid[p]);
    

    inside your function you loop till width and height respectively. With values set beyond the expected bounds from outside the actual bounds you are gradually overriding your image with random data as pixels. You will need to pass curr_height and curr_width, respectively to the function, or loop up to their halves inside of the function.