Compare commits
5 Commits
a3019975c7
...
8fdcd1d966
Author | SHA1 | Date | |
---|---|---|---|
8fdcd1d966 | |||
d4e09bf957 | |||
2a612c6064 | |||
1e7b3b0031 | |||
79ea42bdf7 |
19
README.md
19
README.md
@ -2,7 +2,6 @@
|
||||
|
||||
## References:
|
||||
- [Seam carving for content-aware image resizing](https://perso.crans.org/frenoy/matlab2012/seamcarving.pdf)
|
||||
- [By-example Synthesis of Architectural Textures](https://inria.hal.science/inria-00547754v1/file/paper.pdf)
|
||||
|
||||
## Build
|
||||
|
||||
@ -17,5 +16,21 @@ cd ..
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
build/seam-carving --source imgs/birds.jpg --output out.jpg -n 300
|
||||
build/seam-carving ...
|
||||
```
|
||||
|
||||
Mandatory arguments:
|
||||
|
||||
- `-s FILE`: source image
|
||||
- `-o FILE`: output image
|
||||
|
||||
Options:
|
||||
- `-m FILE`: the mask. RGB image, of the same size than the source, red pixel are those to be removed, the green ones must be kept.
|
||||
- `-n NUMBER`: number of seams to compute or remove (default 0)
|
||||
- `--show-seams`: do not carve the image, only outputs the energy and the relevant seams
|
||||
- `-f FUNCTION NAME`: the energy function to use, see the [beginning of the source file to see the available functions](src/seam-carving.cpp)
|
||||
- `-u`: when a mask is provided and no seam number is provided, remove seams until the zone to remove has disappeared.
|
||||
|
||||
## Generation of the images of the report
|
||||
|
||||
A `Makefile` is provided at the root of the repo, it can be used to generate the images that are shown in the report.
|
||||
|
14
src/image.h
14
src/image.h
@ -1,19 +1,21 @@
|
||||
#ifndef STBIDEF
|
||||
#ifdef STB_IMAGE_WRITE_STATIC
|
||||
#define STBIDEF static
|
||||
#define STBIDEF static
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
#define STBIDEF extern "C"
|
||||
#define STBIDEF extern "C"
|
||||
#else
|
||||
#define STBIDEF extern
|
||||
#define STBIDEF extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef unsigned char stbi_uc;
|
||||
|
||||
STBIDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
|
||||
STBIDEF int stbi_write_png(char const *filename, int w, int h, int comp,
|
||||
const void *data, int stride_in_bytes);
|
||||
|
||||
STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp);
|
||||
STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp,
|
||||
int req_comp);
|
||||
|
||||
STBIDEF void stbi_image_free (void *retval_from_stbi_load);
|
||||
STBIDEF void stbi_image_free(void *retval_from_stbi_load);
|
||||
|
@ -20,6 +20,113 @@ bool until_mask_removal = false;
|
||||
int max_step = 1;
|
||||
std::string function = "grad";
|
||||
|
||||
/* The next macro is ugly, but speeds up the computation. It computes the
|
||||
* energy at one pixel of the image, depending on the energy function used */
|
||||
#define compute_energy_for_pixel( \
|
||||
source, width, height, i, j, \
|
||||
nbChannels, /* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \
|
||||
nbColorChannels, dest) \
|
||||
{ \
|
||||
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; \
|
||||
dest = 0; \
|
||||
if (function == "grad") { \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += ((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 == "gradnorm") { \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += (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 == "gradhoriz") { \
|
||||
/* take the gradient along the horizontal only*/ \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += ((fabs((float)source[indexPixel_left + ch] - \
|
||||
source[indexPixel + ch])) + \
|
||||
(fabs((float)source[indexPixel_right + ch] - \
|
||||
source[indexPixel + ch]))); \
|
||||
} \
|
||||
} else if (function == "gradvertic") { \
|
||||
/* take the gradient along the vertical only*/ \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += ((fabs((float)source[indexPixel_up + ch] - \
|
||||
source[indexPixel + ch])) + \
|
||||
(fabs((float)source[indexPixel_down + ch] - \
|
||||
source[indexPixel + ch]))); \
|
||||
} \
|
||||
} else if (function == "gradnorminf") { \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += 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 if (function == "sumexpgradcomp") { \
|
||||
\
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += (std::exp(fabs((float)source[indexPixel_up + ch] - \
|
||||
source[indexPixel + ch]) / \
|
||||
255) + \
|
||||
\
|
||||
std::exp(fabs((float)source[indexPixel_down + ch] - \
|
||||
source[indexPixel + ch]) / \
|
||||
255) + \
|
||||
\
|
||||
std::exp(fabs((float)source[indexPixel_left + ch] - \
|
||||
source[indexPixel + ch]) / \
|
||||
255) + \
|
||||
std::exp(fabs((float)source[indexPixel_right + ch] - \
|
||||
source[indexPixel + ch]) / \
|
||||
255)); \
|
||||
} \
|
||||
} else { \
|
||||
std::cerr << "no implementation found for function \"" << function \
|
||||
<< "\"" << 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
|
||||
// slower...
|
||||
|
||||
void export_image(const char *filename, const void *data, int width, int height,
|
||||
int nbChannels) {
|
||||
@ -33,117 +140,9 @@ void export_image(const char *filename, const void *data, int width, int height,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define compute_energy_for_pixel( \
|
||||
source, width, height, i, j, \
|
||||
nbChannels, /* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \
|
||||
nbColorChannels, dest) { \
|
||||
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; \
|
||||
dest = 0; \
|
||||
if (function == "grad") { \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += ((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 == "gradnorm") { \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += (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 == "gradhoriz") { \
|
||||
/* take the gradient along the horizontal only*/ \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += ((fabs((float)source[indexPixel_left + ch] - \
|
||||
source[indexPixel + ch])) + \
|
||||
(fabs((float)source[indexPixel_right + ch] - \
|
||||
source[indexPixel + ch]))); \
|
||||
} \
|
||||
} else if (function == "gradvertic") { \
|
||||
/* take the gradient along the vertical only*/ \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += ((fabs((float)source[indexPixel_up + ch] - \
|
||||
source[indexPixel + ch])) + \
|
||||
(fabs((float)source[indexPixel_down + ch] - \
|
||||
source[indexPixel + ch]))); \
|
||||
} \
|
||||
} else if (function == "gradnorminf") { \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += 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 if (function == "sumexpgradcomp") { \
|
||||
\
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
dest += (std::exp(fabs((float)source[indexPixel_up + ch] - \
|
||||
source[indexPixel + ch]) / \
|
||||
255) + \
|
||||
\
|
||||
std::exp(fabs((float)source[indexPixel_down + ch] - \
|
||||
source[indexPixel + ch]) / \
|
||||
255) + \
|
||||
\
|
||||
std::exp(fabs((float)source[indexPixel_left + ch] - \
|
||||
source[indexPixel + ch]) / \
|
||||
255) + \
|
||||
std::exp(fabs((float)source[indexPixel_right + ch] - \
|
||||
source[indexPixel + ch]) / \
|
||||
255)); \
|
||||
} \
|
||||
} else { \
|
||||
std::cerr << "no implementation found for function \"" << function << "\"" \
|
||||
<< 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
|
||||
// slower...
|
||||
|
||||
|
||||
/** e_1 energy*/
|
||||
std::vector<float> energy_e1(std::vector<unsigned char> source, int width,
|
||||
int height, int nbChannels) {
|
||||
/* Compute the energy of an image, using the previous macro. */
|
||||
int nbColorChannels =
|
||||
nbChannels > 3 ? 3 : nbChannels; // nombre de canaux, excepté le alpha
|
||||
std::vector<float> energy(width * height);
|
||||
@ -158,10 +157,10 @@ std::vector<float> energy_e1(std::vector<unsigned char> source, int width,
|
||||
return energy;
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
/* Performs dynamic programming to find the optimal seam given the energy. */
|
||||
|
||||
// dyn_energy is indexed by [dim_large*(i : dim_long) + (j : dim_large)]
|
||||
std::vector<T> dyn_energy(width * height);
|
||||
@ -171,7 +170,7 @@ std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
|
||||
|
||||
//* Find an end of the minimal connected vertical/horizontal seam
|
||||
for (auto i = 0; i < dim_large; i++) {
|
||||
dyn_energy[i] = energy[vertical ? i : i*width];
|
||||
dyn_energy[i] = energy[vertical ? i : i * width];
|
||||
}
|
||||
|
||||
for (auto i = 1; i < dim_long; i++) { // Propagate dyn_energy
|
||||
@ -248,11 +247,12 @@ std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Carves an image by one seam. Returns the optimal seam used */
|
||||
template <typename T>
|
||||
void remove_seam(const std::vector<T> source, std::vector<T> &output, int width,
|
||||
int height, int nbChannels, bool vertical,
|
||||
const std::vector<int> seam) {
|
||||
/* Carves an image by one seam. (the image can be a real image, or an energy
|
||||
* vector). */
|
||||
// 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;
|
||||
@ -272,8 +272,11 @@ 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
|
||||
// this is in fact far less efficient
|
||||
// The two next functions are used to update the energy after one seamcarving
|
||||
// step on the image. It recomputes the energy along the seam. It would be
|
||||
// preferable to use templates only for the value assignation but this is in
|
||||
// fact far less efficient
|
||||
|
||||
void recompute_energy_along_seam(std::vector<unsigned char> carved_img,
|
||||
std::vector<float> &output_energy,
|
||||
std::vector<int> opt_seam, int width,
|
||||
@ -287,6 +290,7 @@ void recompute_energy_along_seam(std::vector<unsigned char> carved_img,
|
||||
|
||||
for (auto j0 = 0; j0 < dim_long; j0++) {
|
||||
auto i0 = opt_seam[j0];
|
||||
// recompute the energy on a square around (i0,j0) that was removed.
|
||||
for (auto i_offset = -max_step - 1; i_offset <= max_step + 1; i_offset++) {
|
||||
for (auto j_offset = -max_step - 1; j_offset <= max_step + 1;
|
||||
j_offset++) {
|
||||
@ -317,6 +321,7 @@ void recompute_energy_along_seam(
|
||||
|
||||
for (auto j0 = 0; j0 < dim_long; j0++) {
|
||||
auto i0 = opt_seam[j0];
|
||||
// recompute the energy on a square around (i0,j0) that was removed.
|
||||
for (auto i_offset = -max_step - 1; i_offset <= max_step + 1; i_offset++) {
|
||||
for (auto j_offset = -max_step - 1; j_offset <= max_step + 1;
|
||||
j_offset++) {
|
||||
@ -327,24 +332,23 @@ void recompute_energy_along_seam(
|
||||
// if the pixel is to be removed, we keep the energy at 0.
|
||||
if (output_energy[width * y + x].first != 0)
|
||||
compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y,
|
||||
nbChannels, nbColorChannels,
|
||||
output_energy[width * y + x].second);
|
||||
nbChannels, nbColorChannels,
|
||||
output_energy[width * y + x].second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Carves an image and its energy by one seam, and recomputes the energy.
|
||||
Returns the optimal seam used */
|
||||
template <typename T>
|
||||
|
||||
std::vector<int> carving_step(const std::vector<unsigned char> source_img,
|
||||
std::vector<T> source_energy,
|
||||
std::vector<unsigned char> &output_img,
|
||||
std::vector<T> &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<int> opt_seam =
|
||||
optimal_seam(source_energy, width, height, vertical);
|
||||
@ -360,18 +364,15 @@ std::vector<int> carving_step(const std::vector<unsigned char> source_img,
|
||||
return opt_seam;
|
||||
}
|
||||
|
||||
|
||||
auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
||||
int nbSeams, bool vertical, unsigned char *mask = nullptr) {
|
||||
|
||||
/* Performs seam Carving */
|
||||
int nbColorChannels = nbChannels > 3 ? 3 : nbChannels;
|
||||
int curWidth = width;
|
||||
int curHeight = height;
|
||||
|
||||
int dim_large = vertical ? width : height;
|
||||
int dim_long = vertical ? height : width;
|
||||
// dim_long=longueur des seam
|
||||
|
||||
std::vector<unsigned char> source_img(width * height * nbChannels);
|
||||
// Contains at each step the carved image
|
||||
std::vector<float> source_energy(width * height);
|
||||
@ -389,11 +390,9 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
||||
// Contains the initial energy, only for "test_energy"
|
||||
std::vector<unsigned char> test_energy_output(width * height * nbChannels);
|
||||
// Final output for "test_energy"
|
||||
|
||||
for (auto i = 0; i < width * height * nbChannels; i++) {
|
||||
source_img[i] = source[i];
|
||||
}
|
||||
|
||||
source_energy = energy_e1(source_img, width, height, nbChannels);
|
||||
if (mask)
|
||||
masked_energy = mask_energy(source_energy, width, height, mask);
|
||||
@ -441,6 +440,7 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
||||
|
||||
auto seam_index = 0;
|
||||
while (seam_index++ < nbSeams || until_mask_removal) {
|
||||
// Main loop of the program: we remove a seam until the end.
|
||||
std::vector<int> opt_seam;
|
||||
if (mask) {
|
||||
opt_seam = carving_step(source_img, masked_energy, output_img,
|
||||
@ -448,8 +448,8 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
||||
nbChannels, nbColorChannels, vertical);
|
||||
|
||||
if (until_mask_removal &&
|
||||
!does_seam_remove_mask(masked_energy, curWidth, curHeight, nbChannels, opt_seam,
|
||||
vertical))
|
||||
!does_seam_remove_mask(masked_energy, curWidth, curHeight, nbChannels,
|
||||
opt_seam, vertical))
|
||||
break;
|
||||
|
||||
} else {
|
||||
@ -503,7 +503,6 @@ int main(int argc, char **argv) {
|
||||
std::string outputImage = "output.png";
|
||||
int nbSeams = DEFAULT_SEAMS;
|
||||
bool vertical = false;
|
||||
|
||||
app.add_option("-s,--source", sourceImage, "Source image")
|
||||
->required()
|
||||
->check(CLI::ExistingFile);
|
||||
@ -513,6 +512,10 @@ int main(int argc, char **argv) {
|
||||
|
||||
app.add_option("-n,--nb-seams", nbSeams, "Number of seams")
|
||||
->check(CLI::Number);
|
||||
app.add_flag(
|
||||
"-u,--until-mask-removal", until_mask_removal,
|
||||
"Carve the image until there are no more red pixels in the mask");
|
||||
|
||||
app.add_option("--max-step", max_step, "Max width of step to find a seam")
|
||||
->check(CLI::Number);
|
||||
|
||||
@ -521,11 +524,9 @@ int main(int argc, char **argv) {
|
||||
app.add_flag("--silent", silent, "No verbose messages");
|
||||
app.add_flag("--show-seams", show_seams,
|
||||
"Don't resize image, just try the specified energy function");
|
||||
app.add_option("-f,--function", function,
|
||||
"The function to apply to compute the energy");
|
||||
app.add_flag(
|
||||
"-u,--until-mask-removal", until_mask_removal,
|
||||
"Carve the image until there are no more red pixels in the mask");
|
||||
app.add_option(
|
||||
"-f,--function", function,
|
||||
"The function to apply to compute the energy (default: gradient)");
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
// Image loading
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
@ -10,28 +10,29 @@
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
bool nearly_equal(float a, float b) {
|
||||
return std::nextafter(a, std::numeric_limits<float>::lowest()) <= b &&
|
||||
std::nextafter(a, std::numeric_limits<float>::max()) >= b;
|
||||
}
|
||||
|
||||
bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, int width, int height, int nbChannels,
|
||||
std::vector<int> opt_seam, bool vertical)
|
||||
{
|
||||
bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, int width,
|
||||
int height, int nbChannels,
|
||||
std::vector<int> opt_seam, bool vertical)
|
||||
/* returns true if and only if the given seam removes one pixel to remove given
|
||||
by the mask*/
|
||||
{
|
||||
int dim_large = vertical ? width : height;
|
||||
int dim_long = vertical ? height : width;
|
||||
|
||||
for (int i=0; i < dim_long; i++) {
|
||||
if (energy[im_index(i, opt_seam[i])].first == 0) return true;
|
||||
for (int i = 0; i < dim_long; i++) {
|
||||
if (energy[im_index(i, opt_seam[i])].first == 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);
|
||||
int width, int height,
|
||||
unsigned char *mask) {
|
||||
/* transform the computed energy to the energy associated to the 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++) {
|
||||
if (mask[i] == 0) // We want to remove the pixel
|
||||
energy[i] = 0.;
|
||||
|
||||
@ -41,20 +42,23 @@ std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
|
||||
return output;
|
||||
}
|
||||
|
||||
std::pair<int, float> operator+(std::pair<int, float>& p1, std::pair<int, float>& p2) {
|
||||
return {
|
||||
// If one of the two pixels is "protected" (ie 2), we want to prevent this line removing
|
||||
// Else, we want to keep the information of "is there a pixel to remove in this seam" (ie 0)
|
||||
(p1.first==2) || (p2.first==2) ? 2 : std::min(p1.first, p2.first),
|
||||
p1.second+p2.second
|
||||
};
|
||||
/* Here we overload the operators on pairs of int and float, to ease the
|
||||
* readability of the code for removing a part of an image using a mask. */
|
||||
std::pair<int, float> operator+(std::pair<int, float> &p1,
|
||||
std::pair<int, float> &p2) {
|
||||
return {// If one of the two pixels is "protected" (ie 2), we want to prevent
|
||||
// this line removing
|
||||
// Else, we want to keep the information of "is there a pixel to
|
||||
// remove in this seam" (ie 0)
|
||||
(p1.first == 2) || (p2.first == 2) ? 2 : std::min(p1.first, p2.first),
|
||||
p1.second + p2.second};
|
||||
}
|
||||
|
||||
void operator+=(std::pair<int, float>& p1, std::pair<int, float>& p2) {
|
||||
p1 = p1+p2;
|
||||
void operator+=(std::pair<int, float> &p1, std::pair<int, float> &p2) {
|
||||
p1 = p1 + p2;
|
||||
}
|
||||
bool operator==(std::pair<int, float>& p1, std::pair<int, float>& p2) {
|
||||
return (p1.first==p2.first && p1.second == p2.second);
|
||||
bool operator==(std::pair<int, float> &p1, std::pair<int, float> &p2) {
|
||||
return (p1.first == p2.first && p1.second == p2.second);
|
||||
}
|
||||
|
||||
std::string str_of_e(std::pair<int, float> energy) {
|
||||
|
@ -4,57 +4,49 @@
|
||||
// : dim_long, b : dim_large
|
||||
#define im_index(a, b) (vertical ? (width * a + b) : (width * b + a))
|
||||
|
||||
// Check if two floats are nearly equal
|
||||
bool nearly_equal(float a, float b);
|
||||
|
||||
bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, int width, int height, int nbChannels,
|
||||
std::vector<int> opt_seam, bool vertical);
|
||||
bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, 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);
|
||||
|
||||
int width, int height,
|
||||
unsigned char *mask);
|
||||
|
||||
//* Operators overwrites
|
||||
|
||||
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);
|
||||
|
||||
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 operator==(std::pair<int, float>& p1, std::pair<int, float>& p2);
|
||||
bool operator==(std::pair<int, float> &p1, std::pair<int, float> &p2);
|
||||
|
||||
|
||||
// Unfortunately, std::cout << (std::pair<>) does not work and can't be rewritten
|
||||
// Unfortunately, std::cout << (std::pair<>) does not work and can't be
|
||||
// rewritten
|
||||
std::string str_of_e(std::pair<int, float> energy);
|
||||
std::string str_of_e(float energy);
|
||||
|
||||
|
||||
namespace limits {
|
||||
struct max_energy {
|
||||
template<class T> operator T() {
|
||||
return std::numeric_limits<T>::max();
|
||||
}
|
||||
operator std::pair<int, float>() {
|
||||
return {3, std::numeric_limits<float>::max()};
|
||||
}
|
||||
};
|
||||
struct max_energy {
|
||||
template <class T> operator T() { return std::numeric_limits<T>::max(); }
|
||||
operator std::pair<int, float>() {
|
||||
return {3, std::numeric_limits<float>::max()};
|
||||
}
|
||||
};
|
||||
|
||||
struct null_energy {
|
||||
template<class T> operator T() {
|
||||
return 0.;
|
||||
}
|
||||
operator std::pair<int, float>() {
|
||||
return {0, limits::null_energy()};
|
||||
}
|
||||
};
|
||||
}
|
||||
struct null_energy {
|
||||
template <class T> operator T() { return 0.; }
|
||||
operator std::pair<int, float>() { return {0, limits::null_energy()}; }
|
||||
};
|
||||
} // namespace limits
|
||||
|
||||
template <typename T>
|
||||
void compute_seam_tot(std::vector<int> opt_seam, std::vector<T> energy, int width, int height, bool vertical) {
|
||||
void compute_seam_tot(std::vector<int> opt_seam, std::vector<T> energy,
|
||||
int width, int height, bool vertical) {
|
||||
int dim_long = vertical ? height : width;
|
||||
|
||||
|
||||
T sum = limits::null_energy();
|
||||
for (int i=0; i < dim_long; i++) {
|
||||
for (int i = 0; i < dim_long; i++) {
|
||||
sum += energy[im_index(i, opt_seam[i])];
|
||||
// std::cout << str_of_e(sum);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user