diff --git a/CMakeLists.txt b/CMakeLists.txt index 63caf82..d1e30e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,3 +17,12 @@ add_subdirectory(libseam_carving) enable_testing() include(GoogleTest) add_subdirectory(tests) + +# demo +add_executable(demo demo.cpp) +target_include_directories(demo + PRIVATE + ${LIBRARY_TEST_DIR}/include) +target_link_libraries(demo + PRIVATE + seam_carving ${OPENCV_LIBS}) diff --git a/README.md b/README.md index c2b0fb5..4b506b3 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,30 @@ content-aware image resizing](https://doi.org/10.1145/1275808.1276390). Learn mo ## Getting Started -It is recommended to make a virtual environment as the setup command will install pip packages. -This project uses [vcpkg](https://github.com/microsoft/vcpkg) as its C++ package manager and -[CMake](https://cmake.org) as the build-system. +The seam carving library can be built using CMake. The available targets are: -Run `make setup` from the root directory to set up the project. This will create and populate a `build/` directory and create a new `out/` directory where output files will -go by default. +- `seam_carving` - builds the dylib +- `demo` - runs the demo at `./demo.cpp` +- `carver_unit/energy_unit` - runs the unit tests at `tests/unit/[carver/energy].test.cpp` respectively -The following `make` commands are currently available: +### Running the demo -- `make demo`: builds a C++17 executable to demo seam carving. Sample images are provided in the -`samples/` directory -- `make py`: exports the existing C++ seam carving code to a Python 3.10 package called `seam_carving` -- `make clean`: cleans the project and removes any build files +1. Configure the CMake project. The code sample below uses Ninja as the generator. There are presets in `CMakePresets.json` +available to use. +2. Build the `demo` target. +3. Run the demo target with a path to an image (sample images are provided in `samples/`), and the target size + +``` +# step 1 - from the root folder of the repo +cmake -B {PATH_TO_BUILD_DIR} -S {PATH_TO_REPO_ROOT} -G Ninja --preset default + +# step 2 +cmake --build {PATH_TO_BUILD_DIR} --target demo + +# step 3 +./demo {PATH_TO_IMG} {TARGET COLS} {TARGET ROWS} +``` ## Seam Carving Shell (SCS) -The Seam Carving Shell can be run from the `cli.py` file at the root: `python cli.py`. The cli -requires at least Python 3.10. +The Seam Carving Shell (SCS) is undergoing major reconstruction and is not supported in v2.0.0. diff --git a/demo.cpp b/demo.cpp index a71a4a1..0f11173 100644 --- a/demo.cpp +++ b/demo.cpp @@ -1,10 +1,71 @@ -// -// Created by Andy Mina on 3/7/23. -// +/** + * Andy Mina + * + * File to test SeamCarver in C++ implementation. Given a path to an image, the target number of + * rows, and the target number of cols, carves the image and displays the result. + * + * Args: + * {path to img} + * {target rows} + * {target cols} +*/ +#include "timer.hpp" +#include + +#include +#include +#include #include +#include +#include + +namespace sc = seam_carving; + +template +class Timer { + using time_pt = typename Clock::time_point; + + time_pt _start = Clock::now(); + time_pt _end = {}; + +public: + void tick() { _start = Clock::now(); } + void tock() { _end = Clock::now(); } + + template + auto duration() const { + return std::chrono::duration_cast(_end - _start).count(); + } +}; + +int main(int argc, char **argv) { + if (argc != 4) { + std::cout << "Usage: " << argv[0] << " {path to img} {target cols} {target rows}\n"; + return -1; + } + + const std::string input_path(argv[1]); + const int target_cols(std::stoi(argv[2])); + const int target_rows(std::stoi(argv[3])); + + cv::Mat img = cv::imread(input_path); + std::cout << "Original size: " << img.cols << "x" << img.rows << "\n"; + + cv::Mat res = img; + Timer carver_timer; + + std::cout << "Starting carving...\n"; + + carver_timer.tick(); + sc::Carver::Carve(target_rows, target_cols, img, res); + carver_timer.tock(); + + std::cout << "Carved size: " << res.cols << "x" << res.rows << "\n"; + std::cout << "Carving took " << carver_timer.duration() << " ms\n"; -int main() { - std::cout << "hello world\n"; + cv::imshow("Original", img); + cv::imshow("Carved", res); + int k = cv::waitKey(0); return 0; } diff --git a/libseam_carving/carver.cpp b/libseam_carving/carver.cpp index e1f4f94..10eb164 100644 --- a/libseam_carving/carver.cpp +++ b/libseam_carving/carver.cpp @@ -249,4 +249,75 @@ namespace seam_carving { res.copyTo(output); } + + void Carver::Carve(const int& rows, const int& cols, cv::InputArray input, cv::OutputArray output) { + // (0, 0) + if (rows == 0 && cols == 0) { + input.copyTo(output); + return; + } + + // prepare result + cv::Mat res; + input.copyTo(res); + + // Difference between target size and current size. + // Positive to insert seams, negative to remove. + int row_diff = rows - res.rows; + int col_diff = cols - res.cols; + + /** HANDLE ALL CASES WHERE ONE DIMENSION IS POSITIVE */ + // (+, +) + while (row_diff > 0 && col_diff > 0) { + const Seam verticalSeam = FindVerticalSeam(res); + InsertVerticalSeam(verticalSeam, res, res); + col_diff--; // update the remaining difference + + const Seam horizontalSeam = FindHorizontalSeam(res); + InsertHorizontalSeam(horizontalSeam, res, res); + row_diff--; // update the remaining difference + } + + // (0/-, +) + while (col_diff > 0) { + const Seam verticalSeam = FindVerticalSeam(res); + InsertVerticalSeam(verticalSeam, res, res); + col_diff--; + } + + // (+, 0/-) + while (row_diff > 0) { + const Seam horizontalSeam = FindHorizontalSeam(res); + InsertHorizontalSeam(horizontalSeam, res, res); + row_diff--; + } + + /** HANDLE ALL CASES WHERE ONE DIMENSION IS NEGATIVE */ + // (-, -) + while (row_diff < 0 && col_diff < 0) { + const Seam verticalSeam = FindVerticalSeam(res); + RemoveVerticalSeam(verticalSeam, res, res); + col_diff++; // update the remaining difference + + const Seam horizontalSeam = FindHorizontalSeam(res); + RemoveHorizontalSeam(horizontalSeam, res, res); + row_diff++; // update the remaining difference + } + + // (0/+, -) + while (col_diff < 0) { + const Seam verticalSeam = FindVerticalSeam(res); + RemoveVerticalSeam(verticalSeam, res, res); + col_diff++; + } + + // (-, 0/+) + while (row_diff < 0) { + const Seam horizontalSeam = FindHorizontalSeam(res); + RemoveHorizontalSeam(horizontalSeam, res, res); + row_diff++; + } + + res.copyTo(output); + } } diff --git a/libseam_carving/seam_carving/carver.hpp b/libseam_carving/seam_carving/carver.hpp index 0b22985..1334699 100644 --- a/libseam_carving/seam_carving/carver.hpp +++ b/libseam_carving/seam_carving/carver.hpp @@ -21,7 +21,7 @@ namespace seam_carving { * Base class for all carvers */ class Carver { - protected: + public: /** * @param img the image to find the optimal seam in * @returns the optimal seam to be removed @@ -42,6 +42,14 @@ namespace seam_carving { */ static void InsertVerticalSeam(const Seam &seam, cv::InputArray input, cv::OutputArray output); static void InsertHorizontalSeam(const Seam &seam, cv::InputArray input, cv::OutputArray output); + + /** + * @param rows the target number of rows for the new image + * @param cols the target number of cols for the new image + * @param input the image to be carved + * @param output a carved version of input + */ + static void Carve(const int& rows, const int& cols, cv::InputArray input, cv::OutputArray output); }; } diff --git a/tests/include/dummy_carver.hpp b/tests/include/dummy_carver.hpp deleted file mode 100644 index f009b02..0000000 --- a/tests/include/dummy_carver.hpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by Andy Mina on 3/10/23. -// - -#ifndef SEAM_CARVING_DUMMY_CARVER_HPP -#define SEAM_CARVING_DUMMY_CARVER_HPP - -// project -#include - -namespace seam_carving::tests { - class DummyCarver : public Carver { - public: - inline static Seam FindVerticalSeam(cv::InputArray img) { - return Carver::FindVerticalSeam(img); - } - - inline static Seam FindHorizontalSeam(cv::InputArray img) { - return Carver::FindHorizontalSeam(img); - } - - inline static void RemoveVerticalSeam(const Seam& seam, cv::InputArray input, cv::OutputArray output) { - Carver::RemoveVerticalSeam(seam, input, output); - } - - inline static void RemoveHorizontalSeam(const Seam& seam, cv::InputArray input, cv::OutputArray output) { - Carver::RemoveHorizontalSeam(seam, input, output); - } - - inline static void InsertVerticalSeam(const Seam& seam, cv::InputArray input, cv::OutputArray output) { - Carver::InsertVerticalSeam(seam, input, output); - } - - inline static void InsertHorizontalSeam(const Seam &seam, cv::InputArray input, cv::OutputArray output) { - Carver::InsertHorizontalSeam(seam, input, output); - } - }; -} - -#endif //SEAM_CARVING_DUMMY_CARVER_HPP diff --git a/tests/unit/carver.test.cpp b/tests/unit/carver.test.cpp index e3449d0..c1a692d 100644 --- a/tests/unit/carver.test.cpp +++ b/tests/unit/carver.test.cpp @@ -2,16 +2,15 @@ // Created by Andy Mina on 3/7/23. // -// project -#include -#include - // test #include "print_utils.hpp" #include "matrix_utils.hpp" #include "json_utils.hpp" #include "carver_data.hpp" -#include "dummy_carver.hpp" + +// project +#include +#include // 3rd party #include @@ -37,7 +36,7 @@ TEST_P(CarverTest, FindVerticalSeam) { cv::Mat input = carver_data.original_matrix; sc::Seam expected = carver_data.vertical_seam; - sc::Seam actual = sct::DummyCarver::FindVerticalSeam(input); + sc::Seam actual = sc::Carver::FindVerticalSeam(input); EXPECT_EQ(expected, actual) << sct::PrintWithLabel(expected, "expected") << "\n" @@ -50,7 +49,7 @@ TEST_P(CarverTest, FindHorizontalSeam) { cv::Mat input = carver_data.original_matrix; sc::Seam expected = carver_data.horizontal_seam; - sc::Seam actual = sct::DummyCarver::FindHorizontalSeam(input); + sc::Seam actual = sc::Carver::FindHorizontalSeam(input); EXPECT_EQ(expected, actual) << sct::PrintWithLabel(expected, "expected") << "\n" @@ -65,7 +64,7 @@ TEST_P(CarverTest, RemoveVerticalSeam) { cv::Mat actual; sc::Seam seam = carver_data.vertical_seam; - sct::DummyCarver::RemoveVerticalSeam(seam, input, actual); + sc::Carver::RemoveVerticalSeam(seam, input, actual); EXPECT_TRUE(sct::equalMatrices(expected, actual)) << sct::PrintWithLabel(expected, "expected") << "\n" @@ -80,7 +79,7 @@ TEST_P(CarverTest, RemoveHorizontalSeam) { cv::Mat actual; sc::Seam seam = carver_data.horizontal_seam; - sct::DummyCarver::RemoveHorizontalSeam(seam, input, actual); + sc::Carver::RemoveHorizontalSeam(seam, input, actual); EXPECT_TRUE(sct::equalMatrices(expected, actual)) << sct::PrintWithLabel(expected, "expected") << "\n" @@ -95,7 +94,7 @@ TEST_P(CarverTest, InsertVerticalSeam) { cv::Mat actual; sc::Seam seam = carver_data.vertical_seam; - sct::DummyCarver::InsertVerticalSeam(seam, input, actual); + sc::Carver::InsertVerticalSeam(seam, input, actual); EXPECT_TRUE(sct::equalMatrices(expected, actual)) << sct::PrintWithLabel(expected, "expected") << "\n" @@ -110,7 +109,7 @@ TEST_P(CarverTest, InsertHorizontalSeam) { cv::Mat actual; sc::Seam seam = carver_data.horizontal_seam; - sct::DummyCarver::InsertHorizontalSeam(seam, input, actual); + sc::Carver::InsertHorizontalSeam(seam, input, actual); EXPECT_TRUE(sct::equalMatrices(expected, actual)) << sct::PrintWithLabel(expected, "expected") << "\n" diff --git a/tests/unit/coord.test.cpp b/tests/unit/coord.test.cpp deleted file mode 100644 index c3a6148..0000000 --- a/tests/unit/coord.test.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// -// Created by Andy Mina on 3/7/23. -// diff --git a/tests/unit/seam.test.cpp b/tests/unit/seam.test.cpp deleted file mode 100644 index c3a6148..0000000 --- a/tests/unit/seam.test.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// -// Created by Andy Mina on 3/7/23. -//