several improvements, including the ability to change the energy function
This commit is contained in:
parent
4ee23bb442
commit
3e96a3df05
@ -39,8 +39,11 @@ void export_image(const char *filename, const void *data, int width, int height,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string function = "grad";
|
||||||
|
|
||||||
#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, energy) \
|
||||||
|
/* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \
|
||||||
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; \
|
||||||
@ -53,29 +56,75 @@ void export_image(const char *filename, const void *data, int width, int height,
|
|||||||
? (nbChannels) * (width * (j) + ((i) + 1)) \
|
? (nbChannels) * (width * (j) + ((i) + 1)) \
|
||||||
: indexPixel; \
|
: indexPixel; \
|
||||||
energy[width * j + i] = 0; \
|
energy[width * j + i] = 0; \
|
||||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
if (function == "gradnorm") { \
|
||||||
energy[(width) * (j) + (i)] += \
|
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||||
(fabs((float)source[indexPixel_up + ch] - source[indexPixel + ch]) + \
|
energy[(width) * (j) + (i)] += \
|
||||||
fabs((float)source[indexPixel_down + ch] - source[indexPixel + ch]) + \
|
(std::pow(fabs((float)source[indexPixel_up + ch] - \
|
||||||
fabs((float)source[indexPixel_left + ch] - source[indexPixel + ch]) + \
|
source[indexPixel + ch]), \
|
||||||
fabs((float)source[indexPixel_right + ch] - \
|
2) + \
|
||||||
source[indexPixel + ch])); \
|
\
|
||||||
} \
|
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 == "grad") { \
|
||||||
|
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]))); \
|
||||||
|
} \
|
||||||
|
} else if (function == "gradnorminf") { \
|
||||||
|
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||||
|
energy[(width) * (j) + (i)] += \
|
||||||
|
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 { \
|
||||||
|
std::cerr << "function " << function << " not available" << std::endl; \
|
||||||
|
exit(1); \
|
||||||
|
};
|
||||||
|
|
||||||
// Le alpha n'est pas pris en compte dans l'énergie
|
// 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
|
// Here, we use this /ugly/ macro to avoid defining a function that would be way
|
||||||
// longer...
|
// slower...
|
||||||
|
|
||||||
/** e_1 energy, energy is always normalized between 0 and 1 */
|
/** 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) {
|
||||||
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);
|
||||||
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(source, width, height, i, j, nbChannels,
|
||||||
nbColorChannels, energy);
|
nbColorChannels, energy);
|
||||||
|
energy[width * j + i] = (energy[width * j + i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return energy;
|
return energy;
|
||||||
@ -190,23 +239,11 @@ void remove_seam(const std::vector<T> source, std::vector<T> &output, int width,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void update_energy_along_seam(std::vector<int> opt_seam,
|
||||||
std::vector<int> carving_step(const std::vector<unsigned char> source_img,
|
std::vector<unsigned char> output_img,
|
||||||
std::vector<float> source_energy,
|
std::vector<float> &output_energy, int height,
|
||||||
std::vector<unsigned char> &output_img,
|
int width, int nbChannels, bool vertical,
|
||||||
std::vector<float> &output_energy, int width,
|
int nbColorChannels) {
|
||||||
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);
|
|
||||||
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
|
// Recompute the energy along the seam
|
||||||
if (energy_recompute_all) {
|
if (energy_recompute_all) {
|
||||||
std::vector<float> energy =
|
std::vector<float> energy =
|
||||||
@ -214,9 +251,7 @@ std::vector<int> carving_step(const std::vector<unsigned char> source_img,
|
|||||||
vertical ? height : height - 1, nbChannels);
|
vertical ? height : height - 1, nbChannels);
|
||||||
std::copy(energy.begin(), energy.end(), output_energy.begin());
|
std::copy(energy.begin(), energy.end(), output_energy.begin());
|
||||||
} else {
|
} else {
|
||||||
|
if (vertical) { // Vertical seam
|
||||||
// ASSUME WE ARE DOING A VERTICAL SEAM
|
|
||||||
if (vertical) {
|
|
||||||
for (auto j = 0; j < height; j++) {
|
for (auto j = 0; j < height; j++) {
|
||||||
for (auto i = -1; i < 2; i++) {
|
for (auto i = -1; i < 2; i++) {
|
||||||
if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < width - 1)) {
|
if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < width - 1)) {
|
||||||
@ -238,11 +273,28 @@ 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<float> source_energy,
|
||||||
|
std::vector<unsigned char> &output_img,
|
||||||
|
std::vector<float> &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);
|
||||||
|
remove_seam(source_img, output_img, width, height, nbChannels, vertical,
|
||||||
|
opt_seam);
|
||||||
|
remove_seam(source_energy, output_energy, width, height, 1, vertical,
|
||||||
|
opt_seam);
|
||||||
|
update_energy_along_seam(opt_seam, output_img, output_energy, height, width,
|
||||||
|
nbChannels, vertical, nbColorChannels);
|
||||||
return opt_seam;
|
return opt_seam;
|
||||||
}
|
}
|
||||||
|
|
||||||
void seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
||||||
const char *out_filename, 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;
|
||||||
int curHeight = height;
|
int curHeight = height;
|
||||||
@ -337,11 +389,11 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (test_energy) {
|
if (test_energy) {
|
||||||
export_image(out_filename, test_energy_output.data(), width, height,
|
|
||||||
nbChannels);
|
return std::make_tuple(test_energy_output, width, height, nbChannels);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
export_image(out_filename, source_img.data(), curWidth, curHeight,
|
return std::make_tuple(source_img, curWidth, curHeight, nbChannels);
|
||||||
nbChannels);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,6 +424,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_flag("--energy-recompute-all", energy_recompute_all,
|
app.add_flag("--energy-recompute-all", energy_recompute_all,
|
||||||
"recompute the whole energy at each step");
|
"recompute the whole energy at each step");
|
||||||
|
app.add_option("-f,--function", function,
|
||||||
|
"The function to apply to compute the energy");
|
||||||
CLI11_PARSE(app, argc, argv);
|
CLI11_PARSE(app, argc, argv);
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
@ -379,38 +433,48 @@ int main(int argc, char **argv) {
|
|||||||
unsigned char *source =
|
unsigned char *source =
|
||||||
stbi_load(sourceImage.c_str(), &width, &height, &nbChannels, 0);
|
stbi_load(sourceImage.c_str(), &width, &height, &nbChannels, 0);
|
||||||
|
|
||||||
unsigned char* mask = nullptr;
|
unsigned char *mask = nullptr;
|
||||||
if (!maskImage.empty()) {
|
if (!maskImage.empty()) {
|
||||||
int maskWidth, maskHeight, maskChannels;
|
int maskWidth, maskHeight, maskChannels;
|
||||||
mask =
|
mask =
|
||||||
stbi_load(maskImage.c_str(), &maskWidth, &maskHeight, &maskChannels, 0);
|
stbi_load(maskImage.c_str(), &maskWidth, &maskHeight, &maskChannels, 0);
|
||||||
|
|
||||||
if (maskWidth != width || maskHeight != height) {
|
if (maskWidth != width || maskHeight != height) {
|
||||||
std::cerr << maskImage << " and " << sourceImage
|
std::cerr << maskImage << " and " << sourceImage
|
||||||
<< " differ in dimension. Please provide a valid mask."
|
<< " differ in dimension. Please provide a valid mask."
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (maskChannels < 3) {
|
if (maskChannels < 3) {
|
||||||
std::cerr << maskImage << " needs to be RGB." << std::endl;
|
std::cerr << maskImage << " needs to be RGB." << std::endl;
|
||||||
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];
|
g = mask[maskChannels * i];
|
||||||
b = mask[maskChannels*i];
|
b = mask[maskChannels * i];
|
||||||
mask[2*i] = (r == 0) && (g == 255) && (b == 0);
|
mask[2 * i] = (r == 0) && (g == 255) && (b == 0);
|
||||||
mask[2*i+1] = (r == 255) && (g == 0) && (b == 0);
|
mask[2 * i + 1] = (r == 255) && (g == 0) && (b == 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 exactly 2
|
||||||
//* The first channel is positive, the second one negative.
|
// channels
|
||||||
|
//* The first channel is positive, the second one negative.
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
seam_carving(source, width, height, nbChannels, outputImage.c_str(),
|
auto result = seam_carving(source, width, height, nbChannels, nbSeams,
|
||||||
nbSeams, vertical, mask=mask);
|
vertical, mask = mask);
|
||||||
|
auto content = std::get<0>(result);
|
||||||
|
int width_output = std::get<1>(result);
|
||||||
|
int height_output = std::get<2>(result);
|
||||||
|
int nbChannels_output = std::get<3>(result);
|
||||||
|
export_image(outputImage.c_str(), content.data(), width_output, height_output,
|
||||||
|
nbChannels_output);
|
||||||
|
|
||||||
stbi_image_free(source);
|
stbi_image_free(source);
|
||||||
exit(0);
|
exit(0);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user