From 3e96a3df05afdf711c8d75aa1d65926657b915ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colin=20de=20Verdi=C3=A8re?= Date: Thu, 3 Apr 2025 11:42:23 +0200 Subject: [PATCH] several improvements, including the ability to change the energy function --- src/seam-carving.cpp | 190 +++++++++++++++++++++++++++++-------------- 1 file changed, 127 insertions(+), 63 deletions(-) diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index 2a6a250..ca239de 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -39,8 +39,11 @@ void export_image(const char *filename, const void *data, int width, int height, } } +std::string function = "grad"; + #define compute_energy_for_pixel(source, width, height, i, j, nbChannels, \ nbColorChannels, energy) \ + /* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \ auto indexPixel = (nbChannels) * (width * (j) + (i)); \ auto indexPixel_up = \ ((j) - 1 > 0) ? (nbChannels) * (width * ((j) - 1) + (i)) : indexPixel; \ @@ -53,29 +56,75 @@ void export_image(const char *filename, const void *data, int width, int height, ? (nbChannels) * (width * (j) + ((i) + 1)) \ : indexPixel; \ energy[width * j + i] = 0; \ - for (auto ch = 0; ch < (nbColorChannels); ch++) { \ - energy[(width) * (j) + (i)] += \ - (fabs((float)source[indexPixel_up + ch] - source[indexPixel + ch]) + \ - fabs((float)source[indexPixel_down + ch] - source[indexPixel + ch]) + \ - fabs((float)source[indexPixel_left + ch] - source[indexPixel + ch]) + \ - fabs((float)source[indexPixel_right + ch] - \ - source[indexPixel + ch])); \ - } \ + if (function == "gradnorm") { \ + for (auto ch = 0; ch < (nbColorChannels); ch++) { \ + energy[(width) * (j) + (i)] += \ + (std::pow(fabs((float)source[indexPixel_up + ch] - \ + source[indexPixel + ch]), \ + 2) + \ + \ + std::pow(fabs((float)source[indexPixel_down + ch] - \ + source[indexPixel + ch]), \ + 2) + \ + \ + std::pow(fabs((float)source[indexPixel_left + ch] - \ + source[indexPixel + ch]), \ + 2) + \ + std::pow(fabs((float)source[indexPixel_right + ch] - \ + source[indexPixel + ch]), \ + 2)); \ + } \ + } else if (function == "grad") { \ + for (auto ch = 0; ch < (nbColorChannels); ch++) { \ + energy[(width) * (j) + (i)] += \ + ((fabs((float)source[indexPixel_up + ch] - \ + source[indexPixel + ch])) + \ + \ + (fabs((float)source[indexPixel_down + ch] - \ + source[indexPixel + ch])) + \ + \ + (fabs((float)source[indexPixel_left + ch] - \ + source[indexPixel + ch])) + \ + (fabs((float)source[indexPixel_right + ch] - \ + source[indexPixel + ch]))); \ + } \ + } else if (function == "gradnorminf") { \ + for (auto ch = 0; ch < (nbColorChannels); ch++) { \ + energy[(width) * (j) + (i)] += \ + std::max(std::max((fabs((float)source[indexPixel_up + ch] - \ + source[indexPixel + ch])), \ + \ + (fabs((float)source[indexPixel_down + ch] - \ + source[indexPixel + ch]))), \ + std::max( \ + \ + (fabs((float)source[indexPixel_left + ch] - \ + source[indexPixel + ch])), \ + (fabs((float)source[indexPixel_right + ch] - \ + source[indexPixel + ch])))); \ + } \ + } else { \ + std::cerr << "function " << function << " not available" << std::endl; \ + exit(1); \ + }; + // 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 -// longer... +// slower... -/** e_1 energy, energy is always normalized between 0 and 1 */ +/** e_1 energy*/ std::vector energy_e1(std::vector source, int width, int height, int nbChannels) { int nbColorChannels = nbChannels > 3 ? 3 : nbChannels; // nombre de canaux, excepté le alpha std::vector energy(width * height); float max_energy = 0; + for (auto i = 0; i < width; i++) { for (auto j = 0; j < height; j++) { compute_energy_for_pixel(source, width, height, i, j, nbChannels, nbColorChannels, energy); + energy[width * j + i] = (energy[width * j + i]); } } return energy; @@ -190,23 +239,11 @@ void remove_seam(const std::vector source, std::vector &output, int width, } } } - -std::vector carving_step(const std::vector source_img, - std::vector source_energy, - std::vector &output_img, - std::vector &output_energy, int width, - int height, int nbChannels, int nbColorChannels, - bool vertical) { - /** Carves an image and its energy by one seam, and recomputes the energy. - Returns the optimal seam used */ - std::vector opt_seam = - optimal_seam(source_energy, width, height, vertical); - remove_seam(source_img, output_img, width, height, nbChannels, vertical, - opt_seam); - - remove_seam(source_energy, output_energy, width, height, 1, vertical, - opt_seam); - +void update_energy_along_seam(std::vector opt_seam, + std::vector output_img, + std::vector &output_energy, int height, + int width, int nbChannels, bool vertical, + int nbColorChannels) { // Recompute the energy along the seam if (energy_recompute_all) { std::vector energy = @@ -214,9 +251,7 @@ std::vector carving_step(const std::vector source_img, vertical ? height : height - 1, nbChannels); std::copy(energy.begin(), energy.end(), output_energy.begin()); } else { - - // ASSUME WE ARE DOING A VERTICAL SEAM - if (vertical) { + if (vertical) { // Vertical seam for (auto j = 0; j < height; j++) { for (auto i = -1; i < 2; i++) { if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < width - 1)) { @@ -238,11 +273,28 @@ std::vector carving_step(const std::vector source_img, } } } +} +std::vector carving_step(const std::vector source_img, + std::vector source_energy, + std::vector &output_img, + std::vector &output_energy, int width, + int height, int nbChannels, int nbColorChannels, + bool vertical) { + /** Carves an image and its energy by one seam, and recomputes the energy. + Returns the optimal seam used */ + std::vector opt_seam = + optimal_seam(source_energy, width, height, vertical); + remove_seam(source_img, output_img, width, height, nbChannels, vertical, + opt_seam); + remove_seam(source_energy, output_energy, width, height, 1, vertical, + opt_seam); + update_energy_along_seam(opt_seam, output_img, output_energy, height, width, + nbChannels, vertical, nbColorChannels); return opt_seam; } -void seam_carving(unsigned char *source, int width, int height, int nbChannels, - const char *out_filename, int nbSeams, bool vertical, unsigned char* mask=nullptr) { +auto seam_carving(unsigned char *source, int width, int height, int nbChannels, + int nbSeams, bool vertical, unsigned char *mask = nullptr) { int nbColorChannels = nbChannels > 3 ? 3 : nbChannels; int curWidth = width; int curHeight = height; @@ -337,11 +389,11 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels, } if (test_energy) { - export_image(out_filename, test_energy_output.data(), width, height, - nbChannels); + + return std::make_tuple(test_energy_output, width, height, nbChannels); + } else { - export_image(out_filename, source_img.data(), curWidth, curHeight, - nbChannels); + return std::make_tuple(source_img, curWidth, curHeight, nbChannels); } } @@ -352,7 +404,7 @@ int main(int argc, char **argv) { std::string outputImage = "output.png"; int nbSeams = 1; bool vertical = false; - + app.add_option("-s,--source", sourceImage, "Source image") ->required() ->check(CLI::ExistingFile); @@ -372,6 +424,8 @@ int main(int argc, char **argv) { "Don't resize image, just try the specified energy function"); app.add_flag("--energy-recompute-all", energy_recompute_all, "recompute the whole energy at each step"); + app.add_option("-f,--function", function, + "The function to apply to compute the energy"); CLI11_PARSE(app, argc, argv); // Image loading @@ -379,38 +433,48 @@ int main(int argc, char **argv) { unsigned char *source = stbi_load(sourceImage.c_str(), &width, &height, &nbChannels, 0); - unsigned char* mask = nullptr; + unsigned char *mask = nullptr; if (!maskImage.empty()) { int maskWidth, maskHeight, maskChannels; mask = - stbi_load(maskImage.c_str(), &maskWidth, &maskHeight, &maskChannels, 0); - - if (maskWidth != width || maskHeight != height) { - std::cerr << maskImage << " and " << sourceImage - << " differ in dimension. Please provide a valid mask." - << std::endl; - exit(1); - } - if (maskChannels < 3) { - std::cerr << maskImage << " needs to be RGB." << std::endl; - exit(1); - } - unsigned char r, g, b; - for (auto i=0; i < width*height; i++) { - r = mask[maskChannels*i]; - g = mask[maskChannels*i]; - b = mask[maskChannels*i]; - mask[2*i] = (r == 0) && (g == 255) && (b == 0); - mask[2*i+1] = (r == 255) && (g == 0) && (b == 0); - } - //* From now on, mask has the same dimensions as source and exactly 2 channels - //* The first channel is positive, the second one negative. + stbi_load(maskImage.c_str(), &maskWidth, &maskHeight, &maskChannels, 0); + + if (maskWidth != width || maskHeight != height) { + std::cerr << maskImage << " and " << sourceImage + << " differ in dimension. Please provide a valid mask." + << std::endl; + exit(1); + } + if (maskChannels < 3) { + std::cerr << maskImage << " needs to be RGB." << std::endl; + exit(1); + } + unsigned char r, g, b; + for (auto i = 0; i < width * height; i++) { + r = mask[maskChannels * i]; + g = mask[maskChannels * i]; + b = mask[maskChannels * i]; + mask[2 * i] = (r == 0) && (g == 255) && (b == 0); + mask[2 * i + 1] = (r == 255) && (g == 0) && (b == 0); + } + //* From now on, mask has the same dimensions as source and exactly 2 + // channels + //* The first channel is positive, the second one negative. } - nbSeams = std::min(nbSeams, vertical ? width-1 : height-1); // We want to keep at least one row or column + nbSeams = std::min( + nbSeams, vertical + ? width - 1 + : height - 1); // We want to keep at least one row or column - seam_carving(source, width, height, nbChannels, outputImage.c_str(), - nbSeams, vertical, mask=mask); + auto result = seam_carving(source, width, height, nbChannels, nbSeams, + vertical, mask = mask); + auto content = std::get<0>(result); + int width_output = std::get<1>(result); + int height_output = std::get<2>(result); + int nbChannels_output = std::get<3>(result); + export_image(outputImage.c_str(), content.data(), width_output, height_output, + nbChannels_output); stbi_image_free(source); exit(0);