├── snakes.pdf ├── CMakeLists.txt ├── make_pdf.sh ├── README.md ├── snakes.tex └── snake.cpp /snakes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessandro-gentilini/opencv_exercises-snakes/HEAD/snakes.pdf -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | PROJECT( snake ) 3 | FIND_PACKAGE( OpenCV REQUIRED ) 4 | ADD_EXECUTABLE( snake snake.cpp ) 5 | TARGET_LINK_LIBRARIES( snake ${OpenCV_LIBS} ) 6 | SET(CMAKE_CXX_FLAGS "-std=c++0x") 7 | 8 | -------------------------------------------------------------------------------- /make_pdf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm snakes.aux 3 | rm snakes.bbl 4 | rm snakes.blg 5 | rm snakes.out 6 | rm snakes.log 7 | 8 | pdflatex snakes.tex 9 | bibtex snakes.aux 10 | pdflatex snakes.tex 11 | pdflatex snakes.tex 12 | 13 | rm snakes.aux 14 | rm snakes.bbl 15 | rm snakes.blg 16 | rm snakes.out 17 | rm snakes.log 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | opencv_exercises-snakes 2 | ======================= 3 | 4 | Snakes (active contours) implemented with OpenCV. 5 | 6 | Following the Bayesian approach described in: 7 | 8 | Prince, S.J.D. 9 | [*Computer Vision: Models Learning and Inference*](http://web4.cs.ucl.ac.uk/staff/s.prince/book/book.pdf), 10 | Cambridge University Press, 2012 11 | -------------------------------------------------------------------------------- /snakes.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,12pt]{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage[english]{babel} 4 | 5 | \usepackage{graphicx} 6 | \usepackage{amsmath} 7 | \usepackage{hyperref} 8 | \hypersetup{ 9 | colorlinks=false, 10 | pdfborder={0 0 0}, 11 | } 12 | 13 | \title{snakes} 14 | \author{Alessandro Gentilini\thanks{alessandro.gentilini@gmail.com}} 15 | 16 | \DeclareMathOperator*{\argmax}{arg\,max} 17 | 18 | 19 | \begin{document} 20 | \maketitle 21 | 22 | \begin{abstract} 23 | snakes 24 | \end{abstract} 25 | 26 | \section{Inference for Snakes} 27 | The likelihood is formula (17.3) 28 | \begin{equation} 29 | Pr(\mathbf{x}\vert\mathbf{W})\propto \prod_{n=1}^N \exp{[-(\text{dist}[\mathbf{x},\mathbf{w}_n])^2]} 30 | \end{equation} 31 | 32 | The prior is formula (17.4) 33 | \begin{equation} 34 | Pr(\mathbf{W})\propto \prod_{n=1}^N \exp{[\alpha \text{space}[\mathbf{w},n]+\beta \text{curve}[\mathbf{w},n]]} 35 | \end{equation} 36 | 37 | The inference is 38 | \begin{equation} 39 | \argmax_{\mathbf{W}}[Pr(\mathbf{W}\vert\mathbf{x})] = 40 | \argmax_{\mathbf{W}}[Pr(\mathbf{x}\vert\mathbf{W})Pr(\mathbf{W})] 41 | \end{equation} 42 | 43 | Since $\log$ is monotone I can write 44 | \begin{equation} 45 | \begin{split} 46 | \argmax_{\mathbf{W}}[Pr(\mathbf{W}\vert\mathbf{x})] & = 47 | \argmax_{\mathbf{W}}[\log{[Pr(\mathbf{W}\vert\mathbf{x})]}] \\ 48 | & = \argmax_{\mathbf{W}}[\log{[Pr(\mathbf{x}\vert\mathbf{W})Pr(\mathbf{W})]}] \\ 49 | & = \argmax_{\mathbf{W}}[\log{[Pr(\mathbf{x}\vert\mathbf{W})]}+\log{[Pr(\mathbf{W})]}] 50 | \end{split} 51 | \end{equation} 52 | 53 | And so for what regard the likelihood I can write: 54 | \begin{equation} 55 | \begin{split} 56 | \log{[Pr(\mathbf{x}\vert\mathbf{W})]} & \propto \log{[\prod_{n=1}^N \exp{[-(\text{dist}[\mathbf{x},\mathbf{w}_n])^2]}]} \\ 57 | & =\sum_{n=1}^N \log{[\exp{[-(\text{dist}[\mathbf{x},\mathbf{w}_n])^2]}]} \\ 58 | & =\sum_{n=1}^N -(\text{dist}[\mathbf{x},\mathbf{w}_n])^2 59 | \end{split} 60 | \end{equation} 61 | 62 | And for what regard the prior I can write: 63 | \begin{equation} 64 | \begin{split} 65 | \log{[Pr(\mathbf{W})]} & \propto \log{[\prod_{n=1}^N \exp{[\alpha \text{space}[\mathbf{w},n]+\beta \text{curve}[\mathbf{w},n]]}]} \\ 66 | & = \sum_{n=1}^N \log{[\exp{[\alpha \text{space}[\mathbf{w},n]+\beta \text{curve}[\mathbf{w},n]]}]} \\ 67 | & = \sum_{n=1}^N \alpha \text{space}[\mathbf{w},n]+\beta \text{curve}[\mathbf{w},n] 68 | \end{split} 69 | \end{equation} 70 | with $\alpha\geq0$ and $\beta\geq0$.\\ 71 | 72 | Finally the inference is: 73 | \begin{equation} 74 | \begin{split} 75 | \argmax_{\mathbf{W}}[\log{[Pr(\mathbf{W}\vert\mathbf{x})]}] = \argmax_{\mathbf{W}}[\sum_{n=1}^N -(\text{dist}[\mathbf{x},\mathbf{w}_n])^2+\alpha \text{space}[\mathbf{w},n]+\beta \text{curve}[\mathbf{w},n]] 76 | \end{split} 77 | \end{equation} 78 | 79 | The spacing term is formula (17.5) 80 | \begin{equation} 81 | \text{space}[\mathbf{w},n]= - \left(\frac{\sum_{n=1}^N \sqrt{\left( \mathbf{w}_{n}-\mathbf{w}_{n-1} \right)^T\left( \mathbf{w}_{n}-\mathbf{w}_{n-1} \right)}}{N} - \sqrt{\left( \mathbf{w}_{n}-\mathbf{w}_{n-1} \right)^T\left( \mathbf{w}_{n}-\mathbf{w}_{n-1} \right)}\right)^2 82 | \end{equation} 83 | and it is always nonpositive because it is a square and because of the leading minus.\\ 84 | 85 | The curvature term is formula (17.6) 86 | \begin{equation} 87 | \text{curve}[\mathbf{w},n]= - \left(\mathbf{w}_{n-1}-2\mathbf{w}_{n}+\mathbf{w}_{n+1}\right)^T\left(\mathbf{w}_{n-1}-2\mathbf{w}_{n}+\mathbf{w}_{n+1}\right) 88 | \end{equation} 89 | and it is always nonpositive because of the leading minus and because $\mathbf{x}^T\mathbf{x}=\|\mathbf{x}\|^2$ and so it is always nonnegative since it is the square of the ($\ell^2$)norm.\\ 90 | 91 | With these new definitions 92 | \begin{equation} 93 | \begin{split} 94 | \text{d}[\mathbf{x},\mathbf{w}_n]&=(\text{dist}[\mathbf{x},\mathbf{w}_n])^2\\ 95 | \text{s}[\mathbf{w},n]&=-\alpha\text{space}[\mathbf{w},n]\\ 96 | \text{c}[\mathbf{w},n]&=-\beta\text{curve}[\mathbf{w},n] 97 | \end{split} 98 | \end{equation} 99 | we have that $\text{d}[\mathbf{x},\mathbf{w}_n]$, $\text{s}[\mathbf{w},n]$ and $\text{c}[\mathbf{w},n]$ are all nonnegative 100 | and the inference formula can be rewritten as 101 | \begin{equation} 102 | \argmax_{\mathbf{W}}[\log{[Pr(\mathbf{W}\vert\mathbf{x})]}] = -\argmax_{\mathbf{W}}[\sum_{n=1}^N \text{d}[\mathbf{x},\mathbf{w}_n]+\text{s}[\mathbf{w},n]+\text{c}[\mathbf{w},n]] 103 | \end{equation} 104 | and now the argument of $\argmax$ is nonnegative. 105 | 106 | 107 | 108 | \end{document} 109 | -------------------------------------------------------------------------------- /snake.cpp: -------------------------------------------------------------------------------- 1 | // Snakes (active contours) implemented with OpenCV. 2 | // 3 | // Author: Alessandro Gentilini - 2014 4 | // 5 | // Following the Bayesian approach described in "17.2 Snakes" of: 6 | // 7 | // Prince, S.J.D. 8 | // [*Computer Vision: Models Learning and Inference*](http://web4.cs.ucl.ac.uk/staff/s.prince/book/book.pdf), 9 | // Cambridge University Press, 2012 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | std::string getImgType(int imgTypeInt) 19 | { 20 | int numImgTypes = 35; // 7 base types, with five channel options each (none or C1, ..., C4) 21 | 22 | int enum_ints[] = {CV_8U, CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4, 23 | CV_8S, CV_8SC1, CV_8SC2, CV_8SC3, CV_8SC4, 24 | CV_16U, CV_16UC1, CV_16UC2, CV_16UC3, CV_16UC4, 25 | CV_16S, CV_16SC1, CV_16SC2, CV_16SC3, CV_16SC4, 26 | CV_32S, CV_32SC1, CV_32SC2, CV_32SC3, CV_32SC4, 27 | CV_32F, CV_32FC1, CV_32FC2, CV_32FC3, CV_32FC4, 28 | CV_64F, CV_64FC1, CV_64FC2, CV_64FC3, CV_64FC4}; 29 | 30 | std::string enum_strings[] = {"CV_8U", "CV_8UC1", "CV_8UC2", "CV_8UC3", "CV_8UC4", 31 | "CV_8S", "CV_8SC1", "CV_8SC2", "CV_8SC3", "CV_8SC4", 32 | "CV_16U", "CV_16UC1", "CV_16UC2", "CV_16UC3", "CV_16UC4", 33 | "CV_16S", "CV_16SC1", "CV_16SC2", "CV_16SC3", "CV_16SC4", 34 | "CV_32S", "CV_32SC1", "CV_32SC2", "CV_32SC3", "CV_32SC4", 35 | "CV_32F", "CV_32FC1", "CV_32FC2", "CV_32FC3", "CV_32FC4", 36 | "CV_64F", "CV_64FC1", "CV_64FC2", "CV_64FC3", "CV_64FC4"}; 37 | 38 | for(int i=0; i W_t; 46 | typedef float distance_t; 47 | 48 | template< typename T > 49 | struct CPP_TYPE_TO_OPENCV_IMG_TYPE 50 | { 51 | CPP_TYPE_TO_OPENCV_IMG_TYPE() { assert(32==8*sizeof(distance_t)); } 52 | }; 53 | template<> struct CPP_TYPE_TO_OPENCV_IMG_TYPE { enum { type = CV_32FC1 }; }; 54 | 55 | 56 | double distance_from_edge( const cv::Mat& distance_img, const W_t::value_type& w) 57 | { 58 | if ( w.y >= distance_img.rows || w.x >= distance_img.cols ) { 59 | double minVal; 60 | double maxVal; 61 | cv::Point minLoc; 62 | cv::Point maxLoc; 63 | cv::minMaxLoc( distance_img, &minVal, &maxVal, &minLoc, &maxLoc ); 64 | std::cout << "rotto!!!"; 65 | return (pow(w.y,2)+pow(w.x,2))*maxVal; 66 | } 67 | return distance_img.at(w.y,w.x); 68 | } 69 | 70 | double vector_norm2(const W_t::value_type& p) 71 | { 72 | return sqrt(pow(p.x,2) + pow(p.y,2)); 73 | } 74 | 75 | // Formula 17.5 76 | double space(const W_t& W, const size_t& N, const size_t& n) 77 | { 78 | double consecutive_distance_average = 0; 79 | for( size_t i = 1; i<= N; i++ ) { 80 | consecutive_distance_average += vector_norm2(W[i]-W[i-1]); 81 | } 82 | consecutive_distance_average /= N; 83 | double result = -pow(consecutive_distance_average-vector_norm2(W[n]-W[n-1]),2); 84 | //std::cout << "space\t" << result << "\n"; 85 | return result; 86 | } 87 | 88 | // Formula 17.6 89 | double curve(const W_t& W, const size_t& n) 90 | { 91 | double result = -pow(vector_norm2(W[n-1]-2*W[n]+W[n+1]),2); 92 | //std::cout << "curve\t" << result << "\n"; 93 | return result; 94 | } 95 | 96 | // Formula 17.4 97 | double prior(const W_t& W, const size_t& N, const double& alpha, const double& beta) 98 | { 99 | double result = 1; 100 | for ( size_t n = 1; n <= N; n++ ) { 101 | result *= exp(alpha*space(W,N,n)+beta*curve(W,n)); 102 | } 103 | //std::cout << "prior\t" << result << "\n"; 104 | return result; 105 | } 106 | 107 | // Formula 17.3 108 | double likelihood(const cv::Mat& distance_img, const W_t& W, const size_t& N) 109 | { 110 | double result = 1; 111 | for ( size_t n = 1; n <= N; n++ ) { 112 | double d = distance_from_edge(distance_img,W[n]); 113 | result *= exp(-pow(d,2));; 114 | //std::cout << "likelihood d\t" << d << "\t" << -pow(d,2) << "\n"; 115 | } 116 | return result; 117 | } 118 | 119 | // Formula 17.7 120 | double cost(const W_t& W, const cv::Mat& distance_img, const size_t& N,const double& alpha, const double& beta) 121 | { 122 | return log(likelihood(distance_img,W,N)) + log(prior(W,N,alpha,beta)); 123 | } 124 | 125 | int main( int argc, char** argv ) 126 | { 127 | if( argc != 2) 128 | { 129 | std::cout <<" Usage: display_image ImageToLoadAndDisplay" << "\n"; 130 | return -1; 131 | } 132 | 133 | cv::Mat image; 134 | image = cv::imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE); 135 | 136 | if(! image.data ) 137 | { 138 | std::cout << "Could not open or find the image" << "\n"; 139 | return -1; 140 | } 141 | 142 | //cv::namedWindow( "image", cv::WINDOW_AUTOSIZE ); 143 | cv::namedWindow( "image" ); 144 | cv::imshow( "image", image ); 145 | 146 | cv::Mat edges; 147 | cv::Canny(image,edges,100,200); 148 | edges = 255-edges; 149 | 150 | cv::namedWindow( "edges", cv::WINDOW_AUTOSIZE ); 151 | cv::imshow( "edges", edges ); 152 | 153 | cv::Mat distance(edges.rows,edges.cols,CPP_TYPE_TO_OPENCV_IMG_TYPE::type); 154 | 155 | cv::distanceTransform(edges,distance, CV_DIST_L2, CV_DIST_MASK_PRECISE); 156 | std::ostringstream oss; 157 | oss << distance; 158 | std::cout << getImgType(distance.type()) << oss.str().substr(0,70) << "\n"; 159 | 160 | cv::Mat normalized_dist; 161 | cv::normalize(distance.clone(), normalized_dist, 0.0, 1.0, cv::NORM_MINMAX); 162 | 163 | cv::namedWindow( "distance", cv::WINDOW_AUTOSIZE ); 164 | cv::imshow( "distance", normalized_dist ); 165 | 166 | 167 | std::vector circles; 168 | cv::HoughCircles( edges, circles, CV_HOUGH_GRADIENT, 1, 10, 200, 25, 0, 0 ); 169 | 170 | //std::cout << circles.size() << "\n"; 171 | 172 | //for( size_t i = 0; i < circles.size(); i++ ) 173 | cv::Mat hough = image.clone(); 174 | // for( size_t i = 0; i < 1; i++ ) 175 | // { 176 | // Point center(cvRound(circles[i][0]), cvRound(circles[i][1])); 177 | // int radius = cvRound(circles[i][2]); 178 | // circle( hough, center, 3, Scalar(255,255,255)); 179 | // circle( hough, center, radius, Scalar(255,255,255)); 180 | // } 181 | 182 | 183 | const size_t selected_circle = 1; 184 | cv::Point2d w_center(circles[selected_circle][0],circles[selected_circle][1]); 185 | double radius = circles[selected_circle][2]; 186 | const size_t N = 30; 187 | W_t W(N+2); 188 | for ( size_t i = 1; i <= N; i++ ) { 189 | const double a = i*2*M_PI/N; 190 | W[i].x = w_center.x + radius*cos(a); 191 | W[i].y = w_center.y + radius*sin(a); 192 | } 193 | W[0] = W[N]; 194 | W[N+1] = W[1]; 195 | 196 | for ( size_t i = 1; i <= N; i++ ) { 197 | circle( hough, W[i], 3, cv::Scalar(255,255,255)); 198 | } 199 | 200 | cv::namedWindow( "circles", cv::WINDOW_AUTOSIZE ); 201 | cv::imshow( "circles", hough ); 202 | 203 | double alpha = 0.5; 204 | double beta = 0.5; 205 | std::cout << "prior\t" << prior(W,N,alpha,beta) << "\n"; 206 | std::cout << "likelihood\t" << likelihood(distance,W,N) << "\n"; 207 | std::cout << "cost\t" << cost(W,distance,N,alpha,beta) << "\n"; 208 | 209 | cv::waitKey(0); 210 | return 0; 211 | } 212 | --------------------------------------------------------------------------------