From 7446cabaadfe9525f5abe334137dc52e976c28bd Mon Sep 17 00:00:00 2001 From: augustin64 Date: Wed, 26 Mar 2025 10:47:35 +0100 Subject: [PATCH] Remove many seams at once (but don't use overlapping seams) --- src/seam-carving.cpp | 131 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 7 deletions(-) diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index 183788c..3ea8f2b 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -15,6 +15,9 @@ bool silent; bool test_energy; +int min(int a, int b) { + return a < b ? a : b; +} /** e_1 energy */ std::vector energy_e1(unsigned char* source, int width, int height, int nbChannels) { @@ -22,8 +25,8 @@ std::vector energy_e1(unsigned char* source, int width, int heigh for (auto i=0; i < width*height; i++) { energy[i] = 0; } - for(auto i = 0 ; i < width ; ++i) { - for(auto j = 0; j < height; ++j) { + for(auto i=0 ; i < width ; ++i) { + for(auto j=0; j < height; ++j) { 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; @@ -44,6 +47,69 @@ std::vector energy_e1(unsigned char* source, int width, int heigh return energy; } +std::vector optimal_vertical_seams(std::vector energy, int width, int height, int nbSeams) { + std::vector dyn_energy(width*height); + + //* Find an end of the minimal connected vertical/horizontal seam + for (auto i=1; i < height; i++) { + dyn_energy[width*i] = energy[width*i]; + } + + for (auto j=1; j < width; j++) { + for (auto i=0; i < height; i++) { + int bot_center = (i > 0) ? dyn_energy[width*(i-1)+j] : INT_MAX; + int bot_left = (i > 0 && j > 0) ? dyn_energy[width*(i-1)+(j-1)] : INT_MAX; + int bot_right = (i > 0 && j+1 < width) ? dyn_energy[width*(i-1)+(j+1)] : INT_MAX; + + + dyn_energy[width*i+j] = min( + bot_center, + min( + bot_left, + bot_right + ) + ) + energy[width*i+j]; + } + } + + std::vector result(height*nbSeams); + + // To find the nbSeams largest points, we sort the pairs (dyn_energy[idx, height-1], idx) + std::vector> seamEnds(width); + for (auto j=0; j < width; j++) { + seamEnds[j] = {dyn_energy[(height-1)*width+j], j}; + } + std::sort(seamEnds.begin(), seamEnds.end()); + for (auto seam=0; seam < nbSeams; seam++) { + result[height*(seam+1)-1] = seamEnds[seam].second; + } + + //* Backtracking to find the path + for (auto seam=0; seam < nbSeams; seam++) { + int min_idx = result[height*(seam+1)-1]; + int min_val = dyn_energy[(height-1)*width+min_idx]; + + for (auto i=height-1; i > 0; i--) { + // We want to find either (bot_l, bot_c, bot_r) with dyn_energy[.] = min_val - energy[cur] + int objective_energy = min_val - energy[i*width+min_idx]; + + if (dyn_energy[(i-1)+height*min_idx] == objective_energy) { + // min_idx does not change + min_val = dyn_energy[(i-1)+height*min_idx]; + } else if (min_idx > 0 && dyn_energy[(i-1)+height*(min_idx-1)] == objective_energy) { + min_val = dyn_energy[(i-1)+height*(min_idx - 1)]; + min_idx = min_idx - 1; + } else if (min_idx+1 < width && dyn_energy[(i-1)+height*(min_idx+1)] == objective_energy) { + min_val = dyn_energy[(i-1)+height*(min_idx + 1)]; + min_idx = min_idx + 1; + } + result[height*seam+i-1] = min_idx; + } + } + + return result; +} + int main(int argc, char **argv) { CLI::App app{"seam-carving"}; @@ -51,6 +117,8 @@ int main(int argc, char **argv) { app.add_option("-s,--source", sourceImage, "Source image")->required()->check(CLI::ExistingFile);; std::string outputImage= "output.png"; app.add_option("-o,--output", outputImage, "Output image")->required(); + int nbSeams = 1; + app.add_option("-n,--nb-seams", nbSeams, "Number of seams"); silent = false; app.add_flag("--silent", silent, "No verbose messages"); test_energy = false; @@ -66,20 +134,69 @@ int main(int argc, char **argv) { exit(1); } + nbSeams = min(nbSeams, width); int outChannels = nbChannels; + int outHeight = height; + int outWidth = width; std::vector output; if (test_energy) { - outChannels = 1; - output = energy_e1(source, width, height, nbChannels); + //outChannels = 1; + std::vector energy = energy_e1(source, width, height, nbChannels); + std::vector opt_seam = optimal_vertical_seams(energy, width, height, nbSeams); + + std::vector output2(width*height*3); + for (auto i=0; i < width*height; i++) { + output2[3*i] = energy[i]; + output2[3*i+1] = energy[i]; + output2[3*i+2] = energy[i]; + } + for (auto seam=0; seam < nbSeams; seam++) { + for (auto i=0; i < height; i++) { + output2[3*(i*width+opt_seam[seam*height+i])] = 255; + output2[3*(i*width+opt_seam[seam*height+i])+1] = 0; + output2[3*(i*width+opt_seam[seam*height+i])+2] = 0; + } + } + output=output2; } else { - std::cout <<"Not implemented" << std::endl; - exit(1); + std::vector energy = energy_e1(source, width, height, nbChannels); + std::vector opt_seam = optimal_vertical_seams(energy, width, height, nbSeams); + + std::vector blacklist(width*height); + + for (auto k=0; k < width*height; k++) { blacklist[k] = false; } + for (auto seam=0; seam < nbSeams; seam++) { + bool overlap = false; // Check if 2 seams use similar pixels + for (auto i=0; i < height; i++) { + overlap = overlap || blacklist[i*width+opt_seam[seam*height+i]]; + } + if (overlap) { nbSeams--; continue;} + for (auto i=0; i < height; i++) { + blacklist[i*width+opt_seam[seam*height+i]] = true; + } + } + + outWidth = width-nbSeams; //! something is weird here + + std::vector output_l(outWidth*height*3); + for (auto i=0; i < height; i++) { + int cur_j = 0; + for (auto j=0; cur_j < outWidth && j < width; j++) { + if (!blacklist[i*width+j]) { + output_l[3*(i*outWidth+cur_j)] = source[3*(i*width+j)]; + output_l[3*(i*outWidth+cur_j)+1] = source[3*(i*width+j)+1]; + output_l[3*(i*outWidth+cur_j)+2] = source[3*(i*width+j)+2]; + cur_j++; + } + } + } + output = output_l; } //Final export if (!silent) std::cout<<"Exporting.."<