Convexity defects C++ OpenCv
I raised this question because I wasn't able to figure out a solution (it is not only today that I was dealing with the matter hehe), but after all I was able to manage the problem!
I had to change the way I calculated the convex hull, using the index array form. So now we have a vector< int > instead a vector< Point >.
This is the code I used (it works I painted the points over an image):
void HandDetection::findConvexityDefects(vector<Point>& contour, vector<int>& hull, vector<Point>& convexDefects){
if(hull.size() > 0 && contour.size() > 0){
CvSeq* contourPoints;
CvSeq* defects;
CvMemStorage* storage;
CvMemStorage* strDefects;
CvMemStorage* contourStr;
CvConvexityDefect *defectArray = 0;
strDefects = cvCreateMemStorage();
defects = cvCreateSeq( CV_SEQ_KIND_GENERIC|CV_32SC2, sizeof(CvSeq),sizeof(CvPoint), strDefects );
//We transform our vector<Point> into a CvSeq* object of CvPoint.
contourStr = cvCreateMemStorage();
contourPoints = cvCreateSeq(CV_SEQ_KIND_GENERIC|CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), contourStr);
for(int i=0; i<(int)contour.size(); i++) {
CvPoint cp = {contour[i].x, contour[i].y};
cvSeqPush(contourPoints, &cp);
}
//Now, we do the same thing with the hull index
int count = (int)hull.size();
//int hullK[count];
int* hullK = (int*)malloc(count*sizeof(int));
for(int i=0; i<count; i++){hullK[i] = hull.at(i);}
CvMat hullMat = cvMat(1, count, CV_32SC1, hullK);
//We calculate convexity defects
storage = cvCreateMemStorage(0);
defects = cvConvexityDefects(contourPoints, &hullMat, storage);
defectArray = (CvConvexityDefect*)malloc(sizeof(CvConvexityDefect)*defects->total);
cvCvtSeqToArray(defects, defectArray, CV_WHOLE_SEQ);
//printf("DefectArray %i %i\n",defectArray->end->x, defectArray->end->y);
//We store defects points in the convexDefects parameter.
for(int i = 0; i<defects->total; i++){
CvPoint ptf;
ptf.x = defectArray[i].depth_point->x;
ptf.y = defectArray[i].depth_point->y;
convexDefects.push_back(ptf);
}
//We release memory
cvReleaseMemStorage(contourStr);
cvReleaseMemStorage(strDefects);
cvReleaseMemStorage(storage);
}
}
This worked for me. If you see something wrong or another way to manage it, please tell me!
OpenCV last convexity defect not right
i tested your code (with a small modification) with the image below (OpenCV version is 3.2).
as you can see on the result image it works as expected. probably you are using an old version of OpenCV and getting a buggy result. (i think it was a bug recently fixed)
#include "opencv2\opencv.hpp"
using namespace cv;
using namespace std;
int main() {
//VideoCapture cap(0);
Mat src, gray, background, binary, diff;
//cap >> background;
//cvtColor(background, background, CV_BGR2GRAY);
vector<vector<Point> > contours;
vector < vector<int> > hullI = vector<vector<int> >(1);
vector < vector<Point> > hullP = vector<vector<Point> >(1);
vector<Vec4i> defects;
src = imread("hand.png");
cvtColor(src, gray, CV_BGR2GRAY);
blur(gray, gray, Size(3, 3));
threshold(gray, binary, 150, 255, THRESH_BINARY_INV);
//erode(binary, binary, Mat(Size(5, 5), CV_8U));
imshow("binary", binary);
findContours(binary, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
if (!contours.empty()) {
sort(contours.begin(), contours.end(), [](vector<Point> a, vector<Point> b) { return a.size() > b.size(); });
drawContours(src, contours, 0, Scalar(255, 255, 0));
convexHull(contours[0], hullI[0]);
convexHull(contours[0], hullP[0]);
drawContours(src, hullP, 0, Scalar(0, 255, 255));
if (hullI[0].size() > 2) {
convexityDefects(contours[0], hullI[0], defects);
for (Vec4i defect : defects) {
line(src, contours[0][defect[0]], contours[0][defect[2]], Scalar(0, 0, 255));
line(src, contours[0][defect[1]], contours[0][defect[2]], Scalar(0, 0, 255));
}
}
}
imshow("result", src);
char key = waitKey(0);
return 0;
}
OpenCV convexity defects drawing
convexityDefects needs a
Convex hull obtained using convexHull() that should contain indices of the contour points that make the hull.
that contains more than 3 indices. So you need this:
vector<vector<Point> >hull(Contours.size());
vector<vector<int> > hullsI(Contours.size()); // Indices to contour points
vector<vector<Vec4i>> defects(Contours.size());
for (int i = 0; i < Contours.size(); i++)
{
convexHull(Contours[i], hull[i], false);
convexHull(Contours[i], hullsI[i], false);
if(hullsI[i].size() > 3 ) // You need more than 3 indices
{
convexityDefects(Contours[i], hullsI[i], defects[i]);
}
}
Then your drawing part is (adapted from here):
/// Draw convexityDefects
for (int i = 0; i < Contours.size(); ++i)
{
for(const Vec4i& v : defects[i])
{
float depth = v[3] / 256;
if (depth > 10) // filter defects by depth, e.g more than 10
{
int startidx = v[0]; Point ptStart(Contours[i][startidx]);
int endidx = v[1]; Point ptEnd(Contours[i][endidx]);
int faridx = v[2]; Point ptFar(Contours[i][faridx]);
line(frame, ptStart, ptEnd, Scalar(0, 255, 0), 1);
line(frame, ptStart, ptFar, Scalar(0, 255, 0), 1);
line(frame, ptEnd, ptFar, Scalar(0, 255, 0), 1);
circle(frame, ptFar, 4, Scalar(0, 255, 0), 2);
}
}
}
Complete code
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img, frame, img2, img3;
VideoCapture cam(0);
while (true){
cam.read(frame);
cvtColor(frame, img, CV_BGR2HSV);
//thresholding
inRange(img, Scalar(0, 143, 86), Scalar(39, 255, 241), img2);
imshow("hi", img2);
//finding contours
vector<vector<Point>> Contours;
vector<Vec4i> hier;
//morphological transformations
erode(img2, img2, getStructuringElement(MORPH_RECT, Size(3, 3)));
erode(img2, img2, getStructuringElement(MORPH_RECT, Size(3, 3)));
dilate(img2, img2, getStructuringElement(MORPH_RECT, Size(8, 8)));
dilate(img2, img2, getStructuringElement(MORPH_RECT, Size(8, 8)));
//finding the contours required
findContours(img2, Contours, hier, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE, Point(0, 0));
//finding the contour of largest area and storing its index
int lrgctridx = 0;
int maxarea = 0;
for (int i = 0; i < Contours.size(); i++)
{
double a = contourArea(Contours[i]);
if (a> maxarea)
{
maxarea = a;
lrgctridx = i;
}
}
//convex hulls
vector<vector<Point> >hull(Contours.size());
vector<vector<int> > hullsI(Contours.size());
vector<vector<Vec4i>> defects(Contours.size());
for (int i = 0; i < Contours.size(); i++)
{
convexHull(Contours[i], hull[i], false);
convexHull(Contours[i], hullsI[i], false);
if(hullsI[i].size() > 3 )
{
convexityDefects(Contours[i], hullsI[i], defects[i]);
}
}
//REQUIRED contour is detected,then convex hell is found and also convexity defects are found and stored in defects
if (maxarea>100){
drawContours(frame, hull, lrgctridx, Scalar(2555, 0, 255), 3, 8, vector<Vec4i>(), 0, Point());
/// Draw convexityDefects
for(int j=0; j<defects[lrgctridx].size(); ++j)
{
const Vec4i& v = defects[lrgctridx][j];
float depth = v[3] / 256;
if (depth > 10) // filter defects by depth
{
int startidx = v[0]; Point ptStart(Contours[lrgctridx][startidx]);
int endidx = v[1]; Point ptEnd(Contours[lrgctridx][endidx]);
int faridx = v[2]; Point ptFar(Contours[lrgctridx][faridx]);
line(frame, ptStart, ptEnd, Scalar(0, 255, 0), 1);
line(frame, ptStart, ptFar, Scalar(0, 255, 0), 1);
line(frame, ptEnd, ptFar, Scalar(0, 255, 0), 1);
circle(frame, ptFar, 4, Scalar(0, 255, 0), 2);
}
}
}
imshow("output", frame);
char key = waitKey(33);
if (key == 27) break;
}
}
How Convexity Defect is calculated in OpenCV?
Based on the documentation, the input are two lists of coordinates:
contour
defining the original contour (red on the image below)convexhull
defining the convex hull corresponding to that contour (blue on the image below)
The algorithm works in the following manner:
If the contour or the hull contain 3 or less points, then the contour is always convex, and no more processing is needed. The algorithm assures that both the contour and the hull are accessed in the same orientation.
N.B.: In further explanation I assume they are in the same orientation, and ignore the details regarding representation of the floating point depth as an integer.
Then for each pair of adjacent hull points (H[i]
, H[i+1]
), defining one edge of the convex hull, calculate the distance from the edge for each point on the contour C[n]
that lies between H[i]
and H[i+1]
(excluding C[n] == H[i+1]
). If the distance is greater than zero, then a defect is present. When a defect is present, record i
, i+1
, the maximum distance and the index (n
) of the contour point where the maximum located.
Distance is calculated in the following manner:
dx0 = H[i+1].x - H[i].x
dy0 = H[i+1].y - H[i].y
if (dx0 is 0) and (dy0 is 0) then
scale = 0
else
scale = 1 / sqrt(dx0 * dx0 + dy0 * dy0)
dx = C[n].x - H[i].x
dy = C[n].y - H[i].y
distance = abs(-dy0 * dx + dx0 * dy) * scale
It may be easier to visualize in terms of vectors:
C
: defect vector fromH[i]
toC[n]
H
: hull edge vector fromH[i]
toH[i+1]
H_rot
: hull edge vectorH
rotated 90 degreesU_rot
: unit vector in direction ofH_rot
H
components are [dx0, dy0]
, so rotating 90 degrees gives [-dy0, dx0]
.
scale
is used to find U_rot
from H_rot
, but because divisions are more computationally expensive than multiplications, the inverse is used as an optimization. It's also pre-calculated before the loop over C[n]
to avoid recomputing each iteration.
|H
| = sqrt(dx0 * dx0 + dy0 * dy0)
U_rot
= H_rot
/ |H
| = H_rot
* scale
Then, a dot product between C
and U_rot
gives the perpendicular distance from the defect point to the hull edge, and abs()
is used to get a positive magnitude in any orientation.
distance = abs(U_rot
.C
) = abs(-dy0 * dx + dx0 * dy) * scale
In the scenario depicted on the above image, in first iteration, the edge is defined by H[0]
and H[1]
. The contour points tho examine for this edge are C[0]
, C[1]
, and C[2]
(since C[3] == H[1]
).
There are defects at C[1]
and C[2]
. The defect at C[1]
is the deepest, so the algorithm will record (0, 1, 1, 50)
.
The next edge is defined by H[1]
and H[2]
, and corresponding contour point C[3]
. No defect is present, so nothing is recorded.
The next edge is defined by H[2]
and H[3]
, and corresponding contour point C[4]
. No defect is present, so nothing is recorded.
Since C[5] == H[3]
, the last contour point can be ignored -- there can't be a defect there.
Calculating convexityDefects using OpenCV 2.4 in c++
From openCV wiki :
Finds the convexity defects of a contour.
So you should include it in your loop.
std::vector<Vec4i> defects;
vector<cv::vector<int> >hull( contours.size() );
for (int i = 0; i < contours.size(); i++)
{
convexHull( contours[i], hull[i], false );
convexityDefects(contours[i], hull[i], defects[i]);
}
Also, as you mentioned, in wiki is said:
hull – Output convex hull. It is either an integer vector of indices
or vector of points. In the first case, the hull elements are 0-based
indices of the convex hull points in the original array (since the set
of convex hull points is a subset of the original point set). In the
second case, hull elements are the convex hull points themselves.
finding convexity defects in opencv? [crashes depending on the given input image..]
cvConvexityDefects
expects the convexHull
sequence (second argument) to contain indices into the contour
sequence (first argument):
Convex hull obtained using ConvexHull2 that should contain pointers or indices to the contour points, not the hull points themselves
In the most trivial case, where
cvFindContours
returns a single simple contour (your second image) you got lucky and your code would supply the correct sequence as the first parameter.In case
cvFindContours
finds holes in the contour (your third image), or if there are several simple contours or contours with holes (your first image) your code:finds a convex hull of each contour in turn, but only remembers the last one (since each iteration of the loop overwrites
retHulls
variable)passes the whole hierarchy of the contours, which doesn't correspond to indices in
retHulls
, tocvConvexityDefects
as the first argument.
Instead, you should have:
passed
CV_RETR_EXTERNAL
to thecvFindContour
to only get the outer contours (you don't care for defects of holes)moved the
cvConvexityDefects
inside the last loop.
Something like:
/* ... */
if (argc < 2) {
std::cerr << "Usage: convexity IMAGE\n";
exit(1);
}
cvNamedWindow( "original", 1 );
cvNamedWindow( "contours", 1 );
cvNamedWindow( "hull", 1 );
IplImage* original_img = NULL;
original_img = cvLoadImage(argv[1], CV_LOAD_IMAGE_GRAYSCALE );
IplImage* img_edge = cvCreateImage( cvGetSize(original_img), 8, 1 );
IplImage* contour_img = cvCreateImage( cvGetSize(original_img), 8, 3 );
IplImage* hull_img = cvCreateImage( cvGetSize(original_img), 8, 3 );
cvThreshold( original_img, img_edge, 128, 255, CV_THRESH_BINARY );
CvMemStorage* storage = cvCreateMemStorage();
CvSeq* first_contour = NULL;
int Nc = cvFindContours(
img_edge,
storage,
&first_contour,
sizeof(CvContour),
CV_RETR_EXTERNAL // Try all four values and see what happens
);
cvCvtColor( original_img, contour_img, CV_GRAY2BGR );
for( CvSeq* c=first_contour; c!=NULL; c=c->h_next ) {
cvDrawContours(
contour_img,
c,
CVX_RED,
CVX_BLUE,
0,
2,
8
);
}
cvShowImage( "contours", contour_img );
//----------------------------------------------------------------------Convex Hull
//-------------------------------------------------------------------Convex Defects
CvMemStorage* hull_storage = cvCreateMemStorage();
CvSeq* retHulls = NULL;
cvCvtColor( original_img, hull_img, CV_GRAY2BGR );
for(CvSeq* i = first_contour; i != NULL; i = i->h_next){
retHulls = cvConvexHull2(i,hull_storage,CV_CLOCKWISE,0);
printf(" %d elements:\n", retHulls->total );
CvSeq* defect = NULL;
defect = cvConvexityDefects(i,retHulls, NULL); // reuse storage of the contour
printf(" %d defect:\n", defect->total );
// drawing hull.... you can't use the one returned above since it only
// contains indices
retHulls = cvConvexHull2(i,hull_storage,CV_CLOCKWISE,1);
cvDrawContours(
hull_img,
retHulls,
CVX_RED,
CVX_BLUE,
0,
2,
8
);
}
cvShowImage( "hull", hull_img );
/* ... */
OpenCv Convexity Defects
std::vector<Vec4i> defects;
should be
std::vector< std::vector<Vec4i> > defects( contours.size() );
(one for each contour / hull)
Related Topics
C++: How to Get Fprintf Results as a Std::String W/O Sprintf
Is Boost Shared_Ptr <Xxx> Thread Safe
Can't Find Windows Forms Application for C++
Weird Msc 8.0 Error: "The Value of Esp Was Not Properly Saved Across a Function Call..."
Std::Back_Inserter for a Std::Set
Initializing Container of Unique_Ptrs from Initializer List Fails with Gcc 4.7
Access Private Member Using Template Trick
C++ Local Variable Destruction Order
Uses for Anonymous Namespaces in Header Files
Std::Unique_Ptr with Derived Class
Is Writing to &Str[0] Buffer (Of a Std:String) Well-Defined Behaviour in C++11
Performance Difference Between C and C++ Style File Io
Enum VS Constexpr for Actual Static Constants Inside Classes
Visual Studio 2010 & 2008 Can't Handle Source Files with Identical Names in Different Folders
Using Std::Map<K,V> Where V Has No Usable Default Constructor