/* Final stage of contour processing. Three variants possible: 1. Contour, which was retrieved using border following, is added to the contour tree. It is the case when the icvSubstituteContour function was not called after retrieving the contour. 2. New contour, assigned by icvSubstituteContour function, is added to the tree. The retrieved contour itself is removed from the storage. Here two cases are possible: 2a. If one deals with plane variant of algorithm (hierarchical strucutre is not reconstructed), the contour is removed completely. 2b. In hierarchical case, the header of the contour is not removed. It's marked as "link to contour" and h_next pointer of it is set to new, substituting contour. 3. The similar to 2, but when NULL pointer was assigned by icvSubstituteContour function. In this case, the function removes retrieved contour completely if plane case and leaves header if hierarchical (but doesn't mark header as "link"). ------------------------------------------------------------------------ The 1st variant can be used to retrieve and store all the contours from the image (with optional convertion from chains to contours using some approximation from restriced set of methods). Some characteristics of contour can be computed in the same pass. The usage scheme can look like: icvContourScanner scanner; CvMemStorage* contour_storage; CvSeq* first_contour; CvStatus result; ... icvCreateMemStorage( &contour_storage, block_size/0 ); ... cvStartFindContours ( img, contour_storage, header_size, approx_method, [external_only,] &scanner ); for(;;) { [CvSeq* contour;] result = icvFindNextContour( &scanner, &contour/0 ); if( result != CV_OK ) break; // calculate some characteristics ... } if( result < 0 ) goto error_processing; cvEndFindContours( &scanner, &first_contour ); ... ----------------------------------------------------------------- Second variant is more complex and can be used when someone wants store not the retrieved contours but transformed ones. (e.g. approximated with some non-default algorithm ). The scheme can be the as following: icvContourScanner scanner; CvMemStorage* contour_storage; CvMemStorage* temp_storage; CvSeq* first_contour; CvStatus result; ... icvCreateMemStorage( &contour_storage, block_size/0 ); icvCreateMemStorage( &temp_storage, block_size/0 ); ... icvStartFindContours8uC1R ( <img_params>, temp_storage, header_size, approx_method, [retrival_mode], &scanner ); for(;;) { CvSeq* temp_contour; CvSeq* new_contour; result = icvFindNextContour( scanner, &temp_contour ); if( result != CV_OK ) break; <approximation_function>( temp_contour, contour_storage, &new_contour, <parameters...> ); icvSubstituteContour( scanner, new_contour ); ... } if( result < 0 ) goto error_processing; cvEndFindContours( &scanner, &first_contour ); ... ---------------------------------------------------------------------------- Third method to retrieve contours may be applied if contours are irrelevant themselves but some characteristics of them are used only. The usage is similar to second except slightly different internal loop for(;;) { CvSeq* temp_contour; result = icvFindNextContour( &scanner, &temp_contour ); if( result != CV_OK ) break; // calculate some characteristics of temp_contour icvSubstituteContour( scanner, 0 ); ... } new_storage variable is not needed here. Two notes. 1. Second and third method can interleave. I.e. it is possible to remain contours that satisfy with some criteria and reject others. In hierarchic case the resulting tree is the part of original tree with some nodes absent. But in the resulting tree the contour1 is a child (may be indirect) of contour2 iff in the original tree the contour1 is a child (may be indirect) of contour2. */ static void icvEndProcessContour( CvContourScanner scanner ) { _CvContourInfo *l_cinfo = scanner->l_cinfo; if( l_cinfo ) { if( scanner->subst_flag ) { CvMemStoragePos temp; cvSaveMemStoragePos( scanner->storage2, &temp ); if( temp.top == scanner->backup_pos2.top && temp.free_space == scanner->backup_pos2.free_space ) { cvRestoreMemStoragePos( scanner->storage2, &scanner->backup_pos ); } scanner->subst_flag = 0; } if( l_cinfo->contour ) { cvInsertNodeIntoTree( l_cinfo->contour, l_cinfo->parent->contour, &(scanner->frame) ); } scanner->l_cinfo = 0; } }
/* Moves stack pointer to next block. If no blocks, allocate new one and link it to the storage: */ static void icvGoNextMemBlock( CvMemStorage * storage ) { if( !storage ) CV_Error( CV_StsNullPtr, "" ); if( !storage->top || !storage->top->next ) { CvMemBlock *block; if( !(storage->parent) ) { block = (CvMemBlock *)cvAlloc( storage->block_size ); } else { CvMemStorage *parent = storage->parent; CvMemStoragePos parent_pos; cvSaveMemStoragePos( parent, &parent_pos ); icvGoNextMemBlock( parent ); block = parent->top; cvRestoreMemStoragePos( parent, &parent_pos ); if( block == parent->top ) /* the single allocated block */ { assert( parent->bottom == block ); parent->top = parent->bottom = 0; parent->free_space = 0; } else { /* cut the block from the parent's list of blocks */ parent->top->next = block->next; if( block->next ) block->next->prev = parent->top; } } /* link block */ block->next = 0; block->prev = storage->top; if( storage->top ) storage->top->next = block; else storage->top = storage->bottom = block; } if( storage->top->next ) storage->top = storage->top->next; storage->free_space = storage->block_size - sizeof(CvMemBlock); assert( storage->free_space % CV_STRUCT_ALIGN == 0 ); }
CvSeq * cvFindNextContour( CvContourScanner scanner ) { char *img0; char *img; int step; int width, height; int x, y; int prev; CvPoint lnbd; CvSeq *contour = 0; int nbd; int mode; CvStatus result = (CvStatus) 1; CV_FUNCNAME( "cvFindNextContour" ); __BEGIN__; if( !scanner ) CV_ERROR( CV_StsNullPtr, "" ); icvEndProcessContour( scanner ); /* initialize local state */ img0 = scanner->img0; img = scanner->img; step = scanner->img_step; x = scanner->pt.x; y = scanner->pt.y; width = scanner->img_size.width; height = scanner->img_size.height; mode = scanner->mode; lnbd = scanner->lnbd; nbd = scanner->nbd; prev = img[x - 1]; for( ; y < height; y++, img += step ) { for( ; x < width; x++ ) { int p = img[x]; if( p != prev ) { _CvContourInfo *par_info = 0; _CvContourInfo *l_cinfo = 0; CvSeq *seq = 0; int is_hole = 0; CvPoint origin; if( !(prev == 0 && p == 1) ) /* if not external contour */ { /* check hole */ if( p != 0 || prev < 1 ) goto resume_scan; if( prev & -2 ) { lnbd.x = x - 1; } is_hole = 1; } if( mode == 0 && (is_hole || img0[lnbd.y * step + lnbd.x] > 0) ) goto resume_scan; origin.y = y; origin.x = x - is_hole; /* find contour parent */ if( mode <= 1 || (!is_hole && mode == 2) || lnbd.x <= 0 ) { par_info = &(scanner->frame_info); } else { int lval = img0[lnbd.y * step + lnbd.x] & 0x7f; _CvContourInfo *cur = scanner->cinfo_table[lval - 2]; assert( lval >= 2 ); /* find the first bounding contour */ while( cur ) { if( (unsigned) (lnbd.x - cur->rect.x) < (unsigned) cur->rect.width && (unsigned) (lnbd.y - cur->rect.y) < (unsigned) cur->rect.height ) { if( par_info ) { if( icvTraceContour( scanner->img0 + par_info->origin.y * step + par_info->origin.x, step, img + lnbd.x, par_info->is_hole ) > 0 ) break; } par_info = cur; } cur = cur->next; } assert( par_info != 0 ); /* if current contour is a hole and previous contour is a hole or current contour is external and previous contour is external then the parent of the contour is the parent of the previous contour else the parent is the previous contour itself. */ if( par_info->is_hole == is_hole ) { par_info = par_info->parent; /* every contour must have a parent (at least, the frame of the image) */ if( !par_info ) par_info = &(scanner->frame_info); } /* hole flag of the parent must differ from the flag of the contour */ assert( par_info->is_hole != is_hole ); if( par_info->contour == 0 ) /* removed contour */ goto resume_scan; } lnbd.x = x - is_hole; cvSaveMemStoragePos( scanner->storage2, &(scanner->backup_pos) ); seq = cvCreateSeq( scanner->seq_type1, scanner->header_size1, scanner->elem_size1, scanner->storage1 ); if( !seq ) { result = CV_OUTOFMEM_ERR; goto exit_func; } seq->flags |= is_hole ? CV_SEQ_FLAG_HOLE : 0; /* initialize header */ if( mode <= 1 ) { l_cinfo = &(scanner->cinfo_temp); result = icvFetchContour( img + x - is_hole, step, cvPoint( origin.x + scanner->offset.x, origin.y + scanner->offset.y), seq, scanner->approx_method1 ); if( result < 0 ) goto exit_func; } else { union { _CvContourInfo* ci; CvSetElem* se; } v; v.ci = l_cinfo; cvSetAdd( scanner->cinfo_set, 0, &v.se ); l_cinfo = v.ci; result = icvFetchContourEx( img + x - is_hole, step, cvPoint( origin.x + scanner->offset.x, origin.y + scanner->offset.y), seq, scanner->approx_method1, nbd, &(l_cinfo->rect) ); if( result < 0 ) goto exit_func; l_cinfo->rect.x -= scanner->offset.x; l_cinfo->rect.y -= scanner->offset.y; l_cinfo->next = scanner->cinfo_table[nbd - 2]; scanner->cinfo_table[nbd - 2] = l_cinfo; /* change nbd */ nbd = (nbd + 1) & 127; nbd += nbd == 0 ? 3 : 0; } l_cinfo->is_hole = is_hole; l_cinfo->contour = seq; l_cinfo->origin = origin; l_cinfo->parent = par_info; if( scanner->approx_method1 != scanner->approx_method2 ) { result = icvApproximateChainTC89( (CvChain *) seq, scanner->header_size2, scanner->storage2, &(l_cinfo->contour), scanner->approx_method2 ); if( result < 0 ) goto exit_func; cvClearMemStorage( scanner->storage1 ); } l_cinfo->contour->v_prev = l_cinfo->parent->contour; if( par_info->contour == 0 ) { l_cinfo->contour = 0; if( scanner->storage1 == scanner->storage2 ) { cvRestoreMemStoragePos( scanner->storage1, &(scanner->backup_pos) ); } else { cvClearMemStorage( scanner->storage1 ); } p = img[x]; goto resume_scan; } cvSaveMemStoragePos( scanner->storage2, &(scanner->backup_pos2) ); scanner->l_cinfo = l_cinfo; scanner->pt.x = x + 1; scanner->pt.y = y; scanner->lnbd = lnbd; scanner->img = (char *) img; scanner->nbd = nbd; contour = l_cinfo->contour; result = CV_OK; goto exit_func; resume_scan: prev = p; /* update lnbd */ if( prev & -2 ) { lnbd.x = x; } } /* end of prev != p */ } /* end of loop on x */ lnbd.x = 0; lnbd.y = y + 1; x = 1; prev = 0; } /* end of loop on y */ exit_func: if( result != 0 ) contour = 0; if( result < 0 ) CV_ERROR_FROM_STATUS( result ); __END__; return contour; }
/* Initializes scanner structure. Prepare image for scanning ( clear borders and convert all pixels to 0-1. */ CV_IMPL CvContourScanner cvStartFindContours( void* _img, CvMemStorage* storage, int header_size, int mode, int method, CvPoint offset ) { int y; int step; CvSize size; uchar *img = 0; CvContourScanner scanner = 0; CvMat stub, *mat = (CvMat*)_img; CV_FUNCNAME( "cvStartFindContours" ); __BEGIN__; if( !storage ) CV_ERROR( CV_StsNullPtr, "" ); CV_CALL( mat = cvGetMat( mat, &stub )); if( !CV_IS_MASK_ARR( mat )) CV_ERROR( CV_StsUnsupportedFormat, "[Start]FindContours support only 8uC1 images" ); size = cvSize( mat->width, mat->height ); step = mat->step; img = (uchar*)(mat->data.ptr); if( method < 0 || method > CV_CHAIN_APPROX_TC89_KCOS ) CV_ERROR_FROM_STATUS( CV_BADRANGE_ERR ); if( header_size < (int) (method == CV_CHAIN_CODE ? sizeof( CvChain ) : sizeof( CvContour ))) CV_ERROR_FROM_STATUS( CV_BADSIZE_ERR ); scanner = (CvContourScanner)cvAlloc( sizeof( *scanner )); if( !scanner ) CV_ERROR_FROM_STATUS( CV_OUTOFMEM_ERR ); memset( scanner, 0, sizeof( *scanner )); scanner->storage1 = scanner->storage2 = storage; scanner->img0 = (char *) img; scanner->img = (char *) (img + step); scanner->img_step = step; scanner->img_size.width = size.width - 1; /* exclude rightest column */ scanner->img_size.height = size.height - 1; /* exclude bottomost row */ scanner->mode = mode; scanner->offset = offset; scanner->pt.x = scanner->pt.y = 1; scanner->lnbd.x = 0; scanner->lnbd.y = 1; scanner->nbd = 2; scanner->mode = (int) mode; scanner->frame_info.contour = &(scanner->frame); scanner->frame_info.is_hole = 1; scanner->frame_info.next = 0; scanner->frame_info.parent = 0; scanner->frame_info.rect = cvRect( 0, 0, size.width, size.height ); scanner->l_cinfo = 0; scanner->subst_flag = 0; scanner->frame.flags = CV_SEQ_FLAG_HOLE; scanner->approx_method2 = scanner->approx_method1 = method; if( method == CV_CHAIN_APPROX_TC89_L1 || method == CV_CHAIN_APPROX_TC89_KCOS ) scanner->approx_method1 = CV_CHAIN_CODE; if( scanner->approx_method1 == CV_CHAIN_CODE ) { scanner->seq_type1 = CV_SEQ_CHAIN_CONTOUR; scanner->header_size1 = scanner->approx_method1 == scanner->approx_method2 ? header_size : sizeof( CvChain ); scanner->elem_size1 = sizeof( char ); } else { scanner->seq_type1 = CV_SEQ_POLYGON; scanner->header_size1 = scanner->approx_method1 == scanner->approx_method2 ? header_size : sizeof( CvContour ); scanner->elem_size1 = sizeof( CvPoint ); } scanner->header_size2 = header_size; if( scanner->approx_method2 == CV_CHAIN_CODE ) { scanner->seq_type2 = scanner->seq_type1; scanner->elem_size2 = scanner->elem_size1; } else { scanner->seq_type2 = CV_SEQ_POLYGON; scanner->elem_size2 = sizeof( CvPoint ); } scanner->seq_type1 = scanner->approx_method1 == CV_CHAIN_CODE ? CV_SEQ_CHAIN_CONTOUR : CV_SEQ_POLYGON; scanner->seq_type2 = scanner->approx_method2 == CV_CHAIN_CODE ? CV_SEQ_CHAIN_CONTOUR : CV_SEQ_POLYGON; cvSaveMemStoragePos( storage, &(scanner->initial_pos) ); if( method > CV_CHAIN_APPROX_SIMPLE ) { scanner->storage1 = cvCreateChildMemStorage( scanner->storage2 ); } if( mode > CV_RETR_LIST ) { scanner->cinfo_storage = cvCreateChildMemStorage( scanner->storage2 ); scanner->cinfo_set = cvCreateSet( 0, sizeof( CvSet ), sizeof( _CvContourInfo ), scanner->cinfo_storage ); if( scanner->cinfo_storage == 0 || scanner->cinfo_set == 0 ) CV_ERROR_FROM_STATUS( CV_OUTOFMEM_ERR ); } /* make zero borders */ memset( img, 0, size.width ); memset( img + step * (size.height - 1), 0, size.width ); for( y = 1, img += step; y < size.height - 1; y++, img += step ) { img[0] = img[size.width - 1] = 0; } /* converts all pixels to 0 or 1 */ cvThreshold( mat, mat, 0, 1, CV_THRESH_BINARY ); CV_CHECK(); __END__; if( cvGetErrStatus() < 0 ) cvFree( &scanner ); return scanner; }