// RegionDetector.cpp: implementation of the CRegionDetector class. // ////////////////////////////////////////////////////////////////////// #include "precomp.h" #pragma hdrstop #include "regionde.h" inline ULONG Intensity(ULONG value); inline ULONG DifferenceFromGray(ULONG value); inline UCHAR Difference(UCHAR a, UCHAR b); inline ULONG Difference(ULONG a, ULONG b); int inline MAX(int a, int b); int inline MIN(int a, int b); // helper functions // sum of RGB vals inline ULONG Intensity(ULONG value) { return(value&0xff)+((value&0xff00)>>8)+((value&0xff0000)>>16); } // shadows are gray... if you aint gray... you aint a shadow inline ULONG DifferenceFromGray(ULONG value) { UCHAR g,b;//,b; // r=(UCHAR)(value& 0x0000ff); g=(UCHAR)((value& 0x00ff00)>>8); b=(UCHAR)((value& 0xff0000)>>16); // use this instead of the complete formula (uncomment the commented out code for the complete formula) // allow yellow scanner backgrounds return(ULONG)(Difference(b,g));//+Difference(r,g)+Difference(g,b)); } // we should make a Difference Template to clean up this code inline UCHAR Difference(UCHAR a, UCHAR b) { if (a>b) return(a-b); else return(b-a); } inline ULONG Difference(ULONG a, ULONG b) { if (a>b) return(a-b); else return(b-a); } inline LONG Difference(LONG a, LONG b) { if (a>b) return(a-b); else return(b-a); } int inline MAX(int a, int b) { if (a>b) return(a); return(b); } int inline MIN(int a, int b) { if (am_numRects;i++) { m_pRegions->m_pRects[i].left=m_pRegions->m_pRects[i].left*m_resampleFactor+m_resampleFactor/2; m_pRegions->m_pRects[i].right=m_pRegions->m_pRects[i].right*m_resampleFactor+m_resampleFactor/2; m_pRegions->m_pRects[i].top=m_pRegions->m_pRects[i].top*m_resampleFactor+m_resampleFactor/2; m_pRegions->m_pRects[i].bottom=m_pRegions->m_pRects[i].bottom*m_resampleFactor+m_resampleFactor/2; } return(true); } return(false); } // simplified Region detection code for single region detection // Faster and about as accurate // FindSingleRegion encapsulates a subset of FindRegions. at the moment, for code documentation of FindSingleRegion, see FindRegions bool CRegionDetector::FindSingleRegion() { // cast the pointers // S = Scan, B = blank background, V = virtual screen... new image int x,y; int a,b; // loop vars used for merging regions int border; // for aggregation on a rectangle by rectangle basis ULONG position; int numChunks; bool unionOperationInLastPass; ULONG* pImagePixels; ULONG* pEdgePixels; int requiredPixels; // if the bitmap is too large we can use halfsize to resample it down to a more reasonable size... // on a fast proccessor, current performance tests show that for most preview images this won't be needed // but if the user scans an image at 300 dpi, this will come in handy. // m_pScanBlurred->Despeckle(); // two despeckles has a greater effect than one // for FingSingleRegion we plan on doing one resample before image proccessing // with an expected image size of 200x300 pixels which greatly reduces the proccessor load m_resampleFactor=1; while (m_pScan->m_nBitmapWidth>GOALX || m_pScan->m_nBitmapHeight>GOALY) { m_pScan->HalfSize(); m_resampleFactor*=2; } m_pScan->Invert(); // filters operate on inverted images m_pScan->CorrectBrightness(); requiredPixels=m_pScan->m_nBitmapWidth*m_pScan->m_nBitmapHeight/256/10; if (requiredPixels==0) requiredPixels=1; // special case for a particularly small preview scan m_pScan->MaxContrast(requiredPixels); // spread the images color spectrum out... concept: balance color spectra between different scans m_pScanBlurred->CreateBlurBitmap(m_pScan); // sets scanBlurred to be equal to a blurred version of m_pScan m_pScanDoubleBlurred->CreateBlurBitmap(m_pScanBlurred); m_pScanTripleBlurred->CreateBlurBitmap(m_pScanDoubleBlurred); // sets scanBlurred to be equal to a blurred version of m_pScan m_pScanHorizontalBlurred->CreateHorizontalBlurBitmap(m_pScan); m_pScanVerticalBlurred->CreateVerticalBlurBitmap(m_pScan); m_pScanEdges->CreateDifferenceBitmap(m_pScan,m_pScanBlurred); // think about how you create an edge bitmap and you will understand m_pScanDoubleEdges->CreateDifferenceBitmap(m_pScanBlurred,m_pScanDoubleBlurred); // we will get a huge accuracy boost from this simple step m_pScanTripleEdges->CreateDifferenceBitmap(m_pScanDoubleBlurred,m_pScanTripleBlurred); // we will get a huge accuracy boost from this simple step m_pScanHorizontalEdges->CreateDifferenceBitmap(m_pScan,m_pScanHorizontalBlurred); // assuming the user was kind enough to place the image right side up m_pScanVerticalEdges->CreateDifferenceBitmap(m_pScan,m_pScanVerticalBlurred); // we will get a huge accuracy boost from this simple step // free memory as soon as it isn't needed if (m_pScanVerticalBlurred!=NULL) { delete m_pScanVerticalBlurred; m_pScanVerticalBlurred=NULL; } if (m_pScanHorizontalBlurred!=NULL) { delete m_pScanHorizontalBlurred; m_pScanHorizontalBlurred=NULL; } if (m_pScanDoubleBlurred!=NULL) { delete m_pScanDoubleBlurred; m_pScanDoubleBlurred=NULL; } if (m_pScanTripleBlurred!=NULL) { delete m_pScanTripleBlurred; m_pScanTripleBlurred=NULL; } // these 5 calls to killShadows make up the real meat of the region detection work done by the program. // KillShadows now performs more tasks than simply killing shadows. Killshadows also enhances edges // and removes background colors // the doubleBlur and tripleBlur edge maps are needed so that we can distinguish between an uneven background scanner color and a real image. // see KillShadows for more documentation. // m_pScanBlurred is the bitmap we will use to determine where regions are // these calls to KillShadows act to enhance pixels which are part of regions and inhibit pixels which are not part of regions m_pScanBlurred->KillShadows(m_pScanVerticalEdges, MAXSHADOWSTART,MAXSHADOWPIXEL+1,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanHorizontalEdges, MAXSHADOWSTART,MAXSHADOWPIXEL+1,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanEdges, MAXSHADOWSTART,MAXSHADOWPIXEL-1,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,true); m_pScanBlurred->KillShadows(m_pScanDoubleEdges,MAXSHADOWSTART,MAXSHADOWPIXEL+2,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,true); m_pScanBlurred->KillShadows(m_pScanTripleEdges, MAXSHADOWSTART,MAXSHADOWPIXEL+1,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,true); // RemoveBlackBorder removes questionable pixels around the outside edge of an image. // RemoveBlackBorder has only very limited utility for region detection on images that were not aquired from a scanner m_pScan->RemoveBlackBorder(MIN_BLACK_SCANNER_EDGE_CHAN_VALUE,m_pScanBlurred,m_pScan); // despeckle removes small clumps of pixels which are probably stray static m_pScanBlurred->Despeckle(); // two despeckles has a greater effect than one m_pScanBlurred->Despeckle(); // two despeckles has a greater effect than one // pMap will hold information on which region each pixel on the screen is part of int *pMap=new int[m_pScanBlurred->m_nBitmapHeight*m_pScanBlurred->m_nBitmapWidth]; if (pMap) { numChunks=m_pScanBlurred->FindChunks(pMap); // maps chunks on m_pScanBlurred to pMap if (m_pRegions!=NULL) delete m_pRegions; m_pRegions = new CRegionList(numChunks); // create a CRegionList to map the chunks onto. if (m_pRegions) { m_pRegions->m_nBitmapWidth=m_pScan->m_nBitmapWidth; m_pRegions->m_nBitmapHeight=m_pScan->m_nBitmapHeight; // now turn region map into region rectangles // it could be argued that this routine should be placed in C32BitDibWrapper // but we don't want to make C32BitDibWrapper encompas too much functionality which // would only be useful for imagedetection pImagePixels=(ULONG *)(m_pScanBlurred->m_pBits); // we want to use 32 bit chunks instead of 8 bit chunks pEdgePixels=(ULONG *)(m_pScanEdges->m_pBits); // add all the bitmap pixels to the m_pRegions list position=0; for (y=0;ym_nBitmapHeight;y++) { for (x=0;xm_nBitmapWidth;x++) { if (pMap[position]>0) { m_pRegions->AddPixel(pMap[position]-1, pImagePixels[position],pEdgePixels[position], x, y); // pMap values start at 1, let region values start at 0 } // we start pMap at 1 so that 0 can indicate a pixel that is not assigned to any region position++; // we may want to make pMap start at 0 at some later date } } m_pRegions->m_numRects=numChunks; m_pRegions->m_validRects=numChunks; // free bitmaps as soon as they will no longer be used if (m_pScanHorizontalEdges!=NULL) { delete m_pScanHorizontalEdges; m_pScanHorizontalEdges=NULL; } if (m_pScanVerticalEdges!=NULL) { delete m_pScanVerticalEdges; m_pScanVerticalEdges=NULL; } // merge together regions // this routine is pretty much a waste when performing single region detection // the only advantage of it over simply calling unionRegions is that // we can merge together small close together regions, but kill small regions that are far from other regions (probably static) for (border=0;borderm_numRects;a++) // overkill, we could be cleverer about when we check for valid regions { m_pRegions->checkIfValidRegion(a, border); // sets m_valid params } m_pRegions->CompactDown(m_pRegions->m_validRects); // remove all invalid rects to save search time while (unionOperationInLastPass==true) { unionOperationInLastPass=false; for (a=0;am_numRects;a++) { if (m_pRegions->m_valid[a]==true) { for (b=a+1;bm_numRects;b++) { if (m_pRegions->m_valid[b]==true) { if (m_pRegions->CheckIntersect(a,b,border)==true) { m_pRegions->UnionRegions(a,b); m_pRegions->checkIfValidRegion(a, border); // in this context, checkvalid should have the effect of culling regions which are probably only stray dots unionOperationInLastPass=true; } } } } } } } // m_pScanBlurred->ColorChunks(pMap); // for debugging purposes... so we know where exactly chunks are m_pRegions->CompactDown(m_pRegions->m_validRects); // remove all invalid rects to save search time } delete[] pMap; } return(TRUE); } // detect regions // makes heavy use of C32BitWrapper helper functions // WARNING: this function has not been updated to include the latest changes // to compensate for poor image quality // int CRegionDetector::FindRegions() { // cast the pointers // S = Scan, B = blank background, V = virtual screen... new image int x,y; int a,b; int i; bool done, weird; char* pWall; // 2d array keeping track of which regions have walls between them and other regions // wall vals... TRUE, FALSE, UNKNOWN int border; // for aggregation on a rectangle by rectangle basis ULONG position; int numChunks; bool unionOperationInLastPass; ULONG* pImagePixels; ULONG* pEdgePixels; int requiredPixels; // if the bitmap is too large we can use halfsize to resample it down to a more reasonable size... // on a fast proccessor, current performance tests show that for most preview images this won't be needed // but if the user scans an image at 300 dpi, this will come in handy. while (m_pScan->m_nBitmapWidth>GOALX || m_pScan->m_nBitmapHeight>GOALY) { m_pScan->HalfSize(); } m_pScanBlurred->CreateBlurBitmap(m_pScan); // sets scanBlurred to be equal to a blurred version of m_pScan m_pScanDoubleBlurred->CreateBlurBitmap(m_pScanBlurred); // sets scanBlurred to be equal to a blurred version of m_pScan m_pScanTripleBlurred->CreateBlurBitmap(m_pScanDoubleBlurred); // sets scanBlurred to be equal to a blurred version of m_pScan m_pScanHorizontalBlurred->CreateHorizontalBlurBitmap(m_pScan); m_pScanVerticalBlurred->CreateVerticalBlurBitmap(m_pScan); m_pScanDoubleHorizontalBlurred->CreateHorizontalBlurBitmap(m_pScanHorizontalBlurred); m_pScanDoubleVerticalBlurred->CreateVerticalBlurBitmap(m_pScanVerticalBlurred); m_pScanEdges->CreateDifferenceBitmap(m_pScan,m_pScanBlurred); // think about how you create an edge bitmap and you will understand m_pScanDoubleEdges->CreateDifferenceBitmap(m_pScanBlurred,m_pScanDoubleBlurred); // we will get a huge accuracy boost from this simple step m_pScanTripleEdges->CreateDifferenceBitmap(m_pScanDoubleBlurred,m_pScanTripleBlurred); // we will get a huge accuracy boost from this simple step m_pScanHorizontalEdges->CreateDifferenceBitmap(m_pScan,m_pScanHorizontalBlurred); // assuming the user was kind enough to place the image right side up m_pScanVerticalEdges->CreateDifferenceBitmap(m_pScan,m_pScanVerticalBlurred); // we will get a huge accuracy boost from this simple step m_pScanDoubleHorizontalEdges->CreateDifferenceBitmap(m_pScanHorizontalBlurred,m_pScanDoubleHorizontalBlurred); // assuming the user was kind enough to place the image right side up m_pScanDoubleVerticalEdges->CreateDifferenceBitmap(m_pScanVerticalBlurred,m_pScanDoubleVerticalBlurred); // we will get a huge accuracy boost from this simple step m_pScanBlurred->Invert(); // filters operate on inverted images requiredPixels=m_pScanBlurred->m_nBitmapWidth*m_pScanBlurred->m_nBitmapHeight/256/10; if (requiredPixels==0) requiredPixels=1; // special case for a particularly small preview scan m_pScanBlurred->CorrectBrightness(); m_pScanBlurred->MaxContrast(requiredPixels); // free memory as soon as it isn't needed if (m_pScanVerticalBlurred!=NULL) { delete m_pScanVerticalBlurred; m_pScanVerticalBlurred=NULL; } if (m_pScanHorizontalBlurred!=NULL) { delete m_pScanHorizontalBlurred; m_pScanHorizontalBlurred=NULL; } if (m_pScanDoubleBlurred!=NULL) { delete m_pScanDoubleBlurred; m_pScanDoubleBlurred=NULL; } if (m_pScanTripleBlurred!=NULL) { delete m_pScanTripleBlurred; m_pScanTripleBlurred=NULL; } m_pScanWithShadows = new C32BitDibWrapper(m_pScanBlurred); // copy scanBlurred // eliminate shadows m_pScanBlurred->KillShadows(m_pScanTripleEdges, MAXSHADOWSTART,MAXSHADOWPIXEL-2,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanDoubleEdges,MAXSHADOWSTART,MAXSHADOWPIXEL-1,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanEdges, MAXSHADOWSTART,MAXSHADOWPIXEL+1,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanHorizontalEdges, MAXSHADOWSTART,MAXSHADOWPIXEL,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanVerticalEdges, MAXSHADOWSTART,MAXSHADOWPIXEL,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); // compensate for background color // the average background color pixel will probably have a smaller edge factor than the average shadow, // but will have a larger difference from grey // hence to eliminate background color we ignore difference from grey... as accomplished by the (256*3) term m_pScanBlurred->KillShadows(m_pScanTripleEdges, 256*3,MAXSHADOWPIXEL-2,256*3,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanDoubleEdges,256*3,MAXSHADOWPIXEL-2,256*3,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanEdges, 256*3,MAXSHADOWPIXEL-2,256*3,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanHorizontalEdges, 256*3,MAXSHADOWPIXEL-2,256*3,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanVerticalEdges, 256*3,MAXSHADOWPIXEL-2,256*3,NOT_SHADOW_INTENSITY,false); // pure economics... edge pixels are much more likely to be junk // so economically, its worth it to risk // killing good edge pixels to get rid of the bad m_pScanBlurred->EdgeDespeckle(); m_pScanBlurred->Despeckle(); // two despeckles has a greater effect than one m_pScanBlurred->Despeckle(); // two despeckles has a greater effect than one // prepare to find chunks int *pMap=new int[m_pScanBlurred->m_nBitmapHeight*m_pScanBlurred->m_nBitmapWidth]; if (pMap) { done=false; weird=false; while (done==false) // we may have to repeat FindChunks if we find that we did not eliminate enough pixels and ended up with all pixels being determined to be part of the same region { done=true; numChunks=m_pScanBlurred->FindChunks(pMap); // fills the pMap array with chunks if (m_pRegions!=NULL) delete m_pRegions; m_pRegions = new CRegionList(numChunks); if (m_pRegions) { m_pRegions->m_nBitmapWidth=m_pScan->m_nBitmapWidth; m_pRegions->m_nBitmapHeight=m_pScan->m_nBitmapHeight; // now turn the pMap region map into region rectangles // it could be argued that this routine should be placed in C32BitDibWrapper // but we don't want to make C32BitDibWrapper encompas too much functionality which // clearly is directly connected with imagedetection pImagePixels=(ULONG *)(m_pScanBlurred->m_pBits); // we want to use 32 bit chunks instead of 8 bit chunks pEdgePixels=(ULONG *)(m_pScanEdges->m_pBits); position=0; for (y=0;ym_nBitmapHeight;y++) { for (x=0;xm_nBitmapWidth;x++) { if (pMap[position]>0) { m_pRegions->AddPixel(pMap[position]-1, pImagePixels[position],pEdgePixels[position], x, y); // pMap values start at 1, let region values start at 0 } // we start pMap at 1 so that 0 can indicate a pixel that is not assigned to any region position++; // we may want to make pMap start at 0 at some later date } } m_pRegions->m_numRects=numChunks; m_pRegions->m_validRects=numChunks; // check for invalid regions for (a=0;am_numRects;a++) { m_pRegions->checkIfValidRegion(a); // sets m_valid params m_pRegions->m_backgroundColorPixels[a]=m_pScan->PixelsBelowThreshold(m_pScanBlurred,m_pScanEdges,m_pRegions->m_pRects[a]); m_pRegions->RegionType(a); // clasify regions... text or photo } m_pRegions->CompactDown(m_pRegions->m_validRects); if (m_pRegions->m_validRects==0) break; // if there aren't any regions, no sense in proceeding unionOperationInLastPass=true; while (unionOperationInLastPass==true) { unionOperationInLastPass=false; for (a=0;am_numRects;a++) { if (m_pRegions->m_valid[a]==true) { for (b=a+1;bm_numRects;b++) { if (m_pRegions->m_valid[b]==true) { // we are paranoid... we repeatedly check if two regions intersect each other. if (m_pRegions->CheckIntersect(a,b,0)==true) { m_pRegions->UnionRegions(a,b); m_pRegions->checkIfValidRegion(a, 0); // in this context, checkvalid should have the effect of culling regions which are probably only stray dots m_pRegions->RegionType(a); // figure out what type of region the combined region should be unionOperationInLastPass=true; } } } } } } if (weird==false && ((m_pRegions->m_pRects[0].right-m_pRegions->m_pRects[0].left) *(m_pRegions->m_pRects[0].bottom-m_pRegions->m_pRects[0].top)) > ((m_pScanBlurred->m_nBitmapWidth-DESPECKLE_BORDER_WIDTH*2)*(m_pScanBlurred->m_nBitmapWidth-DESPECKLE_BORDER_WIDTH*2))) { weird=true; done=false; // you better not be grey or you are in trouble // some seriously nasty shadow elimination // we will probably eliminate too many good pixels // but at least the poor user will get something more than just finding that the whole screen was selected m_pScanBlurred->KillShadows(m_pScanTripleEdges, 256,MAXSHADOWPIXEL+4,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanDoubleEdges,256,MAXSHADOWPIXEL+4,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanEdges, 256,MAXSHADOWPIXEL+5,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanHorizontalEdges, 256,MAXSHADOWPIXEL+4,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanVerticalEdges, 256,MAXSHADOWPIXEL+4,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanDoubleHorizontalEdges, MAXSHADOWSTART,MAXSHADOWPIXEL+4,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->KillShadows(m_pScanDoubleVerticalEdges, MAXSHADOWSTART,MAXSHADOWPIXEL+4,MAX_DIFFERENCE_FROM_GRAY,NOT_SHADOW_INTENSITY,false); m_pScanBlurred->EdgeDespeckle(); m_pScanBlurred->Despeckle(); // two despeckles has a greater effect than one // m_pScanBlurred->EdgeDespeckle(); m_pScanBlurred->Despeckle(); // two despeckles has a greater effect than one } } } if (m_pRegions) { m_pRegions->CompactDown(m_pRegions->m_validRects); // compact down the CRegionList so that it nolonger includes invalidated regions // we store an array indicating which pairs of regions have walls between them // used for unioning together large text regions... for example, scanning // in two pages with a shadow between them pWall=new char[m_pRegions->m_numRects*m_pRegions->m_numRects]; if (pWall) { for (a=0;am_numRects;a++) { if (m_pRegions->m_valid[a]==true) { for (b=a+1;bm_numRects;b++) { if (m_pRegions->m_valid[b]==true) { pWall[a*m_pRegions->m_numRects+b]=UNKNOWN; } } } } // key ideas. we need to (potentially) merge a large number of fragmented text regions and we need to avoid merging large photo regions // we also want to keep memory usage within reason...so we should delete stuff after we know we will nolonger use it. if (m_pScanHorizontalEdges!=NULL) { delete m_pScanHorizontalEdges; m_pScanHorizontalEdges=NULL; } if (m_pScanVerticalEdges!=NULL) { delete m_pScanVerticalEdges; m_pScanVerticalEdges=NULL; } for (border=0;borderm_numRects;a++) { if (m_pRegions->m_valid[a]==true) { for (b=a+1;bm_numRects;b++) { if (m_pRegions->m_valid[b]==true) { // we are paranoid... we repeatedly check if two regions intersect each other. if (m_pRegions->CheckIntersect(a,b,0)==true) { m_pRegions->UnionRegions(a,b); m_pRegions->checkIfValidRegion(a, 0); // in this context, checkvalid should have the effect of culling regions which are probably only stray dots m_pRegions->RegionType(a); // figure out what type of region the combined region should be for (i=a+1;im_numRects;i++) if (m_pRegions->m_valid[i]==TRUE) pWall[a*m_pRegions->m_numRects+i]=UNKNOWN; for (i=0;im_valid[i]==TRUE) pWall[i*m_pRegions->m_numRects+a]=UNKNOWN; unionOperationInLastPass=true; } // now the complex part. check for intersections after growing borders around regions // but only if we don't have two photo regions if (MERGE_REGIONS) { if ((m_pRegions->m_type[a]&TEXT_REGION && m_pRegions->m_type[b]&TEXT_REGION) ||(((m_pRegions->m_type[a]|m_pRegions->m_type[b])&MERGABLE_WITH_PHOTOGRAPH)&&borderSize(a)Size(b)CheckIntersect(a,b,border)==true) { if (borderUnionRegions(a,b); m_pRegions->checkIfValidRegion(a, border); // in this context, checkvalid should have the effect of culling regions which are probably only stray dots m_pRegions->RegionType(a); // figure out what type of region the combined region should be for (i=a+1;im_numRects;i++) if (m_pRegions->m_valid[i]==TRUE) pWall[a*m_pRegions->m_numRects+i]=UNKNOWN; for (i=0;im_valid[i]==TRUE) pWall[i*m_pRegions->m_numRects+a]=UNKNOWN; unionOperationInLastPass=true; } else { if (pWall[a*m_pRegions->m_numRects+b]==UNKNOWN) pWall[a*m_pRegions->m_numRects+b]=CollisionDetection(m_pRegions->m_pRects[a],m_pRegions->m_pRects[b],m_pScanWithShadows); if (pWall[a*m_pRegions->m_numRects+b]==TRUE || (m_pRegions->m_type[a]&PHOTOGRAPH_REGION) || (m_pRegions->m_type[b]&PHOTOGRAPH_REGION)) { if (!m_pRegions->MergerIntersectsPhoto(a,b)) { m_pRegions->UnionRegions(a,b); m_pRegions->checkIfValidRegion(a, border); // in this context, checkvalid should have the effect of culling regions which are probably only stray dots m_pRegions->RegionType(a); // figure out what type of region the combined region should be unionOperationInLastPass=true; // region a has changed so reset collision flags for (i=a+1;im_numRects;i++) if (m_pRegions->m_valid[i]==TRUE) pWall[a*m_pRegions->m_numRects+i]=UNKNOWN; for (i=0;im_valid[i]==TRUE) pWall[i*m_pRegions->m_numRects+a]=UNKNOWN; if (border>=MERGABLE_WITHOUT_COLLISIONDETECTION) border=MERGABLE_WITHOUT_COLLISIONDETECTION-2; } } } } } } } } } } } } // we have stricter requirements for region validity after we have done the border search. for (a=0;am_numRects;a++) { m_pRegions->checkIfValidRegion(a,DONE_WITH_BORDER_CHECKING); } // m_pScanBlurred->ColorChunks(pMap); // for debugging purposes... so we know where exactly chunks are m_pRegions->CompactDown(m_pRegions->m_validRects+10); // we don't want to hand the user a list which includes invalidated regions // // free pWall // delete[] pWall; } } delete[] pMap; } return(TRUE); } bool CRegionDetector::CollisionDetection(RECT r1, RECT r2, C32BitDibWrapper* pImage) { // use tracer lines to determine if there is an obstical between the two regions... be it possibly a photograph region or a speckle which we were wise enough to delete // we should cache collision results, but we are lazy and compared with the time taken by all of the filters which edited every single bitmap pixel, time spent here is trivial // first we need to determine how the regions are located with respect to each other // we do three tracer rays (top edge to top edge, median to median, and bottom to bottom) // throw out the edge with the highest collsion value... maybe we were unlucky and hit a stray speckle // // Diagram // .____ 253 // ._ // . \* // \ \ 43531 <-- hit speckle, throw out value // \ // \ 215 // average intensity value: aprox 230 so its safe to merge the two regions ULONG resistance[3]; // ULONG totalResistance; ULONG maxResistance,minResistance,i; if (r1.right < r2.left) // region 1 is to the left of region 2 { resistance[0]=pImage->Line(r1.right,r1.top,r2.left,r2.top); resistance[1]=pImage->Line(r1.right,r1.bottom,r2.left,r2.bottom); resistance[2]=pImage->Line(r1.right,(r1.bottom+r1.top)/2,r2.left,(r2.bottom+r2.top)/2); } else if (r2.right < r1.left) //region 2 is to the left of region 1 { resistance[0]=pImage->Line(r2.right,r2.top,r1.left,r1.top); resistance[1]=pImage->Line(r2.right,r2.bottom,r1.left,r1.bottom); resistance[2]=pImage->Line(r2.right,(r2.bottom+r2.top)/2,r1.left,(r1.bottom+r1.top)/2); } else if (r1.bottom < r2.top) // region 1 is above region 2 { resistance[0]=pImage->Line(r1.right,r1.bottom,r2.right,r2.top); resistance[1]=pImage->Line(r1.left,r1.bottom,r2.left,r2.top); resistance[2]=pImage->Line((r1.left+r1.right)/2,r1.bottom,(r2.left+r2.right)/2,r2.top); } else if (r2.bottom < r1.top) // region 2 is above region 1 { resistance[0]=pImage->Line(r2.right,r2.bottom,r1.right,r1.top); resistance[1]=pImage->Line(r2.left,r2.bottom,r1.left,r1.top); resistance[2]=pImage->Line((r2.left+r2.right)/2,r2.bottom,(r1.left+r1.right)/2,r1.top); } // we used to have a more complex scheme where we took the average of the lower two values // hence some of the following code is legacy code from that experiment maxResistance=0; minResistance=MAX_RESISTANCE_ALLOWED_TO_UNION+1; for (i=0;i<3;i++) { if (resistance[i]>maxResistance) maxResistance=resistance[i]; if (resistance[i]MAX_RESISTANCE_ALLOWED_TO_UNION) { return(false); } else { return(true); } } // CRegionList member functions: CRegionList::CRegionList(int num) { int i; m_numRects=0; m_maxRects=num; m_nBitmapWidth=0; m_nBitmapHeight=0; m_pRects = new RECT[num]; m_pixelsFilled = new ULONG[num]; m_valid= new bool[num]; m_type = new int[num]; m_totalColored= new ULONG[num]; m_totalIntensity= new ULONG[num]; m_totalEdge= new ULONG[num]; m_backgroundColorPixels = new int[num]; // // Make sure all of the memory allocations succeeded // if (m_pRects && m_pixelsFilled && m_valid && m_type && m_totalColored && m_totalIntensity && m_totalEdge && m_backgroundColorPixels) { for (i=0;iTEXTPHOTO_THRESHOLD) { m_type[region]=PHOTOGRAPH_REGION; // regions which we are very confident with } else { m_type[region]=TEXT_REGION; /// regions we don't have a darn clue about if (largeRegion(region)==true) m_type[region]=TEXT_REGION|MERGABLE_WITH_PHOTOGRAPH; } return(m_type[region]); } bool CRegionList::largeRegion(int region) { int width, height, size; width=(m_pRects[region].right-m_pRects[region].left); height=(m_pRects[region].bottom-m_pRects[region].top); size=width*height; if (size>LARGEREGION_THRESHOLD) return(true); return(false); } double CRegionList::ClassifyRegion(int region) // determine if the region is a text or a graphics region { // higher numbers are photo regions // low numbers are text regions // this function is not been written for speed // its been written so that it is still almost understandable // concept: use a bunch of tests that are accurate about 75% of the time // to get a test that is accurate 99.9% of the time double edgeFactor; // shadows and stray smudges should have real low edge factors... but // dots should have high edge factors double intensityFactor; // if a region has a very high intensity factor, forget about worrying if it is valid or not double colorFactor; // a region with a lot of color is unlikely to be a stray speckle double aspectRatioFactor; double width,height; double size; double textRegionStylePixelsFactor; double classificationValue; width=(double)(m_pRects[region].right-m_pRects[region].left)+.01; // avoid divide by zero height=(double)(m_pRects[region].bottom-m_pRects[region].top)+.01; // avoid divide by zero size=width*height; if (width>height) aspectRatioFactor=height/width; else aspectRatioFactor=width/height; //if(m_pixelsFilled=MIN_BORDERLINE_TEXTPHOTO && classificationValue <=MAX_BORDERLINE_TEXTPHOTO) { classificationValue*=size/REASONABLE_PHOTO_SIZE; // big images are usually pictures.. and big images which are text blocks should have had very low color vals // add more tests here // potentially add more time intensive tests such as count num colors }*/ // classificationValue=textRegionStylePixelsFactor; // debug return(classificationValue); } bool CRegionList::checkIfValidRegion(int region, int border) // syncs whether a region is valid or not { if (m_valid[region]==true) // ignore already invalidated regions { m_valid[region]=ValidRegion(region, border); if (m_valid[region]==false) m_validRects--; } return(m_valid[region]); } bool CRegionList::ValidRegion(int region, int border) // determines if a region is likely a worthless speck of dust or shadow or if we should care about the region { double aspectRatioFactor; double width,height; double size; int edgePenaltyFactor; // check if the region crosses the EDGE_PENALTY_WIDTH outer pixels of the image width=(double)(m_pRects[region].right-m_pRects[region].left)+.01; // just to be safe to avoid divide by zero height=(double)(m_pRects[region].bottom-m_pRects[region].top)+.01; // just to be safe to avoid divide by zero edgePenaltyFactor=1; // disable penalty factor calculations /* if( m_pRects[region].leftMAX_MERGE_DIFFERENT_REGIONS) edgePenaltyFactor=EDGE_PENALTY_FACTOR; else edgePenaltyFactor=CLOSE_TO_EDGE_PENALTY_FACTOR; } else if( m_pRects[region].leftMAX_MERGE_DIFFERENT_REGIONS) edgePenaltyFactor=edgePenaltyFactor*2; // if(border>BORDER_EXTREME_EDGE_PIXEL_REGION_PENALTY) edgePenaltyFactor=edgePenaltyFactor*2; size=width*height; // the problem child text regions are small ones... so we use the size of the image as a factor if (width>height) aspectRatioFactor=height/width; else aspectRatioFactor=width/height; // its too small if ((int)m_pixelsFilled[region]MAXREGIONRATIO && (width*edgePenaltyFactor=(m_pRects[region].left-border) && x<=(m_pRects[region].right+border) && y>=(m_pRects[region].top-border) && y<=(m_pRects[region].bottom+border)) return(true); return(false); } void CRegionList::AddPixel(int region, ULONG pixel,ULONG edge, int x, int y) { if (m_pixelsFilled[region]!=0) { if (xm_pRects[region].right) m_pRects[region].right=x; if (ym_pRects[region].bottom) m_pRects[region].bottom=y; } else // init region { m_pixelsFilled[region]=0; m_totalColored[region]=0; m_totalIntensity[region]=0; m_pRects[region].left=x; m_pRects[region].right=x; m_pRects[region].top=y; m_pRects[region].bottom=y; m_numRects++; m_validRects++; } m_pixelsFilled[region]++; m_totalColored[region]+=DifferenceFromGray(pixel); m_totalIntensity[region]+=Intensity(pixel); m_totalEdge[region]+=Intensity(edge); } // unions two regions together... region b is invalidated bool CRegionList::UnionRegions(int a, int b) { if (m_valid[a]!=true || m_valid[b]!=true) return(false); // the user tried to union an invalidated region m_valid[b]=false; m_pRects[a].left=MIN(m_pRects[a].left,m_pRects[b].left); m_pRects[a].top=MIN(m_pRects[a].top,m_pRects[b].top); m_pRects[a].right=MAX(m_pRects[a].right,m_pRects[b].right); m_pRects[a].bottom=MAX(m_pRects[a].bottom,m_pRects[b].bottom); m_pixelsFilled[a]+=m_pixelsFilled[b]; m_totalColored[a]+=m_totalColored[b]; m_totalIntensity[a]+=m_totalIntensity[b]; m_totalEdge[a]+=m_totalEdge[b]; m_backgroundColorPixels[a]+=m_backgroundColorPixels[b]; m_validRects--; return(true); } RECT CRegionList::UnionRects(RECT a, RECT b) { RECT result; result.left=MIN(a.left,b.left); result.top=MIN(a.top,b.top); result.right=MAX(a.right,b.right); result.bottom=MAX(a.bottom,b.bottom); return(result); } bool CRegionList::MergerIntersectsPhoto(int a, int b) // if we merge these two regions, will we also be merging with a photo region (a taboo) { RECT mergedRect; int i; mergedRect=UnionRects(m_pRects[a],m_pRects[b]); for (i=0;iintersect.top) return(true); else return(false); /* // old buggy code for checking if two regions intersected if(InsideRegion(r1,r2.left,r2.top,border) || // check if any of the four corner pixels are inside the other region InsideRegion(r1,r2.left,r2.bottom,border) || InsideRegion(r1,r2.right,r2.top,border) || // b inside a InsideRegion(r1,r2.right,r2.bottom,border)|| InsideRegion(r2,r1.left,r1.top,border) || // a inside b InsideRegion(r2,r1.left,r1.bottom,border) || InsideRegion(r2,r1.right,r1.top,border) || InsideRegion(r2,r1.right,r1.bottom,border) ) return true; else return false;*/ } RECT CRegionList::Intersect(RECT r1, RECT r2) { RECT intersect; intersect.left=MAX(r1.left,r2.left); intersect.right=MIN(r1.right,r2.right); intersect.top=MAX(r1.top,r2.top); intersect.bottom=MIN(r1.bottom,r2.bottom); if (intersect.left<=intersect.right && intersect.top<=intersect.bottom) return(intersect); else { intersect.left=-1; intersect.right=-1; intersect.top=-1; intersect.bottom=-1; return(intersect); } } bool CRegionList::InsideRegion(RECT region, int x, int y, int border) // border is the amount of border space to place around the outside of the region { if (x>=(region.left-border) && x<=(region.right+border) && y>=(region.top-border) && y<=(region.bottom+border)) return(true); return(false); } // compact down ignores all other info aside from rect location // leads to faster access void CRegionList::CompactDown(int size) { int i; int j = 0; RECT * compactedRects = new RECT[size]; bool * compactedValid = new bool[size]; int * compactedType = new int[size]; int * compactedBackgroundColorPixels = new int[size]; ULONG * compactedPixelsFilled = new ULONG[size]; // how many of the pixels in the region were actually selected? ULONG * compactedTotalColored = new ULONG[size]; // accumulated color difference indicator ULONG * compactedTotalIntensity = new ULONG[size]; // accumulated intensity indicator ULONG * compactedTotalEdge = new ULONG[size]; // accumulated edge values // // Make sure all of the memory allocations succeeded // if (compactedRects && compactedValid && compactedType && compactedBackgroundColorPixels && compactedPixelsFilled && compactedTotalColored && compactedTotalIntensity && compactedTotalEdge) { for (i=0;i