Search code examples
c++copencvconvex-hull

Adding to a CvSeq or other type of dynamic memory storage in C/C++ in an OpenCV project


Question Answered, see solution at end of question. More comments / answers still welcome.

So I'm having some trouble getting all my elements into one memory store, the best I have managed to do is overwrite the store each time and only get out the final elements that overwrite it which sadly isn't enough.

I'm primarily a Java programmer so I'll probably use some Java terms in place of the C equivalent and possiblly mention the wrong types when refering to C objects so I appoligise in advance for this (and don't get me started on pointers).

Ultimately what I am trying to do is go grab all of the Convexity Defects in an image and store them into one continuous space which I will then pass back into Java via JNI. The type of the store isn't important, at this stage I just need it working and I can work on optomization if we get the go ahead but I'm thinking a Stack, Queue, List, Vector or similar would do the job. Currently I've been trying to loop round adding groups of CvSeq objects to one large CvSeq, I'll post up my code and discuss it after the post:

 CvSeq *allDefects;
allDefects = cvCreateSeq(0, sizeof (CvSeq), sizeof (CvConvexityDefect), mem_storage4);

CvContourScanner scanner3 = cvStartFindContours(img_bin, mem_storage3);
while ((c2 = cvFindNextContour(scanner3)) != NULL) {
    if (threshold != 0 && cvContourPerimeter(c2) < threshold) {
        cvSubstituteContour(scanner3, NULL);
    } else { // otherwise create the hull
        CvSeq* c_new;
        c_new = cvConvexHull2(c2, mem_storage5, CV_CLOCKWISE, 0);
        CvMemStorage* storage;
        storage = cvCreateMemStorage(0);
        defects = cvConvexityDefects(c2, c_new, storage);
        allDefects = defects;
        //            for (int i = 0;  i < defects->total; i++){
        //            cvSeqPush(allDefects, CV_GET_SEQ_ELEM(CvSeq, defects, i));
        //            }
    }
}

So we create a scanner and whilst we can find contours we do so and check it is bigger than a given threshold. Assuming it is we create a hull round it and then find the defects between it and the hull. This means defects can have multi objects in it which I want to add to allDefects each time we loop round but the only way I can get it going is by making allDefects equal to defects meaning it is overwritten each loop. You can see a little bit of commented code where I attempt to push it like a stack but this crashes with the error:

Assertion failed: sizeof(((defects))->first[0]) == sizeof(CvSeqBlock) && ((defects))->elem_size == sizeof(CvSeq), file vtoolsModified.cpp, line 1407

Going by the documention "The cvConvexityDefects() routine returns a sequence of CvConvexityDefect structures" so that is a CvSeq full of CvConvexityDefect(s). The signiture of cvConvexityDefects in case that is of any help is:

 CVAPI(CvSeq*)  cvConvexityDefects( const CvArr* contour, const CvArr* 
    convexhull, CvMemStorage* storage CV_DEFAULT(NULL));

So to summarise what I am looking to do is find a contour, find its hull, find the defects between them, add all these defects to one large store, repeat till there is no contours left, return the large store with all defects to Java. It is the bit in bold I am looking for help with.

Anyone able to help or point me in the direction of a source that can? (I've been working on this specific problem for about 2 weeks now so have hit up a lot of resources, and yes I feel really daft for not managing to figure what should effectively be a simple loop on my own.)

Thanks

EDIT - More detail added as a result of comments.

EDIT 2 - Question Answered, the resultant, fixed code is below

Looks like my lack of understanding of C was the underlying problem. I was assuming I needed to use CvSeq in a place where I should have been using CvConvexityDefect. The corrected code is as follows:

CvSeq *defects;
CvSeq *allDefects;
allDefects = cvCreateSeq(0, sizeof(CvSeq), sizeof(CvConvexityDefect), mem_storage4);

CvContourScanner scanner3 = cvStartFindContours(img_bin, mem_storage3);
while ((c2 = cvFindNextContour(scanner3)) != NULL) {
    if (threshold != 0 && cvContourPerimeter(c2) < threshold) {
        cvSubstituteContour(scanner3, NULL);
    } else { // otherwise create the hull
        CvSeq* c_new;
        c_new = cvConvexHull2(c2, mem_storage5, CV_CLOCKWISE, 0);
        CvMemStorage* storage;
        storage = cvCreateMemStorage(0);
        defects = cvConvexityDefects(c2, c_new, storage);
        //            allDefects = defects;
        if (defects->total < 100) {
         for (int i = 0; i < defects->total; i++) {
          CvConvexityDefect* element = CV_GET_SEQ_ELEM(CvConvexityDefect, defects, i);
           if (element != 0){      
               cvSeqPush(allDefects, element);                    }
            }
        }
    }
}

You can see I am also checking to make sure less than 100 defects are returned (sometimes it is millions which breaks the system) and also making sure CV_GET_SEQ_ELEM doesn't return null (I'm not sure if it can return null but I'll check just in case.


Solution

  • My guess is that your loop has a mistake in this line:

    CV_GET_SEQ_ELEM(CvSeq, defects, i)
    

    If you look at the definition of it :

    #define CV_GET_SEQ_ELEM( elem_type, seq, index ) CV_SEQ_ELEM( (seq), elem_type, (index) )
    

    you can see that the first parameter has to be the element type(cvFindContours usually makes sequences of CvPoint) and not the type of the sequence (CvSeq)

    Other ways that you can store your sequences would be to use

        void* cvCvtSeqToArray(const CvSeq* seq,
                              void* elements,
                              CvSlice slice = CV_WHOLE_SEQ
                             );
    

    and store the arrays in a list, in so keeping the defects separated for each object. There are even more options, depending on what you need.

    EDIT2:

    Ok, so i just reviewed cvConvexityDefects and saw that it returns a sequence of structures, not CvPoint, then your allDefects should be a custom made CvSeq and you will modify you code like this:

    cvSeqPush(allDefects, CV_GET_SEQ_ELEM(CvConvexityDefect, defects, i));