Does not recompute energy for vertical seams

This commit is contained in:
François Colin de Verdière 2025-04-01 16:44:29 +02:00
parent e7ab4f0bd6
commit 93e14375dd

View File

@ -15,6 +15,7 @@
// Global flag to silent verbose messages // Global flag to silent verbose messages
bool silent; bool silent;
bool test_energy; bool test_energy;
bool energy_recompute_all = false;
#define min(a, b) \ #define min(a, b) \
{ (a < b ? a : b) } { (a < b ? a : b) }
@ -39,6 +40,33 @@ void export_image(const char *filename, const void *data, int width, int height,
exit(errcode); exit(errcode);
} }
} }
#define compute_energy_for_pixel(source, width, height, i, j, nbChannels, \
nbColorChannels, energy) \
auto indexPixel = (nbChannels) * (width * (j) + (i)); \
auto indexPixel_up = \
((j) - 1 > 0) ? (nbChannels) * (width * ((j) - 1) + (i)) : indexPixel; \
auto indexPixel_down = ((j) + 1 < height) \
? (nbChannels) * (width * ((j) + 1) + (i)) \
: indexPixel; \
auto indexPixel_left = \
((i) - 1 > 0) ? (nbChannels) * (width * (j) + ((i) - 1)) : indexPixel; \
auto indexPixel_right = ((i) + 1 < width) \
? (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])); \
} \
// 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...
/** e_1 energy, energy is always normalized between 0 and 1 */ /** e_1 energy, energy is always normalized between 0 and 1 */
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) {
@ -48,38 +76,10 @@ std::vector<float> energy_e1(std::vector<unsigned char> source, int width,
float max_energy = 0; float max_energy = 0;
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) {
auto indexPixel = nbChannels * (width * j + i); compute_energy_for_pixel(source, width, height, i, j, nbChannels,
auto indexPixel_up = nbColorChannels, energy);
(j - 1 > 0) ? nbChannels * (width * (j - 1) + i) : indexPixel;
auto indexPixel_down =
(j + 1 < height) ? nbChannels * (width * (j + 1) + i) : indexPixel;
auto indexPixel_left =
(i - 1 > 0) ? nbChannels * (width * j + (i - 1)) : indexPixel;
auto indexPixel_right =
(i + 1 < width) ? nbChannels * (width * j + (i + 1)) : indexPixel;
energy[width * j + i] = 0;
for (auto ch = 0; ch < nbColorChannels;
ch++) { // Le alpha n'est pas pris en compte dans l'énergie
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]));
}
max_energy = fmax(max_energy, energy[width * j + i]);
} }
} }
if (max_energy == 0) {
return energy;
}
for (auto k = 0; k < width * height; k++) {
energy[k] = energy[k] / max_energy;
}
return energy; return energy;
} }
@ -154,13 +154,14 @@ std::vector<int> optimal_seam(std::vector<float> energy, int width, int height,
return result; return result;
} }
void remove_seam(const std::vector<unsigned char> source, template <typename T>
std::vector<unsigned char> &output, int width, int height, void remove_seam(const std::vector<T> source, std::vector<T> &output, int width,
int nbChannels, bool vertical, const std::vector<int> seam) { int height, int nbChannels, bool vertical,
const std::vector<int> seam) {
// remove the given seam from the image, the result is in output // remove the given seam from the image, the result is in output
// the output must have at least the right size!
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 (auto i = 0; i < dim_long; i++) { for (auto i = 0; i < dim_long; i++) {
int cur_j = 0; int cur_j = 0;
for (auto j = 0; cur_j < dim_large - 1 && j < dim_large; j++) { for (auto j = 0; cur_j < dim_large - 1 && j < dim_large; j++) {
@ -168,7 +169,6 @@ void remove_seam(const std::vector<unsigned char> source,
int out_pixelIndex = nbChannels * (vertical ? ((width - 1) * i + cur_j) int out_pixelIndex = nbChannels * (vertical ? ((width - 1) * i + cur_j)
: (width * cur_j + i)); : (width * cur_j + i));
int src_pixelIndex = nbChannels * im_index(i, j); int src_pixelIndex = nbChannels * im_index(i, j);
for (auto ch = 0; ch < nbChannels; ch++) for (auto ch = 0; ch < nbChannels; ch++)
output[out_pixelIndex + ch] = source[src_pixelIndex + ch]; output[out_pixelIndex + ch] = source[src_pixelIndex + ch];
cur_j++; cur_j++;
@ -177,13 +177,43 @@ void remove_seam(const std::vector<unsigned char> source,
} }
} }
std::vector<int> carving_step(const std::vector<unsigned char> source, std::vector<int> carving_step(const std::vector<unsigned char> source_img,
std::vector<float> energy, std::vector<float> source_energy,
std::vector<unsigned char> &output, int width, std::vector<unsigned char> &output_img,
std::vector<float> &output_energy, int width,
int height, int nbChannels, bool vertical) { int height, int nbChannels, bool vertical) {
/** Carves an image by one seam. Returns the optimal seam used */ /** Carves an image and its energy by one seam, and recomputes the energy.
std::vector<int> opt_seam = optimal_seam(energy, width, height, vertical); Returns the optimal seam used */
remove_seam(source, output, width, height, nbChannels, vertical, opt_seam); std::vector<int> 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);
// Recompute the energy along the seam
if (energy_recompute_all) {
std::vector<float> energy =
energy_e1(output_img, vertical ? width - 1 : width,
vertical ? height : height - 1, nbChannels);
std::copy(energy.begin(), energy.end(), output_energy.begin());
} else {
int nbColorChannels = 3;
std::cout << "starting to recompute the energy along the seam" << std::endl;
// ASSUME WE ARE DOING A 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)) {
compute_energy_for_pixel(output_img, (width - 1), height,
(opt_seam[j] + i), j, nbChannels,
nbColorChannels, output_energy);
}
}
}
std::cout << "DONE" << std::endl;
}
return opt_seam; return opt_seam;
} }
@ -198,10 +228,14 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
int dim_long = vertical ? height : width; int dim_long = vertical ? height : width;
// dim_long=longueur des seam // dim_long=longueur des seam
std::vector<unsigned char> carve_output(width * height * nbChannels);
// Receives at each step the newly carved image
std::vector<unsigned char> source_img(width * height * nbChannels); std::vector<unsigned char> source_img(width * height * nbChannels);
// Contains at each step the carved image // Contains at each step the carved image
std::vector<float> source_energy(width * height);
// Contains at each step the carved energy
std::vector<unsigned char> output_img(width * height * nbChannels);
// Receives at each step the newly carved image
std::vector<float> output_energy(width * height);
// Contains at each step the carved energy
std::vector<bool> complete_blacklist(width * height); std::vector<bool> complete_blacklist(width * height);
// Contains all removed pixels, for "test_energy" // Contains all removed pixels, for "test_energy"
std::vector<float> ini_energy; std::vector<float> ini_energy;
@ -213,6 +247,8 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
source_img[i] = source[i]; source_img[i] = source[i];
} }
source_energy = energy_e1(source_img, width, height, nbChannels);
if (test_energy) { if (test_energy) {
ini_energy = energy_e1(source_img, width, height, nbChannels); ini_energy = energy_e1(source_img, width, height, nbChannels);
for (auto k = 0; k < width * height; k++) { for (auto k = 0; k < width * height; k++) {
@ -220,10 +256,21 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
} }
//* Prepare final output //* Prepare final output
float max_energy=__FLT_MAX__;
for (auto k=0;k<width*height;k++){
max_energy=fmax(max_energy, ini_energy[k]);
}
if (max_energy!=0){
for (auto k=0;k<width*height;k++){
ini_energy[k]/=max_energy;
}
}
for (auto k = 0; k < width * height; k++) { for (auto k = 0; k < width * height; k++) {
// for (auto i=0; i < nbColorChannels; i++) //* Uncomment if you prefer to // for (auto i=0; i < nbColorChannels; i++) //* Uncomment if you prefer to
// see darkened source image // see darkened source image
// output[nbChannels*k+i] = source_img[nbChannels*k+i]/nbChannels; // output[nbChannels*k+i] = source_img[nbChannels*k+i]/nbChannels;
for (auto i = 0; i < nbColorChannels; i++) for (auto i = 0; i < nbColorChannels; i++)
test_energy_output[nbChannels * k + i] = ini_energy[k] * 255; test_energy_output[nbChannels * k + i] = ini_energy[k] * 255;
@ -233,13 +280,13 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
} }
SimpleProgressBar::ProgressBar bar(nbSeams); SimpleProgressBar::ProgressBar bar(nbSeams);
for (auto seam = 0; seam < nbSeams; seam++) { for (auto seam_index = 0; seam_index < nbSeams; seam_index++) {
std::vector<float> energy =
energy_e1(source_img, width, height, nbChannels);
std::vector<int> opt_seam = std::vector<int> opt_seam =
carving_step(source_img, energy, carve_output, curWidth, curHeight, carving_step(source_img, source_energy, output_img, output_energy,
nbChannels, vertical); curWidth, curHeight, nbChannels, vertical);
std::copy(carve_output.begin(), carve_output.end(), source_img.begin()); std::copy(output_img.begin(), output_img.end(), source_img.begin());
std::copy(output_energy.begin(), output_energy.end(),
source_energy.begin());
if (vertical) // We just reduced the dimension if (vertical) // We just reduced the dimension
curWidth--; curWidth--;
@ -247,6 +294,7 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
curHeight--; curHeight--;
if (test_energy) { // Update blacklist if (test_energy) { // Update blacklist
for (auto i = 0; i < dim_long; i++) { for (auto i = 0; i < dim_long; i++) {
int j, cur_j = 0; // cur_j is the index relative to the current carved int j, cur_j = 0; // cur_j is the index relative to the current carved
// image. j is absolute in the source image // image. j is absolute in the source image
@ -263,6 +311,7 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
255; // Set carved pixel to red 255; // Set carved pixel to red
} }
} }
bar.increment(); bar.increment();
bar.print(); bar.print();
} }
@ -289,12 +338,15 @@ int main(int argc, char **argv) {
int nbSeams = 1; int nbSeams = 1;
app.add_option("-n,--nb-seams", nbSeams, "Number of seams"); app.add_option("-n,--nb-seams", nbSeams, "Number of seams");
bool vertical = false; bool vertical = false;
app.add_flag("--vertical", vertical, "Vertical carving"); app.add_flag("--vertical", vertical,
"Vertical carving (remove vertical seams)");
silent = false; silent = false;
app.add_flag("--silent", silent, "No verbose messages"); app.add_flag("--silent", silent, "No verbose messages");
test_energy = false; test_energy = false;
app.add_flag("--test-energy", test_energy, app.add_flag("--test-energy", test_energy,
"Don't resize image, just try the specified energy function"); "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");
CLI11_PARSE(app, argc, argv); CLI11_PARSE(app, argc, argv);
// Image loading // Image loading
@ -304,7 +356,6 @@ int main(int argc, char **argv) {
nbSeams = min(nbSeams, width); nbSeams = min(nbSeams, width);
// std::cout << "channels: " << nbChannels << std::endl; // std::cout << "channels: " << nbChannels << std::endl;
seam_carving(source, width, height, nbChannels, outputImage.c_str(), nbSeams, seam_carving(source, width, height, nbChannels, outputImage.c_str(), nbSeams,
vertical, test_energy = test_energy); vertical, test_energy = test_energy);