Compare commits
No commits in common. "8fdcd1d966b9ab43a10a604552f3ad11f7526bad" and "a3019975c7f2b25613e9af794c86b7e2af60e6d1" have entirely different histories.
8fdcd1d966
...
a3019975c7
19
README.md
19
README.md
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## References:
|
## References:
|
||||||
- [Seam carving for content-aware image resizing](https://perso.crans.org/frenoy/matlab2012/seamcarving.pdf)
|
- [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
|
## Build
|
||||||
|
|
||||||
@ -16,21 +17,5 @@ cd ..
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
build/seam-carving ...
|
build/seam-carving --source imgs/birds.jpg --output out.jpg -n 300
|
||||||
```
|
```
|
||||||
|
|
||||||
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,21 +1,19 @@
|
|||||||
#ifndef STBIDEF
|
#ifndef STBIDEF
|
||||||
#ifdef STB_IMAGE_WRITE_STATIC
|
#ifdef STB_IMAGE_WRITE_STATIC
|
||||||
#define STBIDEF static
|
#define STBIDEF static
|
||||||
#else
|
#else
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
#define STBIDEF extern "C"
|
#define STBIDEF extern "C"
|
||||||
#else
|
#else
|
||||||
#define STBIDEF extern
|
#define STBIDEF extern
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef unsigned char stbi_uc;
|
typedef unsigned char stbi_uc;
|
||||||
|
|
||||||
STBIDEF int stbi_write_png(char const *filename, int w, int h, int comp,
|
STBIDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
|
||||||
const void *data, int stride_in_bytes);
|
|
||||||
|
|
||||||
STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp,
|
STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_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,113 +20,6 @@ bool until_mask_removal = false;
|
|||||||
int max_step = 1;
|
int max_step = 1;
|
||||||
std::string function = "grad";
|
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,
|
void export_image(const char *filename, const void *data, int width, int height,
|
||||||
int nbChannels) {
|
int nbChannels) {
|
||||||
@ -140,9 +33,117 @@ 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,
|
std::vector<float> energy_e1(std::vector<unsigned char> source, int width,
|
||||||
int height, int nbChannels) {
|
int height, int nbChannels) {
|
||||||
/* Compute the energy of an image, using the previous macro. */
|
|
||||||
int nbColorChannels =
|
int nbColorChannels =
|
||||||
nbChannels > 3 ? 3 : nbChannels; // nombre de canaux, excepté le alpha
|
nbChannels > 3 ? 3 : nbChannels; // nombre de canaux, excepté le alpha
|
||||||
std::vector<float> energy(width * height);
|
std::vector<float> energy(width * height);
|
||||||
@ -157,10 +158,10 @@ std::vector<float> energy_e1(std::vector<unsigned char> source, int width,
|
|||||||
return energy;
|
return energy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Given the energy value, returns the optimal seam */
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
|
std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
|
||||||
bool vertical) {
|
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)]
|
// dyn_energy is indexed by [dim_large*(i : dim_long) + (j : dim_large)]
|
||||||
std::vector<T> dyn_energy(width * height);
|
std::vector<T> dyn_energy(width * height);
|
||||||
@ -170,7 +171,7 @@ std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
|
|||||||
|
|
||||||
//* Find an end of the minimal connected vertical/horizontal seam
|
//* Find an end of the minimal connected vertical/horizontal seam
|
||||||
for (auto i = 0; i < dim_large; i++) {
|
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
|
for (auto i = 1; i < dim_long; i++) { // Propagate dyn_energy
|
||||||
@ -247,12 +248,11 @@ std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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,
|
||||||
const std::vector<int> seam) {
|
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
|
// remove the given seam from the image, the result is in output
|
||||||
// the output must have at least the right size!
|
// the output must have at least the right size!
|
||||||
int dim_large = vertical ? width : height;
|
int dim_large = vertical ? width : height;
|
||||||
@ -272,11 +272,8 @@ void remove_seam(const std::vector<T> source, std::vector<T> &output, int width,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The two next functions are used to update the energy after one seamcarving
|
// It would be preferable to use templates only for the value assignation but
|
||||||
// step on the image. It recomputes the energy along the seam. It would be
|
// this is in fact far less efficient
|
||||||
// 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,
|
void recompute_energy_along_seam(std::vector<unsigned char> carved_img,
|
||||||
std::vector<float> &output_energy,
|
std::vector<float> &output_energy,
|
||||||
std::vector<int> opt_seam, int width,
|
std::vector<int> opt_seam, int width,
|
||||||
@ -290,7 +287,6 @@ void recompute_energy_along_seam(std::vector<unsigned char> carved_img,
|
|||||||
|
|
||||||
for (auto j0 = 0; j0 < dim_long; j0++) {
|
for (auto j0 = 0; j0 < dim_long; j0++) {
|
||||||
auto i0 = opt_seam[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 i_offset = -max_step - 1; i_offset <= max_step + 1; i_offset++) {
|
||||||
for (auto j_offset = -max_step - 1; j_offset <= max_step + 1;
|
for (auto j_offset = -max_step - 1; j_offset <= max_step + 1;
|
||||||
j_offset++) {
|
j_offset++) {
|
||||||
@ -321,7 +317,6 @@ void recompute_energy_along_seam(
|
|||||||
|
|
||||||
for (auto j0 = 0; j0 < dim_long; j0++) {
|
for (auto j0 = 0; j0 < dim_long; j0++) {
|
||||||
auto i0 = opt_seam[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 i_offset = -max_step - 1; i_offset <= max_step + 1; i_offset++) {
|
||||||
for (auto j_offset = -max_step - 1; j_offset <= max_step + 1;
|
for (auto j_offset = -max_step - 1; j_offset <= max_step + 1;
|
||||||
j_offset++) {
|
j_offset++) {
|
||||||
@ -332,23 +327,24 @@ void recompute_energy_along_seam(
|
|||||||
// if the pixel is to be removed, we keep the energy at 0.
|
// if the pixel is to be removed, we keep the energy at 0.
|
||||||
if (output_energy[width * y + x].first != 0)
|
if (output_energy[width * y + x].first != 0)
|
||||||
compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y,
|
compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y,
|
||||||
nbChannels, nbColorChannels,
|
nbChannels, nbColorChannels,
|
||||||
output_energy[width * y + x].second);
|
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>
|
template <typename T>
|
||||||
|
|
||||||
std::vector<int> carving_step(const std::vector<unsigned char> source_img,
|
std::vector<int> carving_step(const std::vector<unsigned char> source_img,
|
||||||
std::vector<T> source_energy,
|
std::vector<T> source_energy,
|
||||||
std::vector<unsigned char> &output_img,
|
std::vector<unsigned char> &output_img,
|
||||||
std::vector<T> &output_energy, int width,
|
std::vector<T> &output_energy, int width,
|
||||||
int height, int nbChannels, int nbColorChannels,
|
int height, int nbChannels, int nbColorChannels,
|
||||||
bool vertical) {
|
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 =
|
std::vector<int> opt_seam =
|
||||||
optimal_seam(source_energy, width, height, vertical);
|
optimal_seam(source_energy, width, height, vertical);
|
||||||
@ -364,15 +360,18 @@ std::vector<int> carving_step(const std::vector<unsigned char> source_img,
|
|||||||
return opt_seam;
|
return opt_seam;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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) {
|
||||||
/* Performs seam Carving */
|
|
||||||
int nbColorChannels = nbChannels > 3 ? 3 : nbChannels;
|
int nbColorChannels = nbChannels > 3 ? 3 : nbChannels;
|
||||||
int curWidth = width;
|
int curWidth = width;
|
||||||
int curHeight = height;
|
int curHeight = height;
|
||||||
|
|
||||||
int dim_large = vertical ? width : height;
|
int dim_large = vertical ? width : height;
|
||||||
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> 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);
|
std::vector<float> source_energy(width * height);
|
||||||
@ -390,9 +389,11 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
|||||||
// Contains the initial energy, only for "test_energy"
|
// Contains the initial energy, only for "test_energy"
|
||||||
std::vector<unsigned char> test_energy_output(width * height * nbChannels);
|
std::vector<unsigned char> test_energy_output(width * height * nbChannels);
|
||||||
// Final output for "test_energy"
|
// Final output for "test_energy"
|
||||||
|
|
||||||
for (auto i = 0; i < width * height * nbChannels; i++) {
|
for (auto i = 0; i < width * height * nbChannels; i++) {
|
||||||
source_img[i] = source[i];
|
source_img[i] = source[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
source_energy = energy_e1(source_img, width, height, nbChannels);
|
source_energy = energy_e1(source_img, width, height, nbChannels);
|
||||||
if (mask)
|
if (mask)
|
||||||
masked_energy = mask_energy(source_energy, width, height, mask);
|
masked_energy = mask_energy(source_energy, width, height, mask);
|
||||||
@ -440,7 +441,6 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
|||||||
|
|
||||||
auto seam_index = 0;
|
auto seam_index = 0;
|
||||||
while (seam_index++ < nbSeams || until_mask_removal) {
|
while (seam_index++ < nbSeams || until_mask_removal) {
|
||||||
// Main loop of the program: we remove a seam until the end.
|
|
||||||
std::vector<int> opt_seam;
|
std::vector<int> opt_seam;
|
||||||
if (mask) {
|
if (mask) {
|
||||||
opt_seam = carving_step(source_img, masked_energy, output_img,
|
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);
|
nbChannels, nbColorChannels, vertical);
|
||||||
|
|
||||||
if (until_mask_removal &&
|
if (until_mask_removal &&
|
||||||
!does_seam_remove_mask(masked_energy, curWidth, curHeight, nbChannels,
|
!does_seam_remove_mask(masked_energy, curWidth, curHeight, nbChannels, opt_seam,
|
||||||
opt_seam, vertical))
|
vertical))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -503,6 +503,7 @@ int main(int argc, char **argv) {
|
|||||||
std::string outputImage = "output.png";
|
std::string outputImage = "output.png";
|
||||||
int nbSeams = DEFAULT_SEAMS;
|
int nbSeams = DEFAULT_SEAMS;
|
||||||
bool vertical = false;
|
bool vertical = false;
|
||||||
|
|
||||||
app.add_option("-s,--source", sourceImage, "Source image")
|
app.add_option("-s,--source", sourceImage, "Source image")
|
||||||
->required()
|
->required()
|
||||||
->check(CLI::ExistingFile);
|
->check(CLI::ExistingFile);
|
||||||
@ -512,10 +513,6 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
app.add_option("-n,--nb-seams", nbSeams, "Number of seams")
|
app.add_option("-n,--nb-seams", nbSeams, "Number of seams")
|
||||||
->check(CLI::Number);
|
->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")
|
app.add_option("--max-step", max_step, "Max width of step to find a seam")
|
||||||
->check(CLI::Number);
|
->check(CLI::Number);
|
||||||
|
|
||||||
@ -524,9 +521,11 @@ int main(int argc, char **argv) {
|
|||||||
app.add_flag("--silent", silent, "No verbose messages");
|
app.add_flag("--silent", silent, "No verbose messages");
|
||||||
app.add_flag("--show-seams", show_seams,
|
app.add_flag("--show-seams", show_seams,
|
||||||
"Don't resize image, just try the specified energy function");
|
"Don't resize image, just try the specified energy function");
|
||||||
app.add_option(
|
app.add_option("-f,--function", function,
|
||||||
"-f,--function", function,
|
"The function to apply to compute the energy");
|
||||||
"The function to apply to compute the energy (default: gradient)");
|
app.add_flag(
|
||||||
|
"-u,--until-mask-removal", until_mask_removal,
|
||||||
|
"Carve the image until there are no more red pixels in the mask");
|
||||||
CLI11_PARSE(app, argc, argv);
|
CLI11_PARSE(app, argc, argv);
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <random>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <random>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
@ -10,29 +10,28 @@
|
|||||||
|
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
|
|
||||||
bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, int width,
|
bool nearly_equal(float a, float b) {
|
||||||
int height, int nbChannels,
|
return std::nextafter(a, std::numeric_limits<float>::lowest()) <= b &&
|
||||||
std::vector<int> opt_seam, bool vertical)
|
std::nextafter(a, std::numeric_limits<float>::max()) >= b;
|
||||||
/* returns true if and only if the given seam removes one pixel to remove given
|
}
|
||||||
by the mask*/
|
|
||||||
{
|
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)
|
||||||
|
{
|
||||||
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 (energy[im_index(i, opt_seam[i])].first == 0)
|
if (energy[im_index(i, opt_seam[i])].first == 0) return true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
int width, int height, unsigned char* mask) {
|
||||||
unsigned char *mask) {
|
std::vector<std::pair<int, float>> output(width*height);
|
||||||
/* 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
|
if (mask[i] == 0) // We want to remove the pixel
|
||||||
energy[i] = 0.;
|
energy[i] = 0.;
|
||||||
|
|
||||||
@ -42,23 +41,20 @@ std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Here we overload the operators on pairs of int and float, to ease the
|
std::pair<int, float> operator+(std::pair<int, float>& p1, std::pair<int, float>& p2) {
|
||||||
* readability of the code for removing a part of an image using a mask. */
|
return {
|
||||||
std::pair<int, float> operator+(std::pair<int, float> &p1,
|
// If one of the two pixels is "protected" (ie 2), we want to prevent this line removing
|
||||||
std::pair<int, float> &p2) {
|
// Else, we want to keep the information of "is there a pixel to remove in this seam" (ie 0)
|
||||||
return {// If one of the two pixels is "protected" (ie 2), we want to prevent
|
(p1.first==2) || (p2.first==2) ? 2 : std::min(p1.first, p2.first),
|
||||||
// this line removing
|
p1.second+p2.second
|
||||||
// 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) {
|
void operator+=(std::pair<int, float>& p1, std::pair<int, float>& p2) {
|
||||||
p1 = p1 + p2;
|
p1 = p1+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) {
|
||||||
return (p1.first == p2.first && p1.second == p2.second);
|
return (p1.first==p2.first && p1.second == p2.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string str_of_e(std::pair<int, float> energy) {
|
std::string str_of_e(std::pair<int, float> energy) {
|
||||||
|
@ -4,49 +4,57 @@
|
|||||||
// : 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))
|
||||||
|
|
||||||
bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, int width,
|
// Check if two floats are nearly equal
|
||||||
int height, int nbChannels,
|
bool nearly_equal(float a, float b);
|
||||||
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,
|
std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
|
||||||
int width, int height,
|
int width, int height, unsigned char* mask);
|
||||||
unsigned char *mask);
|
|
||||||
|
|
||||||
//* Operators overwrites
|
//* Operators overwrites
|
||||||
|
|
||||||
std::pair<int, float> operator+(std::pair<int, float> &p1,
|
std::pair<int, float> operator+(std::pair<int, float>& p1, std::pair<int, float>& p2);
|
||||||
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(std::pair<int, float> energy);
|
||||||
std::string str_of_e(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 null_energy {
|
namespace limits {
|
||||||
template <class T> operator T() { return 0.; }
|
struct max_energy {
|
||||||
operator std::pair<int, float>() { return {0, limits::null_energy()}; }
|
template<class T> operator T() {
|
||||||
};
|
return std::numeric_limits<T>::max();
|
||||||
} // namespace limits
|
}
|
||||||
|
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()};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void compute_seam_tot(std::vector<int> opt_seam, std::vector<T> energy,
|
void compute_seam_tot(std::vector<int> opt_seam, std::vector<T> energy, int width, int height, bool vertical) {
|
||||||
int width, int height, bool vertical) {
|
|
||||||
int dim_long = vertical ? height : width;
|
int dim_long = vertical ? height : width;
|
||||||
|
|
||||||
|
|
||||||
T sum = limits::null_energy();
|
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])];
|
sum += energy[im_index(i, opt_seam[i])];
|
||||||
// std::cout << str_of_e(sum);
|
// std::cout << str_of_e(sum);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user