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:
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
}
}
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
}
}
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.