// Function cvUpdateFGDStatModel updates statistical model and returns number of foreground regions // parameters: // curr_frame - current frame from video sequence // p_model - pointer to CvFGDStatModel structure static int CV_CDECL icvUpdateFGDStatModel( IplImage* curr_frame, CvFGDStatModel* model, double ) { int mask_step = model->Ftd->widthStep; CvSeq *first_seq = NULL, *prev_seq = NULL, *seq = NULL; IplImage* prev_frame = model->prev_frame; int region_count = 0; int FG_pixels_count = 0; int deltaC = cvRound(model->params.delta * 256 / model->params.Lc); int deltaCC = cvRound(model->params.delta * 256 / model->params.Lcc); int i, j, k, l; //clear storages cvClearMemStorage(model->storage); cvZero(model->foreground); // From foreground pixel candidates using image differencing // with adaptive thresholding. The algorithm is from: // // Thresholding for Change Detection // Paul L. Rosin 1998 6p // http://www.cis.temple.edu/~latecki/Courses/CIS750-03/Papers/thresh-iccv.pdf // cvChangeDetection( prev_frame, curr_frame, model->Ftd ); cvChangeDetection( model->background, curr_frame, model->Fbd ); for( i = 0; i < model->Ftd->height; i++ ) { for( j = 0; j < model->Ftd->width; j++ ) { if( ((uchar*)model->Fbd->imageData)[i*mask_step+j] || ((uchar*)model->Ftd->imageData)[i*mask_step+j] ) { float Pb = 0; float Pv = 0; float Pvb = 0; CvBGPixelStat* stat = model->pixel_stat + i * model->Ftd->width + j; CvBGPixelCStatTable* ctable = stat->ctable; CvBGPixelCCStatTable* cctable = stat->cctable; uchar* curr_data = (uchar*)(curr_frame->imageData) + i*curr_frame->widthStep + j*3; uchar* prev_data = (uchar*)(prev_frame->imageData) + i*prev_frame->widthStep + j*3; int val = 0; // Is it a motion pixel? if( ((uchar*)model->Ftd->imageData)[i*mask_step+j] ) { if( !stat->is_trained_dyn_model ) { val = 1; } else { // Compare with stored CCt vectors: for( k = 0; PV_CC(k) > model->params.alpha2 && k < model->params.N1cc; k++ ) { if ( abs( V_CC(k,0) - prev_data[0]) <= deltaCC && abs( V_CC(k,1) - prev_data[1]) <= deltaCC && abs( V_CC(k,2) - prev_data[2]) <= deltaCC && abs( V_CC(k,3) - curr_data[0]) <= deltaCC && abs( V_CC(k,4) - curr_data[1]) <= deltaCC && abs( V_CC(k,5) - curr_data[2]) <= deltaCC) { Pv += PV_CC(k); Pvb += PVB_CC(k); } } Pb = stat->Pbcc; if( 2 * Pvb * Pb <= Pv ) val = 1; } } else if( stat->is_trained_st_model ) { // Compare with stored Ct vectors: for( k = 0; PV_C(k) > model->params.alpha2 && k < model->params.N1c; k++ ) { if ( abs( V_C(k,0) - curr_data[0]) <= deltaC && abs( V_C(k,1) - curr_data[1]) <= deltaC && abs( V_C(k,2) - curr_data[2]) <= deltaC ) { Pv += PV_C(k); Pvb += PVB_C(k); } } Pb = stat->Pbc; if( 2 * Pvb * Pb <= Pv ) val = 1; } // Update foreground: ((uchar*)model->foreground->imageData)[i*mask_step+j] = (uchar)(val*255); FG_pixels_count += val; } // end if( change detection... } // for j... } // for i... //end BG/FG classification // Foreground segmentation. // Smooth foreground map: if( model->params.perform_morphing ){ cvMorphologyEx( model->foreground, model->foreground, 0, 0, CV_MOP_OPEN, model->params.perform_morphing ); cvMorphologyEx( model->foreground, model->foreground, 0, 0, CV_MOP_CLOSE, model->params.perform_morphing ); } if( model->params.minArea > 0 || model->params.is_obj_without_holes ){ // Discard under-size foreground regions: // cvFindContours( model->foreground, model->storage, &first_seq, sizeof(CvContour), CV_RETR_LIST ); for( seq = first_seq; seq; seq = seq->h_next ) { CvContour* cnt = (CvContour*)seq; if( cnt->rect.width * cnt->rect.height < model->params.minArea || (model->params.is_obj_without_holes && CV_IS_SEQ_HOLE(seq)) ) { // Delete under-size contour: prev_seq = seq->h_prev; if( prev_seq ) { prev_seq->h_next = seq->h_next; if( seq->h_next ) seq->h_next->h_prev = prev_seq; } else { first_seq = seq->h_next; if( seq->h_next ) seq->h_next->h_prev = NULL; } } else { region_count++; } } model->foreground_regions = first_seq; cvZero(model->foreground); cvDrawContours(model->foreground, first_seq, CV_RGB(0, 0, 255), CV_RGB(0, 0, 255), 10, -1); } else { model->foreground_regions = NULL; } // Check ALL BG update condition: if( ((float)FG_pixels_count/(model->Ftd->width*model->Ftd->height)) > CV_BGFG_FGD_BG_UPDATE_TRESH ) { for( i = 0; i < model->Ftd->height; i++ ) for( j = 0; j < model->Ftd->width; j++ ) { CvBGPixelStat* stat = model->pixel_stat + i * model->Ftd->width + j; stat->is_trained_st_model = stat->is_trained_dyn_model = 1; } } // Update background model: for( i = 0; i < model->Ftd->height; i++ ) { for( j = 0; j < model->Ftd->width; j++ ) { CvBGPixelStat* stat = model->pixel_stat + i * model->Ftd->width + j; CvBGPixelCStatTable* ctable = stat->ctable; CvBGPixelCCStatTable* cctable = stat->cctable; uchar *curr_data = (uchar*)(curr_frame->imageData)+i*curr_frame->widthStep+j*3; uchar *prev_data = (uchar*)(prev_frame->imageData)+i*prev_frame->widthStep+j*3; if( ((uchar*)model->Ftd->imageData)[i*mask_step+j] || !stat->is_trained_dyn_model ) { float alpha = stat->is_trained_dyn_model ? model->params.alpha2 : model->params.alpha3; float diff = 0; int dist, min_dist = 2147483647, indx = -1; //update Pb stat->Pbcc *= (1.f-alpha); if( !((uchar*)model->foreground->imageData)[i*mask_step+j] ) { stat->Pbcc += alpha; } // Find best Vi match: for(k = 0; PV_CC(k) && k < model->params.N2cc; k++ ) { // Exponential decay of memory PV_CC(k) *= (1-alpha); PVB_CC(k) *= (1-alpha); if( PV_CC(k) < MIN_PV ) { PV_CC(k) = 0; PVB_CC(k) = 0; continue; } dist = 0; for( l = 0; l < 3; l++ ) { int val = abs( V_CC(k,l) - prev_data[l] ); if( val > deltaCC ) break; dist += val; val = abs( V_CC(k,l+3) - curr_data[l] ); if( val > deltaCC) break; dist += val; } if( l == 3 && dist < min_dist ) { min_dist = dist; indx = k; } } if( indx < 0 ) { // Replace N2th elem in the table by new feature: indx = model->params.N2cc - 1; PV_CC(indx) = alpha; PVB_CC(indx) = alpha; //udate Vt for( l = 0; l < 3; l++ ) { V_CC(indx,l) = prev_data[l]; V_CC(indx,l+3) = curr_data[l]; } } else { // Update: PV_CC(indx) += alpha; if( !((uchar*)model->foreground->imageData)[i*mask_step+j] ) { PVB_CC(indx) += alpha; } } //re-sort CCt table by Pv for( k = 0; k < indx; k++ ) { if( PV_CC(k) <= PV_CC(indx) ) { //shift elements CvBGPixelCCStatTable tmp1, tmp2 = cctable[indx]; for( l = k; l <= indx; l++ ) { tmp1 = cctable[l]; cctable[l] = tmp2; tmp2 = tmp1; } break; } } float sum1=0, sum2=0; //check "once-off" changes for(k = 0; PV_CC(k) && k < model->params.N1cc; k++ ) { sum1 += PV_CC(k); sum2 += PVB_CC(k); } if( sum1 > model->params.T ) stat->is_trained_dyn_model = 1; diff = sum1 - stat->Pbcc * sum2; // Update stat table: if( diff > model->params.T ) { //printf("once off change at motion mode\n"); //new BG features are discovered for( k = 0; PV_CC(k) && k < model->params.N1cc; k++ ) { PVB_CC(k) = (PV_CC(k)-stat->Pbcc*PVB_CC(k))/(1-stat->Pbcc); } assert(stat->Pbcc<=1 && stat->Pbcc>=0); } } // Handle "stationary" pixel: if( !((uchar*)model->Ftd->imageData)[i*mask_step+j] ) { float alpha = stat->is_trained_st_model ? model->params.alpha2 : model->params.alpha3; float diff = 0; int dist, min_dist = 2147483647, indx = -1; //update Pb stat->Pbc *= (1.f-alpha); if( !((uchar*)model->foreground->imageData)[i*mask_step+j] ) { stat->Pbc += alpha; } //find best Vi match for( k = 0; k < model->params.N2c; k++ ) { // Exponential decay of memory PV_C(k) *= (1-alpha); PVB_C(k) *= (1-alpha); if( PV_C(k) < MIN_PV ) { PV_C(k) = 0; PVB_C(k) = 0; continue; } dist = 0; for( l = 0; l < 3; l++ ) { int val = abs( V_C(k,l) - curr_data[l] ); if( val > deltaC ) break; dist += val; } if( l == 3 && dist < min_dist ) { min_dist = dist; indx = k; } } if( indx < 0 ) {//N2th elem in the table is replaced by a new features indx = model->params.N2c - 1; PV_C(indx) = alpha; PVB_C(indx) = alpha; //udate Vt for( l = 0; l < 3; l++ ) { V_C(indx,l) = curr_data[l]; } } else {//update PV_C(indx) += alpha; if( !((uchar*)model->foreground->imageData)[i*mask_step+j] ) { PVB_C(indx) += alpha; } } //re-sort Ct table by Pv for( k = 0; k < indx; k++ ) { if( PV_C(k) <= PV_C(indx) ) { //shift elements CvBGPixelCStatTable tmp1, tmp2 = ctable[indx]; for( l = k; l <= indx; l++ ) { tmp1 = ctable[l]; ctable[l] = tmp2; tmp2 = tmp1; } break; } } // Check "once-off" changes: float sum1=0, sum2=0; for( k = 0; PV_C(k) && k < model->params.N1c; k++ ) { sum1 += PV_C(k); sum2 += PVB_C(k); } diff = sum1 - stat->Pbc * sum2; if( sum1 > model->params.T ) stat->is_trained_st_model = 1; // Update stat table: if( diff > model->params.T ) { //printf("once off change at stat mode\n"); //new BG features are discovered for( k = 0; PV_C(k) && k < model->params.N1c; k++ ) { PVB_C(k) = (PV_C(k)-stat->Pbc*PVB_C(k))/(1-stat->Pbc); } stat->Pbc = 1 - stat->Pbc; } } // if !(change detection) at pixel (i,j) // Update the reference BG image: if( !((uchar*)model->foreground->imageData)[i*mask_step+j]) { uchar* ptr = ((uchar*)model->background->imageData) + i*model->background->widthStep+j*3; if( !((uchar*)model->Ftd->imageData)[i*mask_step+j] && !((uchar*)model->Fbd->imageData)[i*mask_step+j] ) { // Apply IIR filter: for( l = 0; l < 3; l++ ) { int a = cvRound(ptr[l]*(1 - model->params.alpha1) + model->params.alpha1*curr_data[l]); ptr[l] = (uchar)a; //((uchar*)model->background->imageData)[i*model->background->widthStep+j*3+l]*=(1 - model->params.alpha1); //((uchar*)model->background->imageData)[i*model->background->widthStep+j*3+l] += model->params.alpha1*curr_data[l]; } } else { // Background change detected: for( l = 0; l < 3; l++ ) { //((uchar*)model->background->imageData)[i*model->background->widthStep+j*3+l] = curr_data[l]; ptr[l] = curr_data[l]; } } } } // j } // i // Keep previous frame: cvCopy( curr_frame, model->prev_frame ); return region_count; }
static CvStatus icvFetchContourEx( char* ptr, int step, CvPoint pt, CvSeq* contour, int _method, int nbd, CvRect* _rect ) { int deltas[16]; CvSeqWriter writer; char *i0 = ptr, *i1, *i3, *i4; CvRect rect; int prev_s = -1, s, s_end; int method = _method - 1; assert( (unsigned) _method <= CV_CHAIN_APPROX_SIMPLE ); assert( 1 < nbd && nbd < 128 ); /* initialize local state */ CV_INIT_3X3_DELTAS( deltas, step, 1 ); memcpy( deltas + 8, deltas, 8 * sizeof( deltas[0] )); /* initialize writer */ cvStartAppendToSeq( contour, &writer ); if( method < 0 ) ((CvChain *)contour)->origin = pt; rect.x = rect.width = pt.x; rect.y = rect.height = pt.y; s_end = s = CV_IS_SEQ_HOLE( contour ) ? 0 : 4; do { s = (s - 1) & 7; i1 = i0 + deltas[s]; if( *i1 != 0 ) break; } while( s != s_end ); if( s == s_end ) /* single pixel domain */ { *i0 = (char) (nbd | 0x80); if( method >= 0 ) { CV_WRITE_SEQ_ELEM( pt, writer ); } } else { i3 = i0; prev_s = s ^ 4; /* follow border */ for( ;; ) { s_end = s; for( ;; ) { i4 = i3 + deltas[++s]; if( *i4 != 0 ) break; } s &= 7; /* check "right" bound */ if( (unsigned) (s - 1) < (unsigned) s_end ) { *i3 = (char) (nbd | 0x80); } else if( *i3 == 1 ) { *i3 = (char) nbd; } if( method < 0 ) { char _s = (char) s; CV_WRITE_SEQ_ELEM( _s, writer ); } else if( s != prev_s || method == 0 ) { CV_WRITE_SEQ_ELEM( pt, writer ); } if( s != prev_s ) { /* update bounds */ if( pt.x < rect.x ) rect.x = pt.x; else if( pt.x > rect.width ) rect.width = pt.x; if( pt.y < rect.y ) rect.y = pt.y; else if( pt.y > rect.height ) rect.height = pt.y; } prev_s = s; pt.x += icvCodeDeltas[s].x; pt.y += icvCodeDeltas[s].y; if( i4 == i0 && i3 == i1 ) break; i3 = i4; s = (s + 4) & 7; } /* end of border following loop */ } rect.width -= rect.x - 1; rect.height -= rect.y - 1; cvEndWriteSeq( &writer ); if( _method != CV_CHAIN_CODE ) ((CvContour*)contour)->rect = rect; assert( writer.seq->total == 0 && writer.seq->first == 0 || writer.seq->total > writer.seq->first->count || (writer.seq->first->prev == writer.seq->first && writer.seq->first->next == writer.seq->first) ); if( _rect ) *_rect = rect; return CV_OK; }
IplImage * find_macbeth( const char *img ) { IplImage * macbeth_img = cvLoadImage( img, CV_LOAD_IMAGE_ANYCOLOR|CV_LOAD_IMAGE_ANYDEPTH ); IplImage * macbeth_original = cvCreateImage( cvSize(macbeth_img->width, macbeth_img->height), macbeth_img->depth, macbeth_img->nChannels ); cvCopy(macbeth_img, macbeth_original); IplImage * macbeth_split[3]; IplImage * macbeth_split_thresh[3]; for(int i = 0; i < 3; i++) { macbeth_split[i] = cvCreateImage( cvSize(macbeth_img->width, macbeth_img->height), macbeth_img->depth, 1 ); macbeth_split_thresh[i] = cvCreateImage( cvSize(macbeth_img->width, macbeth_img->height), macbeth_img->depth, 1 ); } cvSplit(macbeth_img, macbeth_split[0], macbeth_split[1], macbeth_split[2], NULL); if( macbeth_img ) { int adaptive_method = CV_ADAPTIVE_THRESH_MEAN_C; int threshold_type = CV_THRESH_BINARY_INV; int block_size = cvRound( MIN(macbeth_img->width,macbeth_img->height)*0.02)|1; fprintf(stderr,"Using %d as block size\n", block_size); double offset = 6; // do an adaptive threshold on each channel for(int i = 0; i < 3; i++) { cvAdaptiveThreshold(macbeth_split[i], macbeth_split_thresh[i], 255, adaptive_method, threshold_type, block_size, offset); } IplImage * adaptive = cvCreateImage( cvSize(macbeth_img->width, macbeth_img->height), IPL_DEPTH_8U, 1 ); // OR the binary threshold results together cvOr(macbeth_split_thresh[0],macbeth_split_thresh[1],adaptive); cvOr(macbeth_split_thresh[2],adaptive,adaptive); for(int i = 0; i < 3; i++) { cvReleaseImage( &(macbeth_split[i]) ); cvReleaseImage( &(macbeth_split_thresh[i]) ); } int element_size = (block_size/10)+2; fprintf(stderr,"Using %d as element size\n", element_size); // do an opening on the threshold image IplConvKernel * element = cvCreateStructuringElementEx(element_size,element_size,element_size/2,element_size/2,CV_SHAPE_RECT); cvMorphologyEx(adaptive,adaptive,NULL,element,CV_MOP_OPEN); cvReleaseStructuringElement(&element); CvMemStorage* storage = cvCreateMemStorage(0); CvSeq* initial_quads = cvCreateSeq( 0, sizeof(*initial_quads), sizeof(void*), storage ); CvSeq* initial_boxes = cvCreateSeq( 0, sizeof(*initial_boxes), sizeof(CvBox2D), storage ); // find contours in the threshold image CvSeq * contours = NULL; cvFindContours(adaptive,storage,&contours); int min_size = (macbeth_img->width*macbeth_img->height)/ (MACBETH_SQUARES*100); if(contours) { int count = 0; for( CvSeq* c = contours; c != NULL; c = c->h_next) { CvRect rect = ((CvContour*)c)->rect; // only interested in contours with these restrictions if(CV_IS_SEQ_HOLE(c) && rect.width*rect.height >= min_size) { // only interested in quad-like contours CvSeq * quad_contour = find_quad(c, storage, min_size); if(quad_contour) { cvSeqPush( initial_quads, &quad_contour ); count++; rect = ((CvContour*)quad_contour)->rect; CvScalar average = contour_average((CvContour*)quad_contour, macbeth_img); CvBox2D box = cvMinAreaRect2(quad_contour,storage); cvSeqPush( initial_boxes, &box ); // fprintf(stderr,"Center: %f %f\n", box.center.x, box.center.y); double min_distance = MAX_RGB_DISTANCE; CvPoint closest_color_idx = cvPoint(-1,-1); for(int y = 0; y < MACBETH_HEIGHT; y++) { for(int x = 0; x < MACBETH_WIDTH; x++) { double distance = euclidean_distance_lab(average,colorchecker_srgb[y][x]); if(distance < min_distance) { closest_color_idx.x = x; closest_color_idx.y = y; min_distance = distance; } } } CvScalar closest_color = colorchecker_srgb[closest_color_idx.y][closest_color_idx.x]; // fprintf(stderr,"Closest color: %f %f %f (%d %d)\n", // closest_color.val[2], // closest_color.val[1], // closest_color.val[0], // closest_color_idx.x, // closest_color_idx.y // ); // cvDrawContours( // macbeth_img, // quad_contour, // cvScalar(255,0,0), // cvScalar(0,0,255), // 0, // element_size // ); // cvCircle( // macbeth_img, // cvPointFrom32f(box.center), // element_size*6, // cvScalarAll(255), // -1 // ); // cvCircle( // macbeth_img, // cvPointFrom32f(box.center), // element_size*6, // closest_color, // -1 // ); // cvCircle( // macbeth_img, // cvPointFrom32f(box.center), // element_size*4, // average, // -1 // ); // CvRect rect = contained_rectangle(box); // cvRectangle( // macbeth_img, // cvPoint(rect.x,rect.y), // cvPoint(rect.x+rect.width, rect.y+rect.height), // cvScalarAll(0), // element_size // ); } } } ColorChecker found_colorchecker; fprintf(stderr,"%d initial quads found", initial_quads->total); if(count > MACBETH_SQUARES) { fprintf(stderr," (probably a Passport)\n"); CvMat* points = cvCreateMat( initial_quads->total , 1, CV_32FC2 ); CvMat* clusters = cvCreateMat( initial_quads->total , 1, CV_32SC1 ); CvSeq* partitioned_quads[2]; CvSeq* partitioned_boxes[2]; for(int i = 0; i < 2; i++) { partitioned_quads[i] = cvCreateSeq( 0, sizeof(**partitioned_quads), sizeof(void*), storage ); partitioned_boxes[i] = cvCreateSeq( 0, sizeof(**partitioned_boxes), sizeof(CvBox2D), storage ); } // set up the points sequence for cvKMeans2, using the box centers for(int i = 0; i < initial_quads->total; i++) { CvBox2D box = (*(CvBox2D*)cvGetSeqElem(initial_boxes, i)); cvSet1D(points, i, cvScalar(box.center.x,box.center.y)); } // partition into two clusters: passport and colorchecker cvKMeans2( points, 2, clusters, cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 1.0 ) ); for(int i = 0; i < initial_quads->total; i++) { CvPoint2D32f pt = ((CvPoint2D32f*)points->data.fl)[i]; int cluster_idx = clusters->data.i[i]; cvSeqPush( partitioned_quads[cluster_idx], cvGetSeqElem(initial_quads, i) ); cvSeqPush( partitioned_boxes[cluster_idx], cvGetSeqElem(initial_boxes, i) ); // cvCircle( // macbeth_img, // cvPointFrom32f(pt), // element_size*2, // cvScalar(255*cluster_idx,0,255-(255*cluster_idx)), // -1 // ); } ColorChecker partitioned_checkers[2]; // check each of the two partitioned sets for the best colorchecker for(int i = 0; i < 2; i++) { partitioned_checkers[i] = find_colorchecker(partitioned_quads[i], partitioned_boxes[i], storage, macbeth_img, macbeth_original); } // use the colorchecker with the lowest error found_colorchecker = partitioned_checkers[0].error < partitioned_checkers[1].error ? partitioned_checkers[0] : partitioned_checkers[1]; cvReleaseMat( &points ); cvReleaseMat( &clusters ); } else { // just one colorchecker to test fprintf(stderr,"\n"); found_colorchecker = find_colorchecker(initial_quads, initial_boxes, storage, macbeth_img, macbeth_original); } // render the found colorchecker draw_colorchecker(found_colorchecker.values,found_colorchecker.points,macbeth_img,found_colorchecker.size); // print out the colorchecker info for(int y = 0; y < MACBETH_HEIGHT; y++) { for(int x = 0; x < MACBETH_WIDTH; x++) { CvScalar this_value = cvGet2D(found_colorchecker.values,y,x); CvScalar this_point = cvGet2D(found_colorchecker.points,y,x); printf("%.0f,%.0f,%.0f,%.0f,%.0f\n", this_point.val[0],this_point.val[1], this_value.val[2],this_value.val[1],this_value.val[0]); } } printf("%0.f\n%f\n",found_colorchecker.size,found_colorchecker.error); } cvReleaseMemStorage( &storage ); if( macbeth_original ) cvReleaseImage( &macbeth_original ); if( adaptive ) cvReleaseImage( &adaptive ); return macbeth_img; } if( macbeth_img ) cvReleaseImage( &macbeth_img ); return NULL; }
/* * If curve is hole(inner contour), return true. Otherwise return false. */ VALUE rb_hole_q(VALUE self) { return CV_IS_SEQ_HOLE(CVSEQ(self)) ? Qtrue : Qfalse; }