Commit a44a2ba7 authored by Vadim Pisarevsky's avatar Vadim Pisarevsky

Merge pull request #1216 from LaurentBerger:FourierDescriptors

parents 1a61e8d2 8b733e09
......@@ -76,9 +76,9 @@
publisher={Springer}
}
@article{deriche1987using,
@article{deriche1987,
title={Using Canny's criteria to derive a recursively implemented optimal edge detector},
author={Deriche, Rachid},
author={Deriche Rachid},
journal={International journal of computer vision},
volume={1},
number={2},
......@@ -259,3 +259,25 @@
year={2009},
organization={International Society for Optics and Photonics}
}
@article{BergerRaghunathan1998,
title={Coalescence in 2 dimensions: experiments on thin copolymer films and numerical simulations},
author={Berger, L and Raghunathan, V A and Launay, C and Ausserré, D and Gallot, Y},
journal={The European Physical Journal B - Condensed Matter and Complex Systems},
volume={2},
number={1},
pages={93-99},
year={1998},
publisher={Springer}
}
@article{PersoonFu1977,
title={Shape Discrimination Using Fourier Descriptors},
author={E Persoon and King-Sun Fu},
journal={IEEE Transactions on Pattern Analysis and Machine Intelligence},
volume={7},
number={3},
pages={170-179},
year={1977},
publisher={IEEE Computer Society}
}
......@@ -52,6 +52,8 @@
#include "ximgproc/fast_line_detector.hpp"
#include "ximgproc/deriche_filter.hpp"
#include "ximgproc/peilin.hpp"
#include "ximgproc/fourier_descriptors.hpp"
/** @defgroup ximgproc Extended Image Processing
@{
......@@ -67,7 +69,9 @@ i.e. algorithms which somehow takes into account pixel affinities in natural ima
@defgroup ximgproc_segmentation Image segmentation
@defgroup ximgproc_fast_line_detector Fast line detector
@}
@defgroup ximgproc_fourier Fourier descriptors
@}
*/
namespace cv
......
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef __OPENCV_FOURIERDESCRIPTORS_HPP__
#define __OPENCV_FOURIERDESCRIPTORS_HPP__
#include <opencv2/core.hpp>
namespace cv {
namespace ximgproc {
//! @addtogroup ximgproc_fourier
//! @{
/** @brief Class for ContourFitting algorithms.
ContourFitting match two contours \f$ z_a \f$ and \f$ z_b \f$ minimizing distance
\f[ d(z_a,z_b)=\sum (a_n - s b_n e^{j(n \alpha +\phi )})^2 \f] where \f$ a_n \f$ and \f$ b_n \f$ are Fourier descriptors of \f$ z_a \f$ and \f$ z_b \f$ and s is a scaling factor and \f$ \phi \f$ is angle rotation and \f$ \alpha \f$ is starting point factor adjustement
*/
class CV_EXPORTS_W ContourFitting : public Algorithm
{
int ctrSize;
int fdSize;
std::vector<std::complex<double> > b;
std::vector<std::complex<double> > a;
std::vector<double> frequence;
std::vector<double> rho, psi;
void frequencyInit();
void fAlpha(double x, double &fn, double &df);
double distance(std::complex<double> r, double alpha);
double newtonRaphson(double x1, double x2);
public:
/** @brief Fit two closed curves using fourier descriptors. More details in @cite PersoonFu1977 and @cite BergerRaghunathan1998
* @param ctr number of Fourier descriptors equal to number of contour points after resampling.
* @param fd Contour defining second shape (Target).
*/
ContourFitting(int ctr=1024,int fd=16):ctrSize(ctr),fdSize(fd){};
/** @brief Fit two closed curves using fourier descriptors. More details in @cite PersoonFu1977 and @cite BergerRaghunathan1998
@param src Contour defining first shape.
@param dst Contour defining second shape (Target).
@param alphaPhiST : \f$ \alpha \f$=alphaPhiST(0,0), \f$ \phi \f$=alphaPhiST(0,1) (in radian), s=alphaPhiST(0,2), Tx=alphaPhiST(0,3), Ty=alphaPhiST(0,4) rotation center
@param dist distance between src and dst after matching.
@param fdContour false then src and dst are contours and true src and dst are fourier descriptors.
*/
CV_WRAP void estimateTransformation(InputArray src, InputArray dst, OutputArray alphaPhiST, double *dist = 0, bool fdContour = false);
/** @brief Fit two closed curves using fourier descriptors. More details in @cite PersoonFu1977 and @cite BergerRaghunathan1998
@param src Contour defining first shape.
@param dst Contour defining second shape (Target).
@param alphaPhiST : \f$ \alpha \f$=alphaPhiST(0,0), \f$ \phi \f$=alphaPhiST(0,1) (in radian), s=alphaPhiST(0,2), Tx=alphaPhiST(0,3), Ty=alphaPhiST(0,4) rotation center
@param dist distance between src and dst after matching.
@param fdContour false then src and dst are contours and true src and dst are fourier descriptors.
*/
CV_WRAP void estimateTransformation(InputArray src, InputArray dst, OutputArray alphaPhiST, double &dist , bool fdContour = false);
/** @brief set number of Fourier descriptors used in estimateTransformation
@param n number of Fourier descriptors equal to number of contour points after resampling.
*/
CV_WRAP void setCtrSize(int n);
/** @brief set number of Fourier descriptors when estimateTransformation used vector<Point>
@param n number of fourier descriptors used for optimal curve matching.
*/
CV_WRAP void setFDSize(int n);
/**
@returns number of fourier descriptors
*/
CV_WRAP int getCtrSize() { return ctrSize; };
/**
@returns number of fourier descriptors used for optimal curve matching
*/
CV_WRAP int getFDSize() { return fdSize; };
};
/**
* @brief Fourier descriptors for planed closed curves
*
* For more details about this implementation, please see @cite PersoonFu1977
*
* @param src contour type vector<Point> , vector<Point2f> or vector<Point2d>
* @param dst Mat of type CV_64FC2 and nbElt rows A VERIFIER
* @param nbElt number of rows in dst or getOptimalDFTSize rows if nbElt=-1
* @param nbFD number of FD return in dst dst = [FD(1...nbFD/2) FD(nbFD/2-nbElt+1...:nbElt)]
*
*/
CV_EXPORTS_W void fourierDescriptor(InputArray src, OutputArray dst, int nbElt=-1,int nbFD=-1);
/**
* @brief transform a contour
*
* @param src contour or Fourier Descriptors if fd is true
* @param t transform Mat given by estimateTransformation
* @param dst Mat of type CV_64FC2 and nbElt rows
* @param fdContour true src are Fourier Descriptors. fdContour false src is a contour
*
*/
CV_EXPORTS_W void transform(InputArray src, InputArray t,OutputArray dst, bool fdContour=true);
/**
* @brief Contour sampling .
*
* @param src contour type vector<Point> , vector<Point2f> or vector<Point2d>
* @param out Mat of type CV_64FC2 and nbElt rows
* @param nbElt number of points in out contour
*
*/
CV_EXPORTS_W void contourSampling(InputArray src, OutputArray out, int nbElt);
/**
* @brief create
* @param ctr number of Fourier descriptors equal to number of contour points after resampling.
* @param fd Contour defining second shape (Target).
*/
CV_EXPORTS_W Ptr<ContourFitting> create(int ctr = 1024, int fd = 16);
//! @} ximgproc_fourier
}
}
#endif
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/ximgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
struct ThParameters {
int levelNoise;
int angle;
int scale10;
int origin;
int xg;
int yg;
bool update;
} ;
static vector<Point> NoisyPolygon(vector<Point> pRef, double n);
static void UpdateShape(int , void *r);
static void AddSlider(String sliderName, String windowName, int minSlider, int maxSlider, int valDefault, int *valSlider, void(*f)(int, void *), void *r);
int main(void)
{
vector<Point> ctrRef;
vector<Point> ctrRotate, ctrNoisy, ctrNoisyRotate, ctrNoisyRotateShift;
// build a shape with 5 vertex
ctrRef.push_back(Point(250,250)); ctrRef.push_back(Point(400, 250));
ctrRef.push_back(Point(400, 300)); ctrRef.push_back(Point(250, 300));ctrRef.push_back(Point(180, 270));
Point cg(0,0);
for (int i=0;i<static_cast<int>(ctrRef.size());i++)
cg+=ctrRef[i];
cg.x /= static_cast<int>(ctrRef.size());
cg.y /= static_cast<int>(ctrRef.size());
ThParameters p;
p.levelNoise=6;
p.angle=45;
p.scale10=5;
p.origin=10;
p.xg=150;
p.yg=150;
p.update=true;
namedWindow("FD Curve matching");
// A rotation with center at (150,150) of angle 45 degrees and a scaling of 5/10
AddSlider("Noise", "FD Curve matching", 0, 20, p.levelNoise, &p.levelNoise, UpdateShape, &p);
AddSlider("Angle", "FD Curve matching", 0, 359, p.angle, &p.angle, UpdateShape, &p);
AddSlider("Scale", "FD Curve matching", 5, 100, p.scale10, &p.scale10, UpdateShape, &p);
AddSlider("Origin%%", "FD Curve matching", 0, 100, p.origin, &p.origin, UpdateShape, &p);
AddSlider("Xg", "FD Curve matching", 150, 450, p.xg, &p.xg, UpdateShape, &p);
AddSlider("Yg", "FD Curve matching", 150, 450, p.yg, &p.yg, UpdateShape, &p);
int code=0;
double dist;
vector<vector<Point> > c;
Mat img;
cout << "******************** PRESS g TO MATCH CURVES *************\n";
do
{
code = waitKey(30);
if (p.update)
{
Mat r = getRotationMatrix2D(Point(p.xg, p.yg), p.angle, 10.0/ p.scale10);
ctrNoisy= NoisyPolygon(ctrRef,static_cast<double>(p.levelNoise));
transform(ctrNoisy, ctrNoisyRotate,r);
ctrNoisyRotateShift.clear();
for (int i=0;i<static_cast<int>(ctrNoisy.size());i++)
ctrNoisyRotateShift.push_back(ctrNoisyRotate[(i+(p.origin*ctrNoisy.size())/100)% ctrNoisy.size()]);
// To draw contour using drawcontours
c.clear();
c.push_back(ctrRef);
c.push_back(ctrNoisyRotateShift);
p.update = false;
Rect rglobal;
for (int i = 0; i < static_cast<int>(c.size()); i++)
{
rglobal = boundingRect(c[i]) | rglobal;
}
rglobal.width += 10;
rglobal.height += 10;
img = Mat::zeros(2 * rglobal.height, 2 * rglobal.width, CV_8UC(3));
drawContours(img, c, 0, Scalar(255,0,0));
drawContours(img, c, 1, Scalar(0, 255, 0));
circle(img, c[0][0], 5, Scalar(255, 0, 0));
circle(img, c[1][0], 5, Scalar(0, 255, 0));
imshow("FD Curve matching", img);
}
if (code == 'd')
{
destroyWindow("FD Curve matching");
namedWindow("FD Curve matching");
// A rotation with center at (150,150) of angle 45 degrees and a scaling of 5/10
AddSlider("Noise", "FD Curve matching", 0, 20, p.levelNoise, &p.levelNoise, UpdateShape, &p);
AddSlider("Angle", "FD Curve matching", 0, 359, p.angle, &p.angle, UpdateShape, &p);
AddSlider("Scale", "FD Curve matching", 5, 100, p.scale10, &p.scale10, UpdateShape, &p);
AddSlider("Origin%%", "FD Curve matching", 0, 100, p.origin, &p.origin, UpdateShape, &p);
AddSlider("Xg", "FD Curve matching", 150, 450, p.xg, &p.xg, UpdateShape, &p);
AddSlider("Yg", "FD Curve matching", 150, 450, p.yg, &p.yg, UpdateShape, &p);
}
if (code == 'g')
{
ximgproc::ContourFitting fit;
vector<Point2f> ctrRef2d, ctrRot2d;
// sampling contour we want 256 points
ximgproc::contourSampling(ctrRef, ctrRef2d, 256); // use a mat
ximgproc::contourSampling(ctrNoisyRotateShift, ctrRot2d, 256); // use a vector og point
fit.setFDSize(16);
Mat t;
fit.estimateTransformation(ctrRot2d, ctrRef2d, t, &dist, false);
cout << "Transform *********\n "<<"Origin = "<< t.at<double>(0,0)*ctrNoisy.size() <<" expected "<< (p.origin*ctrNoisy.size()) / 100 <<" ("<< ctrNoisy.size()<<")\n";
cout << "Angle = " << t.at<double>(0, 1) * 180 / M_PI << " expected " << p.angle <<"\n";
cout << "Scale = " << t.at<double>(0, 2) << " expected " << p.scale10 / 10.0 << "\n";
Mat dst;
ximgproc::transform(ctrRot2d, t, dst, false);
c.push_back(dst);
drawContours(img, c, 2, Scalar(0,255,255));
circle(img, c[2][0], 5, Scalar(0, 255, 255));
imshow("FD Curve matching", img);
}
}
while (code!=27);
return 0;
}
vector<Point> NoisyPolygon(vector<Point> pRef, double n)
{
RNG rng;
vector<Point> c;
vector<Point> p = pRef;
vector<vector<Point> > contour;
for (int i = 0; i<static_cast<int>(p.size()); i++)
p[i] += Point(Point2d(n*rng.uniform((double)-1, (double)1), n*rng.uniform((double)-1, (double)1)));
if (n==0)
return p;
c.push_back(p[0]);
int minX = p[0].x, maxX = p[0].x, minY = p[0].y, maxY = p[0].y;
for (int i = 0; i <static_cast<int>(p.size()); i++)
{
int next = i + 1;
if (next == static_cast<int>(p.size()))
next = 0;
Point2d u = p[next] - p[i];
int d = static_cast<int>(norm(u));
double a = atan2(u.y, u.x);
int step = 1;
if (n != 0)
step = static_cast<int>(d / n);
for (int j = 1; j<d; j += max(step, 1))
{
Point pNew;
do
{
Point2d pAct = (u*j) / static_cast<double>(d);
double r = n*rng.uniform((double)0, (double)1);
double theta = a + rng.uniform(0., 2 * CV_PI);
pNew = Point(Point2d(r*cos(theta) + pAct.x + p[i].x, r*sin(theta) + pAct.y + p[i].y));
} while (pNew.x<0 || pNew.y<0);
if (pNew.x<minX)
minX = pNew.x;
if (pNew.x>maxX)
maxX = pNew.x;
if (pNew.y<minY)
minY = pNew.y;
if (pNew.y>maxY)
maxY = pNew.y;
c.push_back(pNew);
}
}
return c;
}
void UpdateShape(int , void *r)
{
((ThParameters *)r)->update = true;
}
void AddSlider(String sliderName, String windowName, int minSlider, int maxSlider, int valDefault, int *valSlider, void(*f)(int, void *), void *r)
{
createTrackbar(sliderName, windowName, valSlider, 1, f, r);
setTrackbarMin(sliderName, windowName, minSlider);
setTrackbarMax(sliderName, windowName, maxSlider);
setTrackbarPos(sliderName, windowName, valDefault);
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment