├── .gitignore ├── AUTHORS ├── CMakeLists.txt ├── README.md ├── Settings.h.in ├── TODO ├── classe ├── 0.png ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png └── 8.png ├── input └── 1840_paris_crop.png └── src ├── CMakeLists.txt ├── config.cpp ├── config.h ├── convert.cpp ├── convert.h ├── cost.cpp ├── cost.h ├── main.cpp ├── random.cpp ├── random.h ├── simulated-annealing.cpp └── simulated-annealing.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | # Cmake 16 | build 17 | build.* 18 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * meny_n 2 | * tavare_m 3 | * weng_r 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.11) 2 | 3 | project(markov) 4 | 5 | set(PROJECT_SRC_DIR ${PROJECT_SOURCE_DIR}) 6 | 7 | configure_file ( 8 | "${PROJECT_SOURCE_DIR}/Settings.h.in" 9 | "${PROJECT_BINARY_DIR}/Settings.h") 10 | 11 | include_directories("${PROJECT_BINARY_DIR}") 12 | 13 | if (CMAKE_GENERATOR STREQUAL Xcode) 14 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) 15 | else() 16 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) 17 | endif() 18 | 19 | add_subdirectory(src) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | Image segmentation with a Markov random field 4 | 5 | ### Method 6 | 7 | At the first time, the program need to learn the mean and the covariance of 8 | each class. So, it is a supervised method: we need to put one image for each 9 | class in a folder called "classe" at project root. After that, the program 10 | generates randomly a probability map. Finding the optimal map, it does a 11 | simulated annealing. In each step of simulated annealing, the program iterates 12 | on each pixel and changes the pixel class randomly. Knowing if the new class is 13 | better than the old one, it computes the old cost and the new one and it 14 | compares the difference. This new state will be accepted if new cost is better 15 | and if this state respects the Metropolis algorithm. 16 | 17 | ### Building 18 | 19 | The project uses CMake build system: 20 | 21 | 1. mkdir build && cd build 22 | 2. cmake .. && make 23 | 24 | ### Running 25 | 26 | In project root, you can find the generated executable: 27 | 28 | ./markov input output 29 | 30 | ### Dependencies 31 | 32 | * CMake (build tool) 33 | * OpenCV 34 | * Boost: filesystem 35 | * Armadillo 36 | 37 | ### Examples 38 | 39 | #### Input 40 | 41 | ![Input - example](https://raw.githubusercontent.com/ixartz/Markov-segmentation/master/input/1840_paris_crop.png) 42 | 43 | #### Output 44 | ![Output - example](http://ixartz.github.io/Markov-segmentation/output-markov.png) 45 | -------------------------------------------------------------------------------- /Settings.h.in: -------------------------------------------------------------------------------- 1 | // 2 | // Config.h 3 | // markov 4 | // 5 | // Created by Ixi on 26/05/14. 6 | // 7 | // 8 | 9 | #ifndef face_detection_Config_h 10 | #define face_detection_Config_h 11 | 12 | #define PROJECT_SRC_DIR "@PROJECT_SOURCE_DIR@" 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | [X] Open an image with OpenCV 2 | [X] Implement metropolis 3 | [X] Implement simulated annealing 4 | [X] Implement cost function 5 | [X] Use Luv instead rgb 6 | [X] Generate the executable at project root 7 | -------------------------------------------------------------------------------- /classe/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/classe/0.png -------------------------------------------------------------------------------- /classe/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/classe/1.png -------------------------------------------------------------------------------- /classe/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/classe/2.png -------------------------------------------------------------------------------- /classe/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/classe/3.png -------------------------------------------------------------------------------- /classe/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/classe/4.png -------------------------------------------------------------------------------- /classe/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/classe/5.png -------------------------------------------------------------------------------- /classe/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/classe/6.png -------------------------------------------------------------------------------- /classe/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/classe/7.png -------------------------------------------------------------------------------- /classe/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/classe/8.png -------------------------------------------------------------------------------- /input/1840_paris_crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixartz/Markov-segmentation/7db08905022f60d32aa45cbe8ee8a5f03711fadd/input/1840_paris_crop.png -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(add_dir directory return) 2 | file(GLOB source 3 | "${directory}/*.h" 4 | "${directory}/*.cpp" 5 | ) 6 | 7 | if (${directory} STREQUAL ".") 8 | set(group "root") 9 | else() 10 | set(group ${directory}) 11 | endif() 12 | 13 | source_group(${group} FILES ${source}) 14 | 15 | set(${return} ${source} PARENT_SCOPE) 16 | endfunction(add_dir) 17 | 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -Wextra -pedantic -std=c++11") 19 | 20 | add_dir("." source) 21 | 22 | find_package(OpenCV REQUIRED) 23 | find_package(Boost COMPONENTS system filesystem REQUIRED) 24 | find_package(Armadillo REQUIRED) 25 | 26 | include_directories(SYSTEM ${OpenCV_INCLUDE_DIRS}) 27 | include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) 28 | include_directories(${ARMADILLO_INCLUDE_DIRS}) 29 | 30 | add_executable(markov ${source}) 31 | 32 | target_link_libraries(markov ${OpenCV_LIBS} ${ARMADILLO_LIBRARIES} 33 | ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY}) 34 | -------------------------------------------------------------------------------- /src/config.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // config.cpp 3 | // markov 4 | // 5 | // Created by ixi on 16/12/14. 6 | // 7 | // 8 | 9 | #include "config.h" 10 | 11 | cv::Vec3b Config::colors[NB_COLORS] = 12 | { 13 | cv::Vec3b(238, 0, 0), 14 | cv::Vec3b(0, 0, 0), 15 | cv::Vec3b(127, 127, 127), 16 | cv::Vec3b(238, 0, 238), 17 | cv::Vec3b(0, 255, 127), 18 | cv::Vec3b(0, 0, 255), 19 | cv::Vec3b(36, 127, 255), 20 | cv::Vec3b(51, 255, 255), 21 | cv::Vec3b(255, 255, 51) 22 | }; -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | // 2 | // config.h 3 | // markov 4 | // 5 | // Created by ixi on 16/12/14. 6 | // 7 | // 8 | 9 | #ifndef __markov__config__ 10 | #define __markov__config__ 11 | 12 | #include 13 | 14 | #define NB_COLORS 9 15 | 16 | class Config 17 | { 18 | public: 19 | // Eau, fond, fond relief, bat1, bat2, rouge vif, zone, texte, trait fin 20 | static cv::Vec3b colors[NB_COLORS]; 21 | static constexpr double beta = 1.5; 22 | static constexpr double initial_temperature = 10.0; 23 | static constexpr double temperature_decrease = 0.95; 24 | static constexpr double min_change = 2.0; 25 | }; 26 | 27 | #endif /* defined(__markov__config__) */ 28 | -------------------------------------------------------------------------------- /src/convert.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // convert.cpp 3 | // markov 4 | // 5 | // Created by ixi on 17/12/14. 6 | // 7 | // 8 | 9 | #include "convert.h" 10 | 11 | void convert(cv::Mat& prob, cv::Mat& output) 12 | { 13 | for (int i = 0; i < prob.rows; ++i) 14 | { 15 | for (int j = 0; j < prob.cols; ++j) 16 | { 17 | output.at(i, j) = Config::colors[prob.at(i, j)]; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/convert.h: -------------------------------------------------------------------------------- 1 | // 2 | // convert.h 3 | // markov 4 | // 5 | // Created by ixi on 17/12/14. 6 | // 7 | // 8 | 9 | #ifndef __markov__convert__ 10 | #define __markov__convert__ 11 | 12 | #include 13 | #include "config.h" 14 | 15 | void convert(cv::Mat& prob, cv::Mat& output); 16 | 17 | #endif /* defined(__markov__convert__) */ 18 | -------------------------------------------------------------------------------- /src/cost.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // cost.cpp 3 | // markov 4 | // 5 | // Created by ixi on 16/12/14. 6 | // 7 | // 8 | 9 | #include "cost.h" 10 | 11 | Cost::Cost() 12 | { 13 | for (int i = 0; i < NB_COLORS; ++i) 14 | { 15 | covariance_[i].zeros(); 16 | inv_covariance_[i].zeros(); 17 | } 18 | } 19 | 20 | void Cost::compute_mean_variance_(cv::Mat& image, int classe) 21 | { 22 | double sum; 23 | double sum2; 24 | 25 | for (int k = 0; k < 3; ++k) 26 | { 27 | sum = 0; 28 | sum2 = 0; 29 | 30 | for (int i = 0; i < image.rows; ++i) 31 | { 32 | for (int j = 0; j < image.cols; ++j) 33 | { 34 | sum += image.at(i, j)[k]; 35 | sum2 += image.at(i, j)[k] * 36 | image.at(i, j)[k]; 37 | } 38 | } 39 | 40 | mean_[classe](k) = sum / (image.cols * image.rows); 41 | covariance_[classe](k, k) = (sum2 - (sum * sum) / (image.cols * image.rows)) 42 | / (image.cols * image.rows); 43 | } 44 | } 45 | 46 | void Cost::compute_covariance_(cv::Mat& image, int classe) 47 | { 48 | double sum[3] = { 0, 0, 0}; 49 | 50 | for (int i = 0; i < image.rows; ++i) 51 | { 52 | for (int j = 0; j < image.cols; ++j) 53 | { 54 | sum[0] += (image.at(i, j)[0] - mean_[classe](0)) * 55 | (image.at(i, j)[1] - mean_[classe](1)); 56 | sum[1] += (image.at(i, j)[0] - mean_[classe](0)) * 57 | (image.at(i, j)[2] - mean_[classe](2)); 58 | sum[2] += (image.at(i, j)[1] - mean_[classe](1)) * 59 | (image.at(i, j)[2] - mean_[classe](2)); 60 | } 61 | } 62 | 63 | covariance_[classe](0, 1) = sum[0] / (image.cols * image.rows); 64 | covariance_[classe](0, 2) = sum[1] / (image.cols * image.rows); 65 | covariance_[classe](1, 2) = sum[2] / (image.cols * image.rows); 66 | 67 | covariance_[classe](1, 0) = covariance_[classe](0, 1); 68 | covariance_[classe](2, 0) = covariance_[classe](0, 2); 69 | covariance_[classe](2, 1) = covariance_[classe](1, 2); 70 | } 71 | 72 | void Cost::fix_singular_(int classe) 73 | { 74 | // Avoid singular matrix (that does not have a matrix inverse) 75 | for (arma::uword i = 0; i < covariance_[classe].n_rows; ++i) 76 | { 77 | for (arma::uword j = 0; j < covariance_[classe].n_cols; ++j) 78 | { 79 | if (covariance_[classe](i, j) == 0) 80 | covariance_[classe](i, j) = 1e-10; 81 | } 82 | } 83 | } 84 | 85 | void Cost::init() 86 | { 87 | cv::Mat image; 88 | boost::filesystem::directory_iterator end; 89 | 90 | std::string input_dir(std::string(PROJECT_SRC_DIR) + "/classe"); 91 | int classe = 0; 92 | 93 | if (boost::filesystem::exists(input_dir) 94 | && boost::filesystem::is_directory(input_dir)) 95 | { 96 | for (boost::filesystem::directory_iterator it(input_dir); 97 | it != end; ++it) 98 | { 99 | if (it->path().extension() == ".png") 100 | { 101 | std::cout << it->path().filename() << std::endl; 102 | image = cv::imread(it->path().string(), CV_LOAD_IMAGE_COLOR); 103 | cv::cvtColor(image, image, CV_RGB2Luv); 104 | 105 | compute_mean_variance_(image, classe); 106 | compute_covariance_(image, classe); 107 | 108 | fix_singular_(classe); 109 | inv_covariance_[classe] = arma::inv(covariance_[classe]); 110 | 111 | ++classe; 112 | } 113 | } 114 | } 115 | } 116 | 117 | double Cost::c2_test(cv::Mat& prob, int i, int j, int classe) 118 | { 119 | if (prob.at(i, j) == classe) 120 | return - Config::beta; 121 | else 122 | return Config::beta; 123 | } 124 | 125 | double Cost::c2_potts(cv::Mat& prob, int i, int j, int classe) 126 | { 127 | double cost = 0.; 128 | 129 | if (i > 0) 130 | cost += c2_test(prob, i - 1, j, classe); 131 | 132 | if (i < prob.rows - 1) 133 | cost += c2_test(prob, i + 1, j, classe); 134 | 135 | if (j > 0) 136 | cost += c2_test(prob, i, j - 1, classe); 137 | 138 | if (j < prob.cols - 1) 139 | cost += c2_test(prob, i, j + 1, classe); 140 | 141 | return cost; 142 | } 143 | 144 | double Cost::c1(cv::Mat& img, int i, int j, int classe) 145 | { 146 | arma::vec3 x; 147 | x(0) = img.at(i, j)[0]; 148 | x(1) = img.at(i, j)[1]; 149 | x(2) = img.at(i, j)[2]; 150 | 151 | arma::vec3 m = (x - mean_[classe]); 152 | arma::mat out = trans(m) * inv_covariance_[classe] * m; 153 | 154 | return log(sqrt(2.0 * M_PI * arma::det(covariance_[classe]))) + 155 | 0.5 * out[0]; 156 | } 157 | 158 | double Cost::compute(cv::Mat& img, int i, int j, int classe, cv::Mat& prob) 159 | { 160 | return c1(img, i, j, classe) + c2_potts(prob, i, j, classe); 161 | } -------------------------------------------------------------------------------- /src/cost.h: -------------------------------------------------------------------------------- 1 | // 2 | // cost.h 3 | // markov 4 | // 5 | // Created by ixi on 16/12/14. 6 | // 7 | // 8 | 9 | #ifndef __markov__cost__ 10 | #define __markov__cost__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "Settings.h" 17 | #include "config.h" 18 | 19 | class Cost 20 | { 21 | public: 22 | Cost(); 23 | void init(); 24 | double c2_test(cv::Mat& prob, int i, int j, int classe); 25 | double c2_potts(cv::Mat& prob, int i, int j, int classe); 26 | double c1(cv::Mat& img, int i, int j, int classe); 27 | double compute(cv::Mat& img, int i, int j, int classe, cv::Mat& prob); 28 | 29 | private: 30 | void fix_singular_(int classe); 31 | void compute_mean_variance_(cv::Mat& image, int classe); 32 | void compute_covariance_(cv::Mat& image, int classe); 33 | 34 | private: 35 | arma::vec3 mean_[NB_COLORS]; 36 | arma::mat33 covariance_[NB_COLORS]; 37 | arma::mat33 inv_covariance_[NB_COLORS]; 38 | }; 39 | 40 | #endif /* defined(__markov__cost__) */ 41 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "simulated-annealing.h" 5 | 6 | int main(int argc, char* argv[]) 7 | { 8 | if (argc != 3) 9 | { 10 | std::cerr << "Usage: " << argv[0] << " input output" << std::endl; 11 | return EXIT_FAILURE; 12 | } 13 | 14 | cv::Mat img = cv::imread(argv[1]); 15 | simulated_annealing(img); 16 | 17 | cv::imwrite(argv[2], img); 18 | 19 | return EXIT_SUCCESS; 20 | } 21 | -------------------------------------------------------------------------------- /src/random.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // random.cpp 3 | // markov 4 | // 5 | // Created by ixi on 05/12/14. 6 | // 7 | // 8 | 9 | #include "random.h" 10 | 11 | void random_image(cv::Mat& img) 12 | { 13 | std::mt19937 eng_(time(NULL)); 14 | std::uniform_int_distribution uint_dist_; 15 | 16 | for (int i = 0; i < img.rows; ++i) 17 | { 18 | for (int j = 0; j < img.cols; ++j) 19 | { 20 | img.at(i, j) = uint_dist_(eng_) % NB_COLORS; 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/random.h: -------------------------------------------------------------------------------- 1 | // 2 | // random.h 3 | // markov 4 | // 5 | // Created by ixi on 05/12/14. 6 | // 7 | // 8 | 9 | #ifndef __markov__random__ 10 | #define __markov__random__ 11 | 12 | #include 13 | #include 14 | #include "config.h" 15 | 16 | void random_image(cv::Mat& img); 17 | 18 | #endif /* defined(__markov__random__) */ 19 | -------------------------------------------------------------------------------- /src/simulated-annealing.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // simulated-annealing.cpp 3 | // markov 4 | // 5 | // Created by ixi on 15/12/14. 6 | // 7 | // 8 | 9 | #include "simulated-annealing.h" 10 | 11 | void simulated_annealing(cv::Mat& img) 12 | { 13 | std::mt19937 eng(time(NULL)); 14 | std::uniform_int_distribution uint_dist; 15 | std::uniform_real_distribution ureal_dist; 16 | 17 | double delta_global_enery; 18 | double delta; 19 | double temperature = Config::initial_temperature; 20 | int new_classe; 21 | Cost c; 22 | 23 | cv::Mat prob(img.size(), CV_8UC1, cv::Scalar(255)); 24 | cv::Mat output(img.size(), CV_8UC3, cv::Scalar(255, 255, 255)); 25 | 26 | cv::cvtColor(img, img, CV_RGB2Luv); 27 | 28 | random_image(prob); 29 | convert(prob, output); 30 | 31 | c.init(); 32 | 33 | do 34 | { 35 | //cv::imshow("Display image", output); 36 | //cv::waitKey(0); 37 | 38 | delta_global_enery = 0.; 39 | 40 | // k avoids the influence by neighborhood 41 | for (int k = 0; k < 2; ++k) 42 | { 43 | for (int i = 0; i < prob.rows; ++i) 44 | { 45 | for (int j = k; j < prob.cols; j += 2) 46 | { 47 | new_classe = uint_dist(eng) % NB_COLORS; 48 | delta = c.compute(img, i, j, new_classe, prob) - 49 | c.compute(img, i, j, prob.at(i, j), prob); 50 | 51 | if (delta <= 0. || 52 | exp(-delta / temperature) >= ureal_dist(eng)) 53 | { 54 | delta_global_enery += fabs(delta); 55 | prob.at(i, j) = new_classe; 56 | } 57 | } 58 | } 59 | } 60 | 61 | convert(prob, output); 62 | temperature *= Config::temperature_decrease; 63 | } 64 | while (delta_global_enery > Config::min_change); 65 | 66 | img = output; 67 | } -------------------------------------------------------------------------------- /src/simulated-annealing.h: -------------------------------------------------------------------------------- 1 | // 2 | // simulated-annealing.h 3 | // markov 4 | // 5 | // Created by ixi on 15/12/14. 6 | // 7 | // 8 | 9 | #ifndef __markov__simulated_annealing__ 10 | #define __markov__simulated_annealing__ 11 | 12 | #include 13 | #include 14 | #include "random.h" 15 | #include "convert.h" 16 | #include "cost.h" 17 | 18 | void simulated_annealing(cv::Mat& img); 19 | 20 | #endif /* defined(__markov__simulated_annealing__) */ 21 | --------------------------------------------------------------------------------