This commit is contained in:
François Colin de Verdière 2025-04-03 17:00:34 +02:00
parent 739d45e7cc
commit 5bb4e791b8

View File

@ -1,5 +1,5 @@
#include <iostream>
#include <cassert> #include <cassert>
#include <iostream>
#include <random> #include <random>
#include <string> #include <string>
#include <vector> #include <vector>
@ -7,9 +7,9 @@
#include <CLI11.hpp> #include <CLI11.hpp>
// Image filtering and I/O // Image filtering and I/O
#include <SimpleProgressBar.hpp>
#include "utils.hpp"
#include "image.h" #include "image.h"
#include "utils.hpp"
#include <SimpleProgressBar.hpp>
#define DEFAULT_SEAMS 1 #define DEFAULT_SEAMS 1
@ -23,7 +23,6 @@ int max_step = 1;
// : dim_long, b : dim_large // : dim_long, b : dim_large
#define im_index(a, b) (vertical ? (width * a + b) : (width * b + a)) #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) {
if (!silent) if (!silent)
@ -38,8 +37,9 @@ void export_image(const char *filename, const void *data, int width, int height,
std::string function = "grad"; std::string function = "grad";
#define compute_energy_for_pixel(source, width, height, i, j, nbChannels, \ #define compute_energy_for_pixel( \
/* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \ source, width, height, i, j, \
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 = \
@ -52,11 +52,10 @@ std::string function = "grad";
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 == "gradnorm") { \ if (function == "gradnorm") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest+= \ dest += (std::pow(fabs((float)source[indexPixel_up + ch] - \
(std::pow(fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch]), \ source[indexPixel + ch]), \
2) + \ 2) + \
\ \
@ -73,8 +72,7 @@ std::string function = "grad";
} \ } \
} else if (function == "grad") { \ } else if (function == "grad") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += \ dest += ((fabs((float)source[indexPixel_up + ch] - \
((fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch])) + \ source[indexPixel + ch])) + \
\ \
(fabs((float)source[indexPixel_down + ch] - \ (fabs((float)source[indexPixel_down + ch] - \
@ -87,8 +85,7 @@ std::string function = "grad";
} \ } \
} else if (function == "gradnorminf") { \ } else if (function == "gradnorminf") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest+= \ dest += std::max(std::max((fabs((float)source[indexPixel_up + ch] - \
std::max(std::max((fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch])), \ source[indexPixel + ch])), \
\ \
(fabs((float)source[indexPixel_down + ch] - \ (fabs((float)source[indexPixel_down + ch] - \
@ -118,17 +115,16 @@ std::vector<float> energy_e1(std::vector<unsigned char> source, int width,
for (auto i = 0; i < width; i++) { for (auto i = 0; i < width; i++) {
for (auto j = 0; j < height; j++) { for (auto j = 0; j < height; j++) {
compute_energy_for_pixel( compute_energy_for_pixel(source, width, height, i, j, nbChannels,
source, width, height, nbColorChannels, energy[width * j + i]);
i, j, nbChannels, nbColorChannels, energy[width*j+i]
);
} }
} }
return energy; return energy;
} }
/** Given the energy value, returns the optimal seam */ /** Given the energy value, returns the optimal seam */
template <typename T> std::vector<int> optimal_seam(std::vector<T> energy, int width, int height, template <typename T>
std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
bool vertical) { bool vertical) {
// dyn_energy is indexed by [dim_large*(i : dim_long) + (j : dim_large)] // dyn_energy is indexed by [dim_large*(i : dim_long) + (j : dim_large)]
@ -150,8 +146,9 @@ template <typename T> std::vector<int> optimal_seam(std::vector<T> energy, int w
for (auto k = lower_bound; k <= upper_bound; for (auto k = lower_bound; k <= upper_bound;
k++) { // Compute energy based on predecessors k++) { // Compute energy based on predecessors
if (dyn_energy[dim_large*(i-1)+k] < dyn_energy[dim_large*i+j]) { if (dyn_energy[dim_large * (i - 1) + k] <
dyn_energy[dim_large*i+j] = dyn_energy[dim_large*(i-1)+k]; 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] += energy[im_index(i, j)]; dyn_energy[dim_large * i + j] += energy[im_index(i, j)];
@ -173,13 +170,13 @@ template <typename T> std::vector<int> optimal_seam(std::vector<T> energy, int w
//* Backtracking to find the path //* Backtracking to find the path
for (auto i = dim_long - 1; i > 0; i--) { for (auto i = dim_long - 1; i > 0; i--) {
// We want to find either (bot_l, bot_c, bot_r) with dyn_energy[.] = min_val - // We want to find either (bot_l, bot_c, bot_r) with dyn_energy[.] = min_val -
// energy[cur] // energy[cur]
// Idea : float next_energy = min_val - energy[width*i + min_idx]; // Idea : float next_energy = min_val - energy[width*i + min_idx];
//! With floats, we don't always have x + y - y == x, so we check is x+y == x+y //! With floats, we don't always have x + y - y == x, so we check is x+y == x+y
// This define is a bit ugly but 200x faster than using a lambda function // This define is a bit ugly but 200x faster than using a lambda function
#define is_next_idx(idx) \ #define is_next_idx(idx) \
(dyn_energy[(i - 1) * dim_large + idx] + energy[im_index(i, min_idx)] == \ (dyn_energy[(i - 1) * dim_large + idx] + energy[im_index(i, min_idx)] == \
min_val) min_val)
@ -239,59 +236,56 @@ void remove_seam(const std::vector<T> source, std::vector<T> &output, int width,
} }
} }
// It would be preferable to use templates only for the value assignation but
// It would be preferable to use templates only for the value assignation but this is in fact far less efficient // this is in fact far less efficient
void recompute_energy_along_seam( void recompute_energy_along_seam(std::vector<unsigned char> carved_img,
std::vector<unsigned char> carved_img, std::vector<float> &output_energy, std::vector<int> opt_seam, std::vector<float> &output_energy,
int width, int height, int nbChannels, int nbColorChannels, bool vertical std::vector<int> opt_seam, int width,
) { int height, int nbChannels,
int nbColorChannels, bool vertical) {
int dim_large = vertical ? width : height; int dim_large = vertical ? width : height;
int dim_long = vertical ? height : width; int dim_long = vertical ? height : width;
int newWidth = vertical ? width-1 : width; int newWidth = vertical ? width - 1 : width;
int newHeight = vertical ? height : height-1; int newHeight = vertical ? height : height - 1;
for (auto j = 0; j < dim_long; j++) { for (auto j = 0; j < dim_long; j++) {
for (auto i = -1; i < 2; i++) { for (auto i = -1; i < 2; i++) {
int x = vertical ? (opt_seam[j] + i) : j; int x = vertical ? (opt_seam[j] + i) : j;
int y = vertical ? j : (opt_seam[j] + i); int y = vertical ? j : (opt_seam[j] + i);
if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < dim_large - 1)) { if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < dim_large - 1)) {
compute_energy_for_pixel( compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y,
carved_img, newWidth, newHeight, nbChannels, nbColorChannels,
x, y, nbChannels, output_energy[width * y + x]);
nbColorChannels, output_energy[width*y+x]
);
} }
} }
} }
} }
void recompute_energy_along_seam( void recompute_energy_along_seam(
std::vector<unsigned char> carved_img, std::vector<std::pair<int, float>> &output_energy, std::vector<int> opt_seam, std::vector<unsigned char> carved_img,
int width, int height, int nbChannels, int nbColorChannels, bool vertical std::vector<std::pair<int, float>> &output_energy,
) { std::vector<int> opt_seam, int width, int height, int nbChannels,
int nbColorChannels, bool vertical) {
int dim_large = vertical ? width : height; int dim_large = vertical ? width : height;
int dim_long = vertical ? height : width; int dim_long = vertical ? height : width;
int newWidth = vertical ? width-1 : width; int newWidth = vertical ? width - 1 : width;
int newHeight = vertical ? height : height-1; int newHeight = vertical ? height : height - 1;
for (auto j = 0; j < dim_long; j++) { for (auto j = 0; j < dim_long; j++) {
for (auto i = -1; i < 2; i++) { for (auto i = -1; i < 2; i++) {
int x = vertical ? (opt_seam[j] + i) : j; int x = vertical ? (opt_seam[j] + i) : j;
int y = vertical ? j : (opt_seam[j] + i); int y = vertical ? j : (opt_seam[j] + i);
if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < dim_large - 1)) { if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < dim_large - 1)) {
compute_energy_for_pixel( compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y,
carved_img, newWidth, newHeight, nbChannels, nbColorChannels,
x, y, nbChannels, output_energy[width * y + x].first);
nbColorChannels, output_energy[width*y+x].first
);
} }
} }
} }
} }
/** Carves an image and its energy by one seam, and recomputes the energy. /** Carves an image and its energy by one seam, and recomputes the energy.
Returns the optimal seam used */ Returns the optimal seam used */
template <typename T> template <typename T>
@ -309,45 +303,41 @@ std::vector<int> carving_step(const std::vector<unsigned char> source_img,
opt_seam); opt_seam);
remove_seam(source_energy, output_energy, width, height, 1, vertical, remove_seam(source_energy, output_energy, width, height, 1, vertical,
opt_seam); opt_seam);
// Recompute the energy along the seam, we need a separate function for templating // Recompute the energy along the seam, we need a separate function for
recompute_energy_along_seam( // templating
source_img, output_energy, opt_seam, recompute_energy_along_seam(source_img, output_energy, opt_seam, width,
width, height, nbChannels, nbColorChannels, height, nbChannels, nbColorChannels, vertical);
vertical
);
return opt_seam; return opt_seam;
} }
std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy, std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
int width, int height, unsigned char* mask) { int width, int height,
std::vector<std::pair<int, float>> output(width*height); unsigned char *mask) {
std::vector<std::pair<int, float>> output(width * height);
for (auto i=0; i < width*height; i++) { for (auto i = 0; i < width * height; i++) {
output[i] = {mask[i], energy[i]}; output[i] = {mask[i], energy[i]};
} }
return output; return output;
} }
bool does_seam_remove_mask(unsigned char* mask, int width, int height, int nbChannels, bool does_seam_remove_mask(unsigned char *mask, int width, int height,
std::vector<int> opt_seam, bool vertical) int nbChannels, std::vector<int> opt_seam,
{ bool vertical) {
int dim_large = vertical ? width : height; int dim_large = vertical ? width : height;
int dim_long = vertical ? height : width; int dim_long = vertical ? height : width;
for (int i=0; i < dim_long; i++) { for (int i = 0; i < dim_long; i++) {
if (mask[im_index(i, opt_seam[i])] == 0) return true; if (mask[im_index(i, opt_seam[i])] == 0)
return true;
} }
return false; 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) {
int nbColorChannels = nbChannels > 3 ? 3 : nbChannels; int nbColorChannels = nbChannels > 3 ? 3 : nbChannels;
int curWidth = width; int curWidth = width;
@ -362,7 +352,7 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
std::vector<float> source_energy(width * height); std::vector<float> source_energy(width * height);
// Contains at each step the carved energy // Contains at each step the carved energy
std::vector<std::pair<int, float>> masked_energy; std::vector<std::pair<int, float>> masked_energy;
std::vector<std::pair<int, float>> output_masked_energy(width*height); std::vector<std::pair<int, float>> output_masked_energy(width * height);
// Source energy with (-1, 0, 1) on first element according to mask value // Source energy with (-1, 0, 1) on first element according to mask value
std::vector<unsigned char> output_img(width * height * nbChannels); std::vector<unsigned char> output_img(width * height * nbChannels);
// Receives at each step the newly carved image // Receives at each step the newly carved image
@ -413,8 +403,8 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
if (mask) { if (mask) {
if (mask[k] == 2) // Green if (mask[k] == 2) // Green
test_energy_output[nbChannels*k + 1] = 125; test_energy_output[nbChannels * k + 1] = 125;
else if (mask[k] == 0) {// Red else if (mask[k] == 0) { // Red
test_energy_output[nbChannels * k] = 125; test_energy_output[nbChannels * k] = 125;
} }
} }
@ -428,21 +418,19 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
while (seam_index++ < nbSeams || until_mask_removal) { while (seam_index++ < nbSeams || until_mask_removal) {
std::vector<int> opt_seam; std::vector<int> opt_seam;
if (mask) { if (mask) {
opt_seam = carving_step( opt_seam = carving_step(source_img, masked_energy, output_img,
source_img, masked_energy, output_img, output_masked_energy, output_masked_energy, curWidth, curHeight,
curWidth, curHeight, nbChannels, nbColorChannels, vertical nbChannels, nbColorChannels, vertical);
);
if ( if (until_mask_removal &&
until_mask_removal && !does_seam_remove_mask(mask, width, height, nbChannels, opt_seam,
!does_seam_remove_mask(mask, width, height, nbChannels, opt_seam, vertical) vertical))
) break; break;
} else { } else {
opt_seam = carving_step( opt_seam = carving_step(source_img, source_energy, output_img,
source_img, source_energy, output_img, output_energy, output_energy, curWidth, curHeight, nbChannels,
curWidth, curHeight, nbChannels, nbColorChannels, vertical nbColorChannels, vertical);
);
} }
std::copy(output_img.begin(), output_img.end(), source_img.begin()); std::copy(output_img.begin(), output_img.end(), source_img.begin());
std::copy(output_energy.begin(), output_energy.end(), std::copy(output_energy.begin(), output_energy.end(),
@ -512,7 +500,8 @@ int main(int argc, char **argv) {
"Don't resize image, just try the specified energy function"); "Don't resize image, just try the specified energy function");
app.add_option("-f,--function", function, app.add_option("-f,--function", function,
"The function to apply to compute the energy"); "The function to apply to compute the energy");
app.add_flag("-u,--until-mask-removal", until_mask_removal, app.add_flag(
"-u,--until-mask-removal", until_mask_removal,
"Carve the image until there are no more red pixels in the mask"); "Carve the image until there are no more red pixels in the mask");
CLI11_PARSE(app, argc, argv); CLI11_PARSE(app, argc, argv);
@ -538,16 +527,18 @@ int main(int argc, char **argv) {
exit(1); exit(1);
} }
unsigned char r, g, b; unsigned char r, g, b;
for (auto i=0; i < width*height; i++) { for (auto i = 0; i < width * height; i++) {
r = mask[maskChannels*i]; r = mask[maskChannels * i];
g = mask[maskChannels*i+1]; g = mask[maskChannels * i + 1];
b = mask[maskChannels*i+2]; b = mask[maskChannels * i + 2];
bool positive = (g > r && g > b && g > 100); // Mask images are not always the cleanest bool positive = (g > r && g > b &&
g > 100); // Mask images are not always the cleanest
bool negative = (r > g && r > b && r > 100); bool negative = (r > g && r > b && r > 100);
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 channel //* From now on, mask has the same dimensions as source and one single
//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
@ -558,9 +549,9 @@ int main(int argc, char **argv) {
until_mask_removal = false; until_mask_removal = false;
} }
if (until_mask_removal && nbSeams != DEFAULT_SEAMS) { if (until_mask_removal && nbSeams != DEFAULT_SEAMS) {
std::cerr << "Flag --nb-seams specified but --until-mask-removal provided." << std::endl; std::cerr << "Flag --nb-seams specified but --until-mask-removal provided."
<< std::endl;
nbSeams = DEFAULT_SEAMS; nbSeams = DEFAULT_SEAMS;
} }
nbSeams = std::min( nbSeams = std::min(