From 6be6982ce5dc8069279bf4bd63be553e87fbb278 Mon Sep 17 00:00:00 2001 From: mmcwilliams Date: Wed, 27 Sep 2023 10:40:13 -0400 Subject: [PATCH] All work in progress saved --- .gitignore | 2 + README.md | 8 ++ cpp/.gitignore | 1 + cpp/CMakeLists.txt | 17 +++ cpp/README.md | 6 + cpp/cmake-modules/FindOpenCV.cmake | 173 +++++++++++++++++++++++++++++ cpp/cmd.cpp | 52 +++++++++ cpp/cmd.h | 30 +++++ cpp/compile.sh | 6 + cpp/palette.cpp | 105 +++++++++++++++++ cpp/separate.cpp | 65 +++++++++++ py/.gitignore | 2 + py/requirements.txt | 1 + py/separator.py | 12 ++ 14 files changed, 480 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cpp/.gitignore create mode 100644 cpp/CMakeLists.txt create mode 100644 cpp/README.md create mode 100644 cpp/cmake-modules/FindOpenCV.cmake create mode 100644 cpp/cmd.cpp create mode 100644 cpp/cmd.h create mode 100644 cpp/compile.sh create mode 100644 cpp/palette.cpp create mode 100644 cpp/separate.cpp create mode 100644 py/.gitignore create mode 100644 py/requirements.txt create mode 100644 py/separator.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ed0f93 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +*.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..0fead87 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Marker Separation + +Project for palletizing images based on a limited set of marker colors. + +Includes Python application for prototyping and C++ application for +building a self-contained binary. + + diff --git a/cpp/.gitignore b/cpp/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/cpp/.gitignore @@ -0,0 +1 @@ +build diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..d214322 --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.1) +PROJECT (separate) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules) + +find_package(OpenCV HINTS /usr/local/opt/opencv /usr/local/Cellar/opencv REQUIRED) + +set( NAME_SRC + separate.cpp +) + +INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/include ) +link_directories( ${CMAKE_BINARY_DIR}/bin) +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) +add_executable( separate ${NAME_SRC} ${NAME_HEADERS} ) + +target_link_libraries( separate ${OpenCV_LIBS} ) \ No newline at end of file diff --git a/cpp/README.md b/cpp/README.md new file mode 100644 index 0000000..ea056f4 --- /dev/null +++ b/cpp/README.md @@ -0,0 +1,6 @@ +# Marker Separation + +A C++ application with OpenCV for palletizing images to a limit set of +marker colors. + +Compiles on macOS and Linux. diff --git a/cpp/cmake-modules/FindOpenCV.cmake b/cpp/cmake-modules/FindOpenCV.cmake new file mode 100644 index 0000000..875528e --- /dev/null +++ b/cpp/cmake-modules/FindOpenCV.cmake @@ -0,0 +1,173 @@ +# =================================================================================== +# The OpenCV CMake configuration file +# +# ** File generated automatically, do not modify ** +# +# Usage from an external project: +# In your CMakeLists.txt, add these lines: +# +# FIND_PACKAGE(OpenCV REQUIRED) +# TARGET_LINK_LIBRARIES(MY_TARGET_NAME ${OpenCV_LIBS}) +# +# Or you can search for specific OpenCV modules: +# +# FIND_PACKAGE(OpenCV REQUIRED core imgcodecs) +# +# If the module is found then OPENCV__FOUND is set to TRUE. +# +# This file will define the following variables: +# - OpenCV_LIBS : The list of libraries to link against. +# - OpenCV_LIB_DIR : The directory(es) where lib files are. Calling LINK_DIRECTORIES +# with this path is NOT needed. +# - OpenCV_INCLUDE_DIRS : The OpenCV include directories. +# - OpenCV_COMPUTE_CAPABILITIES : The version of compute capability +# - OpenCV_ANDROID_NATIVE_API_LEVEL : Minimum required level of Android API +# - OpenCV_VERSION : The version of this OpenCV build. Example: "2.4.0" +# - OpenCV_VERSION_MAJOR : Major version part of OpenCV_VERSION. Example: "2" +# - OpenCV_VERSION_MINOR : Minor version part of OpenCV_VERSION. Example: "4" +# - OpenCV_VERSION_PATCH : Patch version part of OpenCV_VERSION. Example: "0" +# +# Advanced variables: +# - OpenCV_SHARED +# - OpenCV_CONFIG_PATH +# - OpenCV_LIB_COMPONENTS +# +# =================================================================================== +# +# Windows pack specific options: +# - OpenCV_STATIC +# - OpenCV_CUDA + +if(CMAKE_VERSION VERSION_GREATER 2.6) + get_property(OpenCV_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + if(NOT ";${OpenCV_LANGUAGES};" MATCHES ";CXX;") + enable_language(CXX) + endif() +endif() + +if(NOT DEFINED OpenCV_STATIC) + # look for global setting + if(BUILD_SHARED_LIBS) + set(OpenCV_STATIC OFF) + else() + set(OpenCV_STATIC ON) + endif() +endif() + +if(NOT DEFINED OpenCV_CUDA) + # if user' app uses CUDA, then it probably wants CUDA-enabled OpenCV binaries + if(CUDA_FOUND) + set(OpenCV_CUDA ON) + endif() +endif() + +if(MSVC) + if(CMAKE_CL_64) + set(OpenCV_ARCH x64) + set(OpenCV_TBB_ARCH intel64) + elseif((CMAKE_GENERATOR MATCHES "ARM") OR ("${arch_hint}" STREQUAL "ARM") OR (CMAKE_VS_EFFECTIVE_PLATFORMS MATCHES "ARM|arm")) + # see Modules/CmakeGenericSystem.cmake + set(OpenCV_ARCH ARM) + else() + set(OpenCV_ARCH x86) + set(OpenCV_TBB_ARCH ia32) + endif() + if(MSVC_VERSION EQUAL 1400) + set(OpenCV_RUNTIME vc8) + elseif(MSVC_VERSION EQUAL 1500) + set(OpenCV_RUNTIME vc9) + elseif(MSVC_VERSION EQUAL 1600) + set(OpenCV_RUNTIME vc10) + elseif(MSVC_VERSION EQUAL 1700) + set(OpenCV_RUNTIME vc11) + elseif(MSVC_VERSION EQUAL 1800) + set(OpenCV_RUNTIME vc12) + elseif(MSVC_VERSION EQUAL 1900) + set(OpenCV_RUNTIME vc14) + endif() +elseif(MINGW) + set(OpenCV_RUNTIME mingw) + + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpmachine + OUTPUT_VARIABLE OPENCV_GCC_TARGET_MACHINE + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(OPENCV_GCC_TARGET_MACHINE MATCHES "amd64|x86_64|AMD64") + set(MINGW64 1) + set(OpenCV_ARCH x64) + else() + set(OpenCV_ARCH x86) + endif() +endif() + +if(CMAKE_VERSION VERSION_GREATER 2.6.2) + unset(OpenCV_CONFIG_PATH CACHE) +endif() + +if(NOT OpenCV_FIND_QUIETLY) + message(STATUS "OpenCV ARCH: ${OpenCV_ARCH}") + message(STATUS "OpenCV RUNTIME: ${OpenCV_RUNTIME}") + message(STATUS "OpenCV STATIC: ${OpenCV_STATIC}") +endif() + +get_filename_component(OpenCV_CONFIG_PATH "${CMAKE_CURRENT_LIST_FILE}" PATH CACHE) +if(OpenCV_RUNTIME AND OpenCV_ARCH) + if(OpenCV_STATIC AND EXISTS "${OpenCV_CONFIG_PATH}/${OpenCV_ARCH}/${OpenCV_RUNTIME}/staticlib/OpenCVConfig.cmake") + if(OpenCV_CUDA AND EXISTS "${OpenCV_CONFIG_PATH}/gpu/${OpenCV_ARCH}/${OpenCV_RUNTIME}/staticlib/OpenCVConfig.cmake") + set(OpenCV_LIB_PATH "${OpenCV_CONFIG_PATH}/gpu/${OpenCV_ARCH}/${OpenCV_RUNTIME}/staticlib") + else() + set(OpenCV_LIB_PATH "${OpenCV_CONFIG_PATH}/${OpenCV_ARCH}/${OpenCV_RUNTIME}/staticlib") + endif() + elseif(EXISTS "${OpenCV_CONFIG_PATH}/${OpenCV_ARCH}/${OpenCV_RUNTIME}/lib/OpenCVConfig.cmake") + if(OpenCV_CUDA AND EXISTS "${OpenCV_CONFIG_PATH}/gpu/${OpenCV_ARCH}/${OpenCV_RUNTIME}/lib/OpenCVConfig.cmake") + set(OpenCV_LIB_PATH "${OpenCV_CONFIG_PATH}/gpu/${OpenCV_ARCH}/${OpenCV_RUNTIME}/lib") + else() + set(OpenCV_LIB_PATH "${OpenCV_CONFIG_PATH}/${OpenCV_ARCH}/${OpenCV_RUNTIME}/lib") + endif() + endif() +endif() + +if(OpenCV_LIB_PATH AND EXISTS "${OpenCV_LIB_PATH}/OpenCVConfig.cmake") + set(OpenCV_LIB_DIR_OPT "${OpenCV_LIB_PATH}" CACHE PATH "Path where release OpenCV libraries are located" FORCE) + set(OpenCV_LIB_DIR_DBG "${OpenCV_LIB_PATH}" CACHE PATH "Path where debug OpenCV libraries are located" FORCE) + set(OpenCV_3RDPARTY_LIB_DIR_OPT "${OpenCV_LIB_PATH}" CACHE PATH "Path where release 3rdparty OpenCV dependencies are located" FORCE) + set(OpenCV_3RDPARTY_LIB_DIR_DBG "${OpenCV_LIB_PATH}" CACHE PATH "Path where debug 3rdparty OpenCV dependencies are located" FORCE) + + include("${OpenCV_LIB_PATH}/OpenCVConfig.cmake") + + if(OpenCV_CUDA) + set(_OpenCV_LIBS "") + foreach(_lib ${OpenCV_LIBS}) + string(REPLACE "${OpenCV_CONFIG_PATH}/gpu/${OpenCV_ARCH}/${OpenCV_RUNTIME}" "${OpenCV_CONFIG_PATH}/${OpenCV_ARCH}/${OpenCV_RUNTIME}" _lib2 "${_lib}") + if(NOT EXISTS "${_lib}" AND EXISTS "${_lib2}") + list(APPEND _OpenCV_LIBS "${_lib2}") + else() + list(APPEND _OpenCV_LIBS "${_lib}") + endif() + endforeach() + set(OpenCV_LIBS ${_OpenCV_LIBS}) + endif() + set(OpenCV_FOUND TRUE CACHE BOOL "" FORCE) + set(OPENCV_FOUND TRUE CACHE BOOL "" FORCE) + + if(NOT OpenCV_FIND_QUIETLY) + message(STATUS "Found OpenCV ${OpenCV_VERSION} in ${OpenCV_LIB_PATH}") + if(NOT OpenCV_LIB_PATH MATCHES "/staticlib") + get_filename_component(_OpenCV_LIB_PATH "${OpenCV_LIB_PATH}/../bin" ABSOLUTE) + file(TO_NATIVE_PATH "${_OpenCV_LIB_PATH}" _OpenCV_LIB_PATH) + message(STATUS "You might need to add ${_OpenCV_LIB_PATH} to your PATH to be able to run your applications.") + if(OpenCV_LIB_PATH MATCHES "/gpu/") + string(REPLACE "\\gpu" "" _OpenCV_LIB_PATH2 "${_OpenCV_LIB_PATH}") + message(STATUS "GPU support is enabled so you might also need ${_OpenCV_LIB_PATH2} in your PATH (it must go after the ${_OpenCV_LIB_PATH}).") + endif() + endif() + endif() +else() + if(NOT OpenCV_FIND_QUIETLY) + message(WARNING +"Found OpenCV Windows Pack but it has no binaries compatible with your configuration. +You should manually point CMake variable OpenCV_DIR to your build of OpenCV library." + ) + endif() + set(OpenCV_FOUND FALSE CACHE BOOL "" FORCE) + set(OPENCV_FOUND FALSE CACHE BOOL "" FORCE) +endif() \ No newline at end of file diff --git a/cpp/cmd.cpp b/cpp/cmd.cpp new file mode 100644 index 0000000..d51e426 --- /dev/null +++ b/cpp/cmd.cpp @@ -0,0 +1,52 @@ +#include "cmd.h" + +inline bool Cmd::in_array(const std::string &value, const std::vector &array) +{ + return std::find(array.begin(), array.end(), value) != array.end(); +} + +inline bool Cmd::file_exists (const std::string& name) +{ + struct stat buffer; + return (stat (name.c_str(), &buffer) == 0); +} + +inline bool Cmd::is_image (const std::string& name) +{ + string ext = name.substr(name.find_last_of(".") + 1); + return in_array(ext, supportedExt); +} + +string Cmd::get_input (int &argc, string argstr) +{ + string input; + + if (argc > 1) + { + input = argstr; + } + else if (argc < 2) + { + cerr << "Please provide one image." << endl; + return; + } + else if (argc > 2) + { + cerr << "Please provide only one image." << endl; + return; + } + + if (!file_exists(input)) + { + cerr << "File " << input << " does not exist." << endl; + return; + } + + if (!is_image(input)) + { + cerr << "File " << input << " is not a valid image." << endl; + return; + } + + return input; +} \ No newline at end of file diff --git a/cpp/cmd.h b/cpp/cmd.h new file mode 100644 index 0000000..5503854 --- /dev/null +++ b/cpp/cmd.h @@ -0,0 +1,30 @@ +#ifndef CMD_MANAGER +#define CMD_MANAGER + +using namespace std; + +#include +#include +#include +#include +#include +#include + +class Cmd { + public: + string get_input(int &argc, string argstr); + + private: + std::vector supportedExt = { + "jpg", "JPG", + "jpeg", "JPEG", + "png", "PNG", + "tif", "TIF", + "tiff", "tiff" + }; + bool in_array(const std::string &value, const std::vector &array); + bool file_exists (const std::string& name); + bool is_image (const std::string& name); +}; + +#endif \ No newline at end of file diff --git a/cpp/compile.sh b/cpp/compile.sh new file mode 100644 index 0000000..1b05d44 --- /dev/null +++ b/cpp/compile.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +mkdir -p build +cd build +cmake .. +make -j diff --git a/cpp/palette.cpp b/cpp/palette.cpp new file mode 100644 index 0000000..394c859 --- /dev/null +++ b/cpp/palette.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#include + +using namespace cv; +using namespace std; + +// https://stackoverflow.com/a/34734939/5008845 +void reducecolor_quantization(const mat3b& src, mat3b& dst) +{ + uchar n = 64; + dst = src / n; + dst *= n; +} + +// https://stackoverflow.com/a/34734939/5008845 +void reducecolor_kmeans(const mat3b& src, mat3b& dst) +{ + int k = 8; + int n = src.rows * src.cols; + mat data = src.reshape(1, n); + data.convertto(data, cv_32f); + + vector labels; + mat1f colors; + kmeans(data, k, labels, cv::termcriteria(), 1, cv::kmeans_pp_centers, colors); + + for (int i = 0; i < n; ++i) + { + data.at(i, 0) = colors(labels[i], 0); + data.at(i, 1) = colors(labels[i], 1); + data.at(i, 2) = colors(labels[i], 2); + } + + mat reduced = data.reshape(3, src.rows); + reduced.convertto(dst, cv_8u); +} + +void reducecolor_stylization(const mat3b& src, mat3b& dst) +{ + stylization(src, dst); +} + +void reducecolor_edgepreserving(const mat3b& src, mat3b& dst) +{ + edgepreservingfilter(src, dst); +} + + +struct lessvec3b +{ + bool operator()(const vec3b& lhs, const vec3b& rhs) const { + return (lhs[0] != rhs[0]) ? (lhs[0] < rhs[0]) : ((lhs[1] != rhs[1]) ? (lhs[1] < rhs[1]) : (lhs[2] < rhs[2])); + } +}; + +map getpalette(const mat3b& src) +{ + map palette; + for (int r = 0; r < src.rows; ++r) + { + for (int c = 0; c < src.cols; ++c) + { + vec3b color = src(r, c); + if (palette.count(color) == 0) + { + palette[color] = 1; + } + else + { + palette[color] = palette[color] + 1; + } + } + } + return palette; +} + + +int main() +{ + mat3b img = imread("path_to_image"); + + // reduce color + mat3b reduced; + + //reducecolor_quantization(img, reduced); + reducecolor_kmeans(img, reduced); + //reducecolor_stylization(img, reduced); + //reducecolor_edgepreserving(img, reduced); + + + // get palette + map palette = getpalette(reduced); + + // print palette + int area = img.rows * img.cols; + + for (auto color : palette) + { + cout << "color: " << color.first << " \t - area: " << 100.f * float(color.second) / float(area) << "%" << endl; + } + + return 0; +} \ No newline at end of file diff --git a/cpp/separate.cpp b/cpp/separate.cpp new file mode 100644 index 0000000..ad4e3e1 --- /dev/null +++ b/cpp/separate.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include + +#include "cmd.h" + +using namespace std; +using namespace cv; + +std::vector supportedExt = { + "jpg", "JPG", + "jpeg", "JPEG", + "png", "PNG", + "tif", "TIF", + "tiff", "tiff" +}; + +inline bool in_array(const std::string &value, const std::vector &array) +{ + return std::find(array.begin(), array.end(), value) != array.end(); +} + +inline bool file_exists (const std::string& name) +{ + struct stat buffer; + return (stat (name.c_str(), &buffer) == 0); +} + +inline bool is_image (const std::string& name) +{ + string ext = name.substr(name.find_last_of(".") + 1); + return in_array(ext, supportedExt); +} + +Cmd cmd; + +int main(int argc, char** argv) +{ + Mat image; + Mat inverted; + string inputcdt = argv[1]; + string input = cmd.get_input(argc, inputcdt); + + if (input.empty()) { + return -1; + } + + + cout << "Using image " << input << "." << endl; + + image = imread(input); + bitwise_not(image, inverted); + imshow("Image", inverted); + + waitKey(0); + + destroyAllWindows(); + + + + return 0; +} \ No newline at end of file diff --git a/py/.gitignore b/py/.gitignore new file mode 100644 index 0000000..0ec2d42 --- /dev/null +++ b/py/.gitignore @@ -0,0 +1,2 @@ +env + diff --git a/py/requirements.txt b/py/requirements.txt new file mode 100644 index 0000000..1db7aea --- /dev/null +++ b/py/requirements.txt @@ -0,0 +1 @@ +opencv-python \ No newline at end of file diff --git a/py/separator.py b/py/separator.py new file mode 100644 index 0000000..bf381e7 --- /dev/null +++ b/py/separator.py @@ -0,0 +1,12 @@ +import argparse + +from os.path import isfile + +parser = argparse.ArgumentParser(description='Separate an image into most similar colors specified') + +parser.add_argument('input', type=str, help='Input image to separate') + +args = parser.parse_args() + + +