Merge branch 'mask'

This commit is contained in:
augustin64 2025-04-04 08:57:54 +02:00
commit 14cccecfa1
3 changed files with 126 additions and 119 deletions

View File

@ -18,10 +18,8 @@ bool silent = false;
bool test_energy = false; bool test_energy = false;
bool until_mask_removal = false; bool until_mask_removal = false;
int max_step = 1; int max_step = 1;
std::string function = "grad";
// Get index for any table indexed by [width*(i : height) + (j : width)], but a
// : dim_long, b : dim_large
#define im_index(a, b) (vertical ? (width * a + b) : (width * b + a))
void export_image(const char *filename, const void *data, int width, int height, void export_image(const char *filename, const void *data, int width, int height,
int nbChannels) { int nbChannels) {
@ -35,25 +33,25 @@ void export_image(const char *filename, const void *data, int width, int height,
} }
} }
std::string function = "grad";
#define compute_energy_for_pixel( \ #define compute_energy_for_pixel( \
source, width, height, i, j, \ source, width, height, i, j, \
nbChannels, /* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \ nbChannels, /* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \
nbColorChannels, dest) \ nbColorChannels, dest) \
auto indexPixel = (nbChannels) * (width * (j) + (i)); \ auto indexPixel = (nbChannels) * (width * (j) + (i)); \
auto indexPixel_up = \ auto indexPixel_up = \
((j) - 1 > 0) ? (nbChannels) * (width * ((j) - 1) + (i)) : indexPixel; \ ((j) - 1 > 0) ? (nbChannels) * (width * ((j) - 1) + (i)) : indexPixel; \
auto indexPixel_down = ((j) + 1 < height) \ auto indexPixel_down = ((j) + 1 < height) \
? (nbChannels) * (width * ((j) + 1) + (i)) \ ? (nbChannels) * (width * ((j) + 1) + (i)) \
: indexPixel; \ : indexPixel; \
auto indexPixel_left = \ auto indexPixel_left = \
((i) - 1 > 0) ? (nbChannels) * (width * (j) + ((i) - 1)) : indexPixel; \ ((i) - 1 > 0) ? (nbChannels) * (width * (j) + ((i) - 1)) : indexPixel; \
auto indexPixel_right = ((i) + 1 < width) \ auto indexPixel_right = ((i) + 1 < width) \
? (nbChannels) * (width * (j) + ((i) + 1)) \ ? (nbChannels) * (width * (j) + ((i) + 1)) \
: indexPixel; \ : indexPixel; \
dest = 0; \ dest = 0; \
if (function == "grad") { \ if (function == "grad") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += ((fabs((float)source[indexPixel_up + ch] - \ dest += ((fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch])) + \ source[indexPixel + ch])) + \
@ -66,7 +64,7 @@ std::string function = "grad";
(fabs((float)source[indexPixel_right + ch] - \ (fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]))); \ source[indexPixel + ch]))); \
} \ } \
} else if (function == "gradnorm") { \ } else if (function == "gradnorm") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += (std::pow(fabs((float)source[indexPixel_up + ch] - \ dest += (std::pow(fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch]), \ source[indexPixel + ch]), \
@ -83,7 +81,7 @@ std::string function = "grad";
source[indexPixel + ch]), \ source[indexPixel + ch]), \
2)); \ 2)); \
} \ } \
} else if (function == "gradhoriz") { \ } else if (function == "gradhoriz") { \
/* take the gradient along the horizontal only*/ \ /* take the gradient along the horizontal only*/ \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += ((fabs((float)source[indexPixel_left + ch] - \ dest += ((fabs((float)source[indexPixel_left + ch] - \
@ -91,7 +89,7 @@ std::string function = "grad";
(fabs((float)source[indexPixel_right + ch] - \ (fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]))); \ source[indexPixel + ch]))); \
} \ } \
} else if (function == "gradvertic") { \ } else if (function == "gradvertic") { \
/* take the gradient along the vertical only*/ \ /* take the gradient along the vertical only*/ \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += ((fabs((float)source[indexPixel_up + ch] - \ dest += ((fabs((float)source[indexPixel_up + ch] - \
@ -99,7 +97,7 @@ std::string function = "grad";
(fabs((float)source[indexPixel_down + ch] - \ (fabs((float)source[indexPixel_down + ch] - \
source[indexPixel + ch]))); \ source[indexPixel + ch]))); \
} \ } \
} else if (function == "gradnorminf") { \ } else if (function == "gradnorminf") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += std::max(std::max((fabs((float)source[indexPixel_up + ch] - \ dest += std::max(std::max((fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch])), \ source[indexPixel + ch])), \
@ -113,14 +111,15 @@ std::string function = "grad";
(fabs((float)source[indexPixel_right + ch] - \ (fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch])))); \ source[indexPixel + ch])))); \
} \ } \
} else { \ } else { \
std::cerr << "function " << function << " not available" << std::endl; \ std::cerr << "function " << function << " not available" << std::endl; \
exit(1); \ exit(1); \
}; };
// Le alpha n'est pas pris en compte dans l'énergie // Le alpha n'est pas pris en compte dans l'énergie
// Here, we use this /ugly/ macro to avoid defining a function that would be way // Here, we use this /ugly/ macro to avoid defining a function that would be way
// slower... // slower...
/** e_1 energy*/ /** e_1 energy*/
std::vector<float> energy_e1(std::vector<unsigned char> source, int width, std::vector<float> energy_e1(std::vector<unsigned char> source, int width,
int height, int nbChannels) { int height, int nbChannels) {
@ -160,8 +159,8 @@ std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
int lower_bound = std::max(j - max_step, 0); int lower_bound = std::max(j - max_step, 0);
int upper_bound = std::min(j + max_step, dim_large - 1); int upper_bound = std::min(j + max_step, dim_large - 1);
for (auto k = lower_bound; k <= upper_bound; // Compute energy based on predecessors
k++) { // Compute energy based on predecessors for (auto k = lower_bound; k <= upper_bound; k++) {
if (dyn_energy[dim_large * (i - 1) + k] < if (dyn_energy[dim_large * (i - 1) + k] <
dyn_energy[dim_large * i + j]) { dyn_energy[dim_large * i + j]) {
dyn_energy[dim_large * i + j] = dyn_energy[dim_large * (i - 1) + k]; dyn_energy[dim_large * i + j] = dyn_energy[dim_large * (i - 1) + k];
@ -177,7 +176,7 @@ std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
T min_val = limits::max_energy(); T min_val = limits::max_energy();
for (auto j = 0; j < dim_large; j++) { for (auto j = 0; j < dim_large; j++) {
if (min_val > dyn_energy[dim_large * (dim_long - 1) + j]) { if (dyn_energy[dim_large * (dim_long - 1) + j] < min_val) {
min_idx = j; min_idx = j;
min_val = dyn_energy[dim_large * (dim_long - 1) + j]; min_val = dyn_energy[dim_large * (dim_long - 1) + j];
} }
@ -306,7 +305,7 @@ void recompute_energy_along_seam(
(((0 < j0 + j_offset) && (j0 + j_offset < dim_long)))) { (((0 < j0 + j_offset) && (j0 + j_offset < dim_long)))) {
compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y, compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y,
nbChannels, nbColorChannels, nbChannels, nbColorChannels,
output_energy[width * y + x].first); output_energy[width * y + x].second);
} }
} }
} }
@ -338,30 +337,6 @@ std::vector<int> carving_step(const std::vector<unsigned char> source_img,
return opt_seam; return opt_seam;
} }
std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
int width, int height,
unsigned char *mask) {
std::vector<std::pair<int, float>> output(width * height);
for (auto i = 0; i < width * height; i++) {
output[i] = {mask[i], energy[i]};
}
return output;
}
bool does_seam_remove_mask(unsigned char *mask, int width, int height,
int nbChannels, std::vector<int> opt_seam,
bool vertical) {
int dim_large = vertical ? width : height;
int dim_long = vertical ? height : width;
for (int i = 0; i < dim_long; i++) {
if (mask[im_index(i, opt_seam[i])] == 0)
return true;
}
return false;
}
auto seam_carving(unsigned char *source, int width, int height, int nbChannels, auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
int nbSeams, bool vertical, unsigned char *mask = nullptr) { int nbSeams, bool vertical, unsigned char *mask = nullptr) {
@ -492,9 +467,7 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
std::cout << std::endl; // Add newline after ProgressBar std::cout << std::endl; // Add newline after ProgressBar
if (test_energy) { if (test_energy) {
return std::make_tuple(test_energy_output, width, height, nbChannels); return std::make_tuple(test_energy_output, width, height, nbChannels);
} else { } else {
return std::make_tuple(source_img, curWidth, curHeight, nbChannels); return std::make_tuple(source_img, curWidth, curHeight, nbChannels);
} }
@ -564,8 +537,8 @@ int main(int argc, char **argv) {
mask[i] = positive ? 2 : (negative ? 0 : 1); mask[i] = positive ? 2 : (negative ? 0 : 1);
} }
//* From now on, mask has the same dimensions as source and one single //* "mask" has the same dimensions as source and one single channel
// channel
//* The values are: //* The values are:
//* . (2) we want to keep the pixel //* . (2) we want to keep the pixel
//* . (1) nothing in particular //* . (1) nothing in particular

View File

@ -1,17 +1,42 @@
#include <iostream>
#include <random>
#include <vector>
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h> #include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h> #include <stb_image_write.h>
#include <iostream> #include "utils.hpp"
#include <random>
#include <vector>
bool nearly_equal(float a, float b) { bool nearly_equal(float a, float b) {
return std::nextafter(a, std::numeric_limits<float>::lowest()) <= b && return std::nextafter(a, std::numeric_limits<float>::lowest()) <= b &&
std::nextafter(a, std::numeric_limits<float>::max()) >= b; std::nextafter(a, std::numeric_limits<float>::max()) >= b;
} }
bool does_seam_remove_mask(unsigned char* mask, int width, int height, int nbChannels,
std::vector<int> opt_seam, bool vertical)
{
int dim_large = vertical ? width : height;
int dim_long = vertical ? height : width;
for (int i=0; i < dim_long; i++) {
if (mask[im_index(i, opt_seam[i])] == 0) return true;
}
return false;
}
std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
int width, int height, unsigned char* mask) {
std::vector<std::pair<int, float>> output(width*height);
for (auto i=0; i < width*height; i++) {
output[i] = {mask[i], energy[i]};
}
return output;
}
std::pair<int, float> operator+(std::pair<int, float>& p1, std::pair<int, float>& p2) { std::pair<int, float> operator+(std::pair<int, float>& p1, std::pair<int, float>& p2) {
return { return {
// If one of the two pixels is "protected" (ie 2), we want to prevent this line removing // If one of the two pixels is "protected" (ie 2), we want to prevent this line removing

View File

@ -1,5 +1,8 @@
#include <random> #include <random>
// Get index for any table indexed by [width*(i : height) + (j : width)], but a
// : dim_long, b : dim_large
#define im_index(a, b) (vertical ? (width * a + b) : (width * b + a))
bool nearly_equal(float a, float b); bool nearly_equal(float a, float b);
@ -7,6 +10,12 @@ std::pair<int, float> operator+(std::pair<int, float>& p1, std::pair<int, float>
void operator+=(std::pair<int, float>& p1, std::pair<int, float>& p2); void operator+=(std::pair<int, float>& p1, std::pair<int, float>& p2);
bool does_seam_remove_mask(unsigned char* mask, int width, int height, int nbChannels,
std::vector<int> opt_seam, bool vertical);
std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
int width, int height, unsigned char* mask);
namespace limits { namespace limits {
struct max_energy { struct max_energy {
template<class T> operator T() { template<class T> operator T() {