diff --git a/CMakeLists.txt b/CMakeLists.txt index a401648..406afc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ link_directories(${LIBJPEG_TURBO_LIB_DIR}) add_library(${PROJECT_NAME} SHARED image_operations.c - laplace_blending.c + blending.c jpeg.c utils.c ) @@ -40,7 +40,7 @@ install(DIRECTORY ${LIBJPEG_TURBO_INCLUDE_DIR}/ DESTINATION include) install(FILES image_operations.h - laplace_blending.h + blending.h utils.h jpeg.h DESTINATION include) diff --git a/README.md b/README.md index 8cae864..e8135b0 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ If you have `libturbojpeg` installed, compile and run the test with the followin gcc-14 -O3 -mavx2 -mfma -I simde/ -pthread -fsanitize=address -g -o stitch \ -I../ -I/usr/local/include \ -L/usr/local/lib -lturbojpeg \ -stitch.c ../laplace_blending.c ../jpeg.c ../image_operations.c ../utils.c && time ./stitch +stitch.c ../blending.c ../jpeg.c ../image_operations.c ../utils.c && time ./stitch ``` ### 2. Testing with Custom-Built NativeSticher Library diff --git a/laplace_blending.c b/blending.c similarity index 81% rename from laplace_blending.c rename to blending.c index 2a28ee2..4db2913 100644 --- a/laplace_blending.c +++ b/blending.c @@ -1,4 +1,8 @@ +#include "blending.h" +#include "jpeg.h" #include "turbojpeg.h" +#include "utils.h" +#include #include #include #include @@ -6,15 +10,13 @@ #include #include -#include "laplace_blending.h" -#include "utils.h" - -Blender *create_blender(Rect out_size, int nb) { +Blender *create_multi_band_blender(Rect out_size, int nb) { Blender *blender = (Blender *)malloc(sizeof(Blender)); - blender->real_out_size = out_size; + blender->blender_type = MULTIBAND; if (!blender) return NULL; + blender->real_out_size = out_size; blender->num_bands = min(MAX_BANDS, nb); @@ -79,6 +81,41 @@ Blender *create_blender(Rect out_size, int nb) { return blender; } +Blender *create_feather_blender(Rect out_size) { + Blender *blender = (Blender *)malloc(sizeof(Blender)); + blender->blender_type = FEATHER; + if (!blender) + return NULL; + blender->real_out_size = out_size; + blender->output_size = out_size; + blender->sharpness = 2.5; + + blender->out = (ImageF *)malloc(sizeof(ImageF)); + blender->final_out = (ImageS *)malloc(sizeof(ImageS)); + blender->out_mask = (ImageF *)malloc(sizeof(ImageF)); + + if (!blender->out || !blender->final_out || !blender->out_mask) { + free(blender->out); + free(blender->final_out); + free(blender->out_mask); + free(blender); + return NULL; + } + + blender->out[0] = create_empty_image_f(out_size.width, out_size.height, 3); + blender->out_mask[0] = + create_empty_image_f(out_size.width, out_size.height, 1); + + return blender; +} + +Blender *create_blender(BlenderType blenderType, Rect out_size, int nb) { + if (blenderType == MULTIBAND) { + return create_multi_band_blender(out_size, nb); + } + return create_feather_blender(out_size); +} + void destroy_blender(Blender *blender) { if (!blender) return; @@ -148,7 +185,8 @@ void *feed_worker(void *args) { f->mask_gaussian[f->level].height) { int outLevelIndex = - ((i + f->x_tl) + (k + f->y_tl) * f->out_level_width) * RGB_CHANNELS + + ((i + f->x_tl) + (k + f->y_tl) * f->out_level_width) * + RGB_CHANNELS + z; float maskVal = f->mask_gaussian[f->level].data[maskIndex]; @@ -175,7 +213,7 @@ void *feed_worker(void *args) { return NULL; } -int feed(Blender *b, Image *img, Image *mask_img, Point tl) { +int multi_band_feed(Blender *b, Image *img, Image *mask_img, Point tl) { ImageS images[b->num_bands + 1]; int return_val = 1; @@ -218,7 +256,8 @@ int feed(Blender *b, Image *img, Image *mask_img, Point tl) { int bottom = br_new.y - tl.y - img->height; int right = br_new.x - tl.x - img->width; - add_border_to_image(img, top, bottom, left, right, RGB_CHANNELS, BORDER_REFLECT); + add_border_to_image(img, top, bottom, left, right, RGB_CHANNELS, + BORDER_REFLECT); add_border_to_image(mask_img, top, bottom, left, right, 1, BORDER_CONSTANT); images[0] = create_empty_image_s(img->width, img->height, img->channels); @@ -300,6 +339,36 @@ int feed(Blender *b, Image *img, Image *mask_img, Point tl) { return return_val; } +int feather_feed(Blender *b, Image *img, Image *mask_img, Point tl) { + distance_transform(mask_img); + + for (int y = 0; y < img->height; y++) { + for (int x = 0; x < img->width; x++) { + int image_pos = ((y * img->width ) + x) * RGB_CHANNELS; + int mask_pos = (y * img->width) + x; + int result_pos = + ((x + tl.x) + ((y + tl.y) * b->output_size.width)) * RGB_CHANNELS; + int result_mask_pos = ((x + tl.x) + ((y + tl.y) * b->output_size.width)); + float w = mask_img->data[mask_pos] / 256.0; + b->out_mask->data[result_mask_pos] += w; + for (int z = 0; z < RGB_CHANNELS; z++) { + b->out->data[result_pos + z] += img->data[image_pos + z] * w; + } + } + } + + return 1; +} + +int feed(Blender *b, Image *img, Image *mask_img, Point tl) { + assert(img->height == mask_img->height && img->width == mask_img->width); + if (b->blender_type == MULTIBAND) { + return multi_band_feed(b, img, mask_img, tl); + } else { + return feather_feed(b, img, mask_img, tl); + } +} + void *blend_worker(void *args) { ThreadArgs *arg = (ThreadArgs *)args; int start_row = arg->start_index; @@ -340,7 +409,7 @@ void *normalize_worker(void *args) { return NULL; } -void blend(Blender *b) { +void multi_band_blend(Blender *b) { for (int level = 0; level <= b->num_bands; ++level) { b->final_out[level] = create_empty_image_s( b->out[level].width, b->out[level].height, b->out[level].channels); @@ -393,12 +462,45 @@ void blend(Blender *b) { } } - crop_image_buf(&b->result, 0, - max(0, b->result.height - b->real_out_size.height), 0, - max(0, b->result.width - b->real_out_size.width), RGB_CHANNELS); + crop_image_buf( + &b->result, 0, max(0, b->result.height - b->real_out_size.height), 0, + max(0, b->result.width - b->real_out_size.width), RGB_CHANNELS); free(blended_image.data); } +void feather_blend(Blender *b) { + b->final_out[0] = create_empty_image_s(b->out[0].width, b->out[0].height, + b->out[0].channels); + + NormalThreadData ntd = {b->out[0].width, 0, b->out, b->out_mask, + b->final_out}; + WorkerThreadArgs wtd; + wtd.ntd = &ntd; + ParallelOperatorArgs args = {b->out[0].height, &wtd}; + + parallel_operator(NORMALIZE, &args); + destroy_image_f(&b->out[0]); + + b->result.data = + (unsigned char *)malloc(b->output_size.width * b->output_size.height * + RGB_CHANNELS * sizeof(unsigned char)); + + b->result.channels = RGB_CHANNELS; + b->result.width = b->output_size.width; + b->result.height = b->output_size.height; + + convert_images_to_image(&b->final_out[0], &b->result); + destroy_image_s(&b->final_out[0]); +} + +void blend(Blender *b) { + if (b->blender_type == MULTIBAND) { + multi_band_blend(b); + } else { + feather_blend(b); + } +} + void parallel_operator(OperatorType operatorType, ParallelOperatorArgs *arg) { int numThreads = get_cpus_count(); int rowsPerThread = arg->rows / numThreads; diff --git a/laplace_blending.h b/blending.h similarity index 70% rename from laplace_blending.h rename to blending.h index c272d94..85f2e40 100644 --- a/laplace_blending.h +++ b/blending.h @@ -1,5 +1,10 @@ #include "image_operations.h" +typedef enum{ + MULTIBAND, + FEATHER +} BlenderType; + typedef struct { int num_bands; @@ -13,9 +18,11 @@ typedef struct Image result; ImageS *img_laplacians; ImageS *mask_gaussian; + BlenderType blender_type; + float sharpness; } Blender; -Blender *create_blender(Rect out_size, int nb); +Blender *create_blender(BlenderType blender_type, Rect out_size, int nb); int feed(Blender *b, Image *img, Image *maskImg, Point tl); void blend(Blender *b); void destroy_blender(Blender *blender); diff --git a/examples/downsampling.c b/examples/downsampling.c index 64d396d..f5476e0 100644 --- a/examples/downsampling.c +++ b/examples/downsampling.c @@ -1,7 +1,7 @@ #include #include #include -#include "laplace_blending.h" +#include "blending.h" #include "utils.h" #include diff --git a/examples/stitch.c b/examples/stitch.c index 2da7665..7cab628 100644 --- a/examples/stitch.c +++ b/examples/stitch.c @@ -3,12 +3,10 @@ #include #include #include -#include "laplace_blending.h" +#include "blending.h" #include "utils.h" -// naive testing -int main() -{ +void test_multiband(){ struct timespec start, end; double duration; @@ -24,7 +22,7 @@ int main() int num_bands = 5; clock_gettime(CLOCK_MONOTONIC, &start); - Blender *b = create_blender(out_size, num_bands); + Blender *b = create_blender(MULTIBAND,out_size, num_bands); clock_gettime(CLOCK_MONOTONIC, &end); duration = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9; printf("Elapsed time for creating blended: %.2f seconds\n", duration); @@ -63,5 +61,45 @@ int main() destroy_image(&mask1); destroy_image(&mask2); +} + +void test_feather(){ + Image img_buf1 = create_image("../files/apple.jpeg"); + Image img_buf2 = create_image("../files/orange.jpeg"); + + + Image mask1 = create_image_mask(img_buf1.width, img_buf1.height, 0.1f, 0, 1); + Image mask2 = create_image_mask(img_buf2.width, img_buf2.height, 0.1f, 1, 0); + + int out = (img_buf1.width * 0.1f); + int out_width = (img_buf1.width * 2) - (out * 2); + Rect out_size = {0, 0, out_width, img_buf1.height}; + + Blender *b = create_blender(FEATHER,out_size, -1); + + Point pt1 = {0, 0}; + feed(b, &img_buf1, &mask1, pt1); + + Point pt2 = {img_buf2.width - out * 2 - 100 , 0}; + feed(b, &img_buf2, &mask2, pt2); + + blend(b); + + if (b->result.data != NULL) + { + if (save_image(&b->result, "merge_feather.jpg")) + { + printf("Merged image saved \n"); + } + } + + +} +// naive testing +int main() +{ + test_multiband(); + test_feather(); + return 0; } diff --git a/image_operations.c b/image_operations.c index 2d6afb7..fa10fe4 100644 --- a/image_operations.c +++ b/image_operations.c @@ -2,6 +2,9 @@ #include "image_operations.h" #include "jpeg.h" #include "simde/simde/x86/avx2.h" +#include +#include +#include #include Image create_image(const char *filename) { return decompress_jpeg(filename); } @@ -389,11 +392,11 @@ void char_convolve_1(int range_start, int range_end, int src_width, if (data->image_type == IMAGE) { \ switch (img->channels) { \ case GRAY_CHANNELS: \ - char_convolve_1(start_row, end_row, img->width, img->height, \ + char_convolve_1(start_row, end_row, img->width, img->height, \ (unsigned char *)img->data, (unsigned char *)sampled); \ break; \ case RGB_CHANNELS: \ - char_convolve_3(start_row, end_row, img->width, img->height, \ + char_convolve_3(start_row, end_row, img->width, img->height, \ (unsigned char *)img->data, (unsigned char *)sampled); \ break; \ default: \ @@ -529,3 +532,96 @@ DEFINE_UPSAMPLE_WORKER_FUNC(upsample_worker_f, ImageF, float) DEFINE_UPSAMPLE_FUNC(upsample, Image, unsigned char, IMAGE) DEFINE_UPSAMPLE_FUNC(upsample_image_s, ImageS, short, IMAGES) DEFINE_UPSAMPLE_FUNC(upsample_image_f, ImageF, float, IMAGES) + +float get_pixel(float *image, int x, int y, int width, int height) { + if (x < 0 || y < 0 || x >= width || y >= height) + return FLT_MAX; + return image[y * width + x]; +} + +void distance_transform(Image *mask) { + assert(mask->channels == GRAY_CHANNELS); + const float mask5[] = {1.0f, 1.4f}; + + int x, y; + ImageF dst = create_empty_image_f(mask->width, mask->height, mask->channels); + + for (int y = 0; y < mask->height; y++) { + for (int x = 0; x < mask->width; x++) { + dst.data[y * mask->width + x] = + mask->data[y * mask->width + x] > 0 ? 0.0f : 255.0f; + } + } + +float max = -1.0f; + for (y = 0; y < mask->height; y++) { + for (x = 0; x < mask->width; x++) { + float current = dst.data[y * mask->width + x]; + if (current == 0.0f) + continue; + + float min_val = current; + + min_val = fminf(min_val, + get_pixel(dst.data, x - 1, y, mask->width, mask->height) + + mask5[0]); + min_val = fminf(min_val, + get_pixel(dst.data, x, y - 1, mask->width, mask->height) + + mask5[0]); + min_val = fminf(min_val, get_pixel(dst.data, x - 1, y - 1, mask->width, + mask->height) + + mask5[1]); + min_val = fminf(min_val, get_pixel(dst.data, x + 1, y - 1, mask->width, + mask->height) + + mask5[1]); + min_val = fminf(min_val, get_pixel(dst.data, x - 2, y - 1, mask->width, + mask->height) + + mask5[0] + mask5[1]); + min_val = fminf(min_val, get_pixel(dst.data, x - 1, y - 2, mask->width, + mask->height) + + mask5[0] + mask5[1]); + + dst.data[y * mask->width + x] = min_val; + } + } + + for (y = mask->height - 1; y >= 0; y--) { + for (x = mask->width - 1; x >= 0; x--) { + float current = dst.data[y * mask->width + x]; + + float min_val = current; + min_val = fminf(min_val, + get_pixel(dst.data, x + 1, y, mask->width, mask->height) + + mask5[0]); + min_val = fminf(min_val, + get_pixel(dst.data, x, y + 1, mask->width, mask->height) + + mask5[0]); + min_val = fminf(min_val, get_pixel(dst.data, x + 1, y + 1, mask->width, + mask->height) + + mask5[1]); + min_val = fminf(min_val, get_pixel(dst.data, x - 1, y + 1, mask->width, + mask->height) + + mask5[1]); + min_val = fminf(min_val, get_pixel(dst.data, x + 2, y + 1, mask->width, + mask->height) + + mask5[0] + mask5[1]); + min_val = fminf(min_val, get_pixel(dst.data, x + 1, y + 2, mask->width, + mask->height) + + mask5[0] + mask5[1]); + + dst.data[y * mask->width + x] = min_val; + } + } + + + for (int y = 0; y < mask->height; y++) { + for (int x = 0; x < mask->width; x++) { + + mask->data[y * mask->width + x] = dst.data[y * mask->width + x] == 0.0f + ? mask->data[y * mask->width + x] + : dst.data[y * mask->width + x]; + } + } + + destroy_image_f(&dst); +} diff --git a/image_operations.h b/image_operations.h index d875d65..3377549 100644 --- a/image_operations.h +++ b/image_operations.h @@ -121,6 +121,8 @@ typedef struct Image create_image(const char *filename); +void distance_transform(Image *mask); + Image create_empty_image(int width, int height, int channels); ImageS create_empty_image_s(int width, int height, int channels); ImageF create_empty_image_f(int width, int height, int channels);