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, \
|
||||
nbColorChannels, energy) \
|
||||
/* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \
|
||||
auto indexPixel = (nbChannels) * (width * (j) + (i)); \
|
||||
auto indexPixel_up = \
|
||||
((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)) \
|
||||
: 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])); \
|
||||
} \
|
||||
if (function == "gradnorm") { \
|
||||
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
|
||||
energy[(width) * (j) + (i)] += \
|
||||
(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 == "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
|
||||
// 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,
|
||||
int height, int nbChannels) {
|
||||
int nbColorChannels =
|
||||
nbChannels > 3 ? 3 : nbChannels; // nombre de canaux, excepté le alpha
|
||||
std::vector<float> energy(width * height);
|
||||
float max_energy = 0;
|
||||
|
||||
for (auto i = 0; i < width; i++) {
|
||||
for (auto j = 0; j < height; j++) {
|
||||
compute_energy_for_pixel(source, width, height, i, j, nbChannels,
|
||||
nbColorChannels, energy);
|
||||
energy[width * j + i] = (energy[width * j + i]);
|
||||
}
|
||||
}
|
||||
return energy;
|
||||
@ -190,23 +239,11 @@ 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,
|
||||
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);
|
||||
|
||||
void update_energy_along_seam(std::vector<int> opt_seam,
|
||||
std::vector<unsigned char> output_img,
|
||||
std::vector<float> &output_energy, int height,
|
||||
int width, int nbChannels, bool vertical,
|
||||
int nbColorChannels) {
|
||||
// Recompute the energy along the seam
|
||||
if (energy_recompute_all) {
|
||||
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);
|
||||
std::copy(energy.begin(), energy.end(), output_energy.begin());
|
||||
} else {
|
||||
|
||||
// ASSUME WE ARE DOING A VERTICAL SEAM
|
||||
if (vertical) {
|
||||
if (vertical) { // 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 - 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;
|
||||
}
|
||||
|
||||
void seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
||||
const char *out_filename, int nbSeams, bool vertical, unsigned char* mask=nullptr) {
|
||||
auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
||||
int nbSeams, bool vertical, unsigned char *mask = nullptr) {
|
||||
int nbColorChannels = nbChannels > 3 ? 3 : nbChannels;
|
||||
int curWidth = width;
|
||||
int curHeight = height;
|
||||
@ -337,11 +389,11 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels,
|
||||
}
|
||||
|
||||
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 {
|
||||
export_image(out_filename, source_img.data(), curWidth, curHeight,
|
||||
nbChannels);
|
||||
return std::make_tuple(source_img, curWidth, curHeight, nbChannels);
|
||||
}
|
||||
}
|
||||
|
||||
@ -352,7 +404,7 @@ int main(int argc, char **argv) {
|
||||
std::string outputImage = "output.png";
|
||||
int nbSeams = 1;
|
||||
bool vertical = false;
|
||||
|
||||
|
||||
app.add_option("-s,--source", sourceImage, "Source image")
|
||||
->required()
|
||||
->check(CLI::ExistingFile);
|
||||
@ -372,6 +424,8 @@ int main(int argc, char **argv) {
|
||||
"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");
|
||||
app.add_option("-f,--function", function,
|
||||
"The function to apply to compute the energy");
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
// Image loading
|
||||
@ -379,38 +433,48 @@ int main(int argc, char **argv) {
|
||||
unsigned char *source =
|
||||
stbi_load(sourceImage.c_str(), &width, &height, &nbChannels, 0);
|
||||
|
||||
unsigned char* mask = nullptr;
|
||||
unsigned char *mask = nullptr;
|
||||
if (!maskImage.empty()) {
|
||||
int maskWidth, maskHeight, maskChannels;
|
||||
mask =
|
||||
stbi_load(maskImage.c_str(), &maskWidth, &maskHeight, &maskChannels, 0);
|
||||
|
||||
if (maskWidth != width || maskHeight != height) {
|
||||
std::cerr << maskImage << " and " << sourceImage
|
||||
<< " differ in dimension. Please provide a valid mask."
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
if (maskChannels < 3) {
|
||||
std::cerr << maskImage << " needs to be RGB." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
unsigned char r, g, b;
|
||||
for (auto i=0; i < width*height; i++) {
|
||||
r = mask[maskChannels*i];
|
||||
g = mask[maskChannels*i];
|
||||
b = mask[maskChannels*i];
|
||||
mask[2*i] = (r == 0) && (g == 255) && (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
|
||||
//* The first channel is positive, the second one negative.
|
||||
stbi_load(maskImage.c_str(), &maskWidth, &maskHeight, &maskChannels, 0);
|
||||
|
||||
if (maskWidth != width || maskHeight != height) {
|
||||
std::cerr << maskImage << " and " << sourceImage
|
||||
<< " differ in dimension. Please provide a valid mask."
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
if (maskChannels < 3) {
|
||||
std::cerr << maskImage << " needs to be RGB." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
unsigned char r, g, b;
|
||||
for (auto i = 0; i < width * height; i++) {
|
||||
r = mask[maskChannels * i];
|
||||
g = mask[maskChannels * i];
|
||||
b = mask[maskChannels * i];
|
||||
mask[2 * i] = (r == 0) && (g == 255) && (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
|
||||
//* 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(),
|
||||
nbSeams, vertical, mask=mask);
|
||||
auto result = seam_carving(source, width, height, nbChannels, nbSeams,
|
||||
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);
|
||||
exit(0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user