mask: Attempting massive templating

This commit is contained in:
augustin64 2025-04-02 17:35:25 +02:00
parent 4ee23bb442
commit 55623c2ae9

View File

@ -27,6 +27,13 @@ bool nearly_equal(float a, float b) {
std::nextafter(a, std::numeric_limits<float>::max()) >= b; std::nextafter(a, std::numeric_limits<float>::max()) >= b;
} }
std::pair<int, float> operator+(std::pair<int, float>& p1, std::pair<int, float>& p2) {
return {
(p1.first==1) || (p2.first==1) ? 1 : std::max(p1.first, p2.first),
p1.second+p2.second
};
}
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)
@ -40,7 +47,7 @@ void export_image(const char *filename, const void *data, int width, int height,
} }
#define compute_energy_for_pixel(source, width, height, i, j, nbChannels, \ #define compute_energy_for_pixel(source, width, height, i, j, nbChannels, \
nbColorChannels, energy) \ 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; \
@ -52,9 +59,9 @@ void export_image(const char *filename, const void *data, int width, int height,
auto indexPixel_right = ((i) + 1 < width) \ auto indexPixel_right = ((i) + 1 < width) \
? (nbChannels) * (width * (j) + ((i) + 1)) \ ? (nbChannels) * (width * (j) + ((i) + 1)) \
: indexPixel; \ : indexPixel; \
energy[width * j + i] = 0; \ dest = 0; \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \
energy[(width) * (j) + (i)] += \ dest += \
(fabs((float)source[indexPixel_up + ch] - source[indexPixel + ch]) + \ (fabs((float)source[indexPixel_up + ch] - source[indexPixel + ch]) + \
fabs((float)source[indexPixel_down + 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_left + ch] - source[indexPixel + ch]) + \
@ -74,19 +81,21 @@ 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++) {
compute_energy_for_pixel(source, width, height, i, j, nbChannels, compute_energy_for_pixel(
nbColorChannels, energy); source, width, height,
i, j, nbChannels, nbColorChannels, energy[width*j+i]
);
} }
} }
return energy; return energy;
} }
std::vector<int> optimal_seam(std::vector<float> energy, int width, int height,
bool vertical) {
/** 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,
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)]
std::vector<float> dyn_energy(width * height); std::vector<T> dyn_energy(width * height);
int dim_large = vertical ? width : height; int dim_large = vertical ? width : height;
int dim_long = vertical ? height : width; // Number of elements in the seam int dim_long = vertical ? height : width; // Number of elements in the seam
@ -98,23 +107,24 @@ std::vector<int> optimal_seam(std::vector<float> energy, int width, int height,
for (auto i = 1; i < dim_long; i++) { // Propagate dyn_energy for (auto i = 1; i < dim_long; i++) { // Propagate dyn_energy
for (auto j = 0; j < dim_large; j++) { for (auto j = 0; j < dim_large; j++) {
dyn_energy[dim_large * i + j] = __FLT_MAX__; dyn_energy[dim_large * i + j] = std::numeric_limits<T>::max();
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; for (auto k = lower_bound; k <= upper_bound;
k++) { // Compute energy based on predecessors k++) { // Compute energy based on predecessors
dyn_energy[dim_large * i + j] = std::min( dyn_energy[dim_large * i + j] = std::min(
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]
);
} }
dyn_energy[dim_large * i + j] += energy[im_index(i, j)]; dyn_energy[dim_large * i + j] = dyn_energy[dim_large * i + j]+energy[im_index(i, j)];
} }
} }
std::vector<int> result(dim_long); std::vector<int> result(dim_long);
// Find the seam end // Find the seam end
int min_idx = -1; int min_idx = -1;
float min_val = __FLT_MAX__; T min_val = std::numeric_limits<T>::max();
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 (min_val > dyn_energy[dim_large * (dim_long - 1) + j]) {
min_idx = j; min_idx = j;
@ -166,8 +176,8 @@ std::vector<int> optimal_seam(std::vector<float> energy, int width, int height,
return result; return result;
} }
/** Carves an image by one seam. Returns the optimal seam used */
/** Carves an image by one seam. Returns the optimal seam used */
template <typename T> template <typename T>
void remove_seam(const std::vector<T> source, std::vector<T> &output, int width, void remove_seam(const std::vector<T> source, std::vector<T> &output, int width,
int height, int nbChannels, bool vertical, int height, int nbChannels, bool vertical,
@ -191,14 +201,68 @@ void remove_seam(const std::vector<T> source, std::vector<T> &output, int width,
} }
} }
std::vector<int> carving_step(const std::vector<unsigned char> source_img, // It would be preferable to use templates only for the value assignation but this is in fact far less efficient
std::vector<float> source_energy, void recompute_energy_along_seam(
std::vector<unsigned char> &output_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 width, int height, int nbChannels, int nbColorChannels, bool vertical
int height, int nbChannels, int nbColorChannels, ) {
bool vertical) { int dim_large = vertical ? width : height;
int dim_long = vertical ? height : width;
int newWidth = vertical ? width-1 : width;
int newHeight = vertical ? height : height-1;
for (auto j = 0; j < dim_long; j++) {
for (auto i = -1; i < 2; i++) {
int x = vertical ? (opt_seam[j] + i) : j;
int y = vertical ? j : (opt_seam[j] + i);
if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < dim_large - 1)) {
compute_energy_for_pixel(
carved_img, newWidth, newHeight,
x, y, nbChannels,
nbColorChannels, output_energy[width*j+i]
);
}
}
}
}
void recompute_energy_along_seam(
std::vector<unsigned char> carved_img, 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_long = vertical ? height : width;
int newWidth = vertical ? width-1 : width;
int newHeight = vertical ? height : height-1;
for (auto j = 0; j < dim_long; j++) {
for (auto i = -1; i < 2; i++) {
int x = vertical ? (opt_seam[j] + i) : j;
int y = vertical ? j : (opt_seam[j] + i);
if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < dim_large - 1)) {
compute_energy_for_pixel(
carved_img, newWidth, newHeight,
x, y, nbChannels,
nbColorChannels, output_energy[width*j+i].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>
std::vector<int> carving_step(const std::vector<unsigned char> source_img,
std::vector<std::pair<int, float>> source_energy,
std::vector<unsigned char> &output_img,
std::vector<std::pair<int, float>> &output_energy, int width,
int height, int nbChannels, int nbColorChannels,
bool vertical) {
std::vector<int> opt_seam = std::vector<int> opt_seam =
optimal_seam(source_energy, width, height, vertical); optimal_seam(source_energy, width, height, vertical);
remove_seam(source_img, output_img, width, height, nbChannels, vertical, remove_seam(source_img, output_img, width, height, nbChannels, vertical,
@ -207,40 +271,27 @@ std::vector<int> carving_step(const std::vector<unsigned char> source_img,
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 // Recompute the energy along the seam, we need a separate function for templating
if (energy_recompute_all) { recompute_energy_along_seam(
std::vector<float> energy = source_img, output_energy, opt_seam,
energy_e1(output_img, vertical ? width - 1 : width, width, height, nbChannels, nbColorChannels,
vertical ? height : height - 1, nbChannels); vertical
std::copy(energy.begin(), energy.end(), output_energy.begin()); );
} else {
// ASSUME WE ARE DOING A VERTICAL SEAM
if (vertical) {
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)) {
compute_energy_for_pixel(output_img, (width - 1), height,
(opt_seam[j] + i), j, nbChannels,
nbColorChannels, output_energy);
}
}
}
} else {
for (auto i = 0; i < width; i++) {
for (auto j = -1; j < 2; j++) {
if ((0 < (opt_seam[i] + j)) && ((opt_seam[i] + j) < height - 1)) {
compute_energy_for_pixel(output_img, width, height - 1, i,
(opt_seam[i] + j), nbChannels,
nbColorChannels, output_energy);
}
}
}
}
}
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;
}
void seam_carving(unsigned char *source, int width, int height, int nbChannels, void seam_carving(unsigned char *source, int width, int height, int nbChannels,
const char *out_filename, int nbSeams, bool vertical, unsigned char* mask=nullptr) { const char *out_filename, int nbSeams, bool vertical, unsigned char* mask=nullptr) {
int nbColorChannels = nbChannels > 3 ? 3 : nbChannels; int nbColorChannels = nbChannels > 3 ? 3 : nbChannels;
@ -255,6 +306,9 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
// Contains at each step the carved image // Contains at each step the carved image
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>> output_masked_energy(width*height);
// 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
std::vector<float> output_energy(width * height); std::vector<float> output_energy(width * height);
@ -271,6 +325,8 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
} }
source_energy = energy_e1(source_img, width, height, nbChannels); source_energy = energy_e1(source_img, width, height, nbChannels);
if (mask)
masked_energy = mask_energy(source_energy, width, height, mask);
if (test_energy) { if (test_energy) {
ini_energy = energy_e1(source_img, width, height, nbChannels); ini_energy = energy_e1(source_img, width, height, nbChannels);
@ -303,14 +359,27 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
} }
SimpleProgressBar::ProgressBar bar(nbSeams); SimpleProgressBar::ProgressBar bar(nbSeams);
bar.print();
for (auto seam_index = 0; seam_index < nbSeams; seam_index++) { for (auto seam_index = 0; seam_index < nbSeams; seam_index++) {
std::vector<int> opt_seam = carving_step( std::vector<int> opt_seam;
source_img, source_energy, output_img, output_energy, curWidth, if (mask) {
curHeight, nbChannels, nbColorChannels, vertical); opt_seam = carving_step(
source_img, masked_energy, output_img, output_masked_energy,
curWidth, curHeight, nbChannels, nbColorChannels, vertical
);
//} else {
// opt_seam = carving_step(
// source_img, source_energy, output_img, output_energy,
// curWidth, curHeight, nbChannels, 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(),
source_energy.begin()); source_energy.begin());
if (mask)
std::copy(output_masked_energy.begin(), output_masked_energy.end(),
masked_energy.begin());
vertical ? curWidth-- : curHeight--; // We just reduced the dimension vertical ? curWidth-- : curHeight--; // We just reduced the dimension
@ -335,6 +404,7 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
bar.increment(); bar.increment();
bar.print(); bar.print();
} }
std::cout << std::endl; // Add newline after ProgressBar
if (test_energy) { if (test_energy) {
export_image(out_filename, test_energy_output.data(), width, height, export_image(out_filename, test_energy_output.data(), width, height,
@ -400,11 +470,15 @@ int main(int argc, char **argv) {
r = mask[maskChannels*i]; r = mask[maskChannels*i];
g = mask[maskChannels*i]; g = mask[maskChannels*i];
b = mask[maskChannels*i]; b = mask[maskChannels*i];
mask[2*i] = (r == 0) && (g == 255) && (b == 0); bool positive = (g > r && g > b);
mask[2*i+1] = (r == 255) && (g == 0) && (b == 0); bool negative = (r > g && r > b);
mask[i] = positive ? 1 : (negative ? -1 : 0);
} }
//* From now on, mask has the same dimensions as source and exactly 2 channels //* From now on, mask has the same dimensions as source and one single channel
//* The first channel is positive, the second one negative. //* The values are:
//* . ( 1) we want to keep the pixel
//* . (-1) we want to remove the pixel
//* . ( 0) nothing in particular
} }
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