// // begin license header // // This file is part of Pixy CMUcam5 or "Pixy" for short // // All Pixy source code is provided under the terms of the // GNU General Public License v2 (http://www.gnu.org/licenses/gpl-2.0.html). // Those wishing to use Pixy source code, software and/or // technologies under different licensing terms should contact us at // cmucam@cs.cmu.edu. Such licensing terms are available for // all portions of the Pixy codebase presented here. // // end license header // #ifndef PIXY #include "debug.h" #else #include "pixy_init.h" #endif #include "blobs.h" #define CC_SIGNATURE(s) (m_ccMode==CC_ONLY || m_clut.getType(s)==CL_MODEL_TYPE_COLORCODE) Blobs::Blobs(Qqueue *qq, uint8_t *lut) : m_clut(lut) { int i; m_mutex = false; m_minArea = MIN_AREA; m_maxBlobs = MAX_BLOBS; m_maxBlobsPerModel = MAX_BLOBS_PER_MODEL; m_mergeDist = MAX_MERGE_DIST; m_maxBlob = NULL; m_qq = qq; #ifdef PIXY m_maxCodedDist = MAX_CODED_DIST; #else m_maxCodedDist = MAX_CODED_DIST/2; m_qvals = new uint32_t[0x8000]; #endif m_ccMode = DISABLED; m_blobs = new uint16_t[MAX_BLOBS*5]; m_numBlobs = 0; m_blobReadIndex = 0; m_ccBlobReadIndex = 0; // reset blob assemblers for (i=0; idequeue(&qval)==0); if (qval.m_col>=0xfffe) break; if (res<0) continue; if (qval.m_col==0) { prevStartCol = 0xffff; prevSig = 0; if (segmentSig) { res = handleSegment(segmentSig, row, segmentStartCol-1, segmentEndCol - segmentStartCol+1); segmentSig = 0; } row++; #ifndef PIXY m_qvals[m_numQvals++] = 0; #else if (icount++==5) // an interleave of every 5 lines or about every 175us seems good { g_chirpUsb->service(); icount = 0; } #endif continue; } sig = qval.m_col&0x07; u = qval.m_u; v = qval.m_v; u <<= CL_LUT_ENTRY_SCALE; v <<= CL_LUT_ENTRY_SCALE; c = qval.m_y; if (c==0) c = 1; u /= c; v /= c; if (m_clut.m_runtimeSigs[sig-1].m_uMin=(int32_t)m_clut.m_miny) { qval.m_col >>= 3; startCol = qval.m_col; merge = startCol-prevStartCol<=5 && prevSig==sig; if (segmentSig==0 && merge) { segmentSig = sig; segmentStartCol = prevStartCol; } else if (segmentSig!=0 && (segmentSig!=sig || !merge)) { res = handleSegment(segmentSig, row, segmentStartCol-1, segmentEndCol - segmentStartCol+1); segmentSig = 0; } if (segmentSig!=0 && merge) segmentEndCol = startCol; else if (segmentSig==0 && !merge) res = handleSegment(sig, row, startCol-1, 2); prevSig = sig; prevStartCol = startCol; } else if (segmentSig!=0) { res = handleSegment(segmentSig, row, segmentStartCol-1, segmentEndCol - segmentStartCol+1); segmentSig = 0; } } endFrame(); if (qval.m_col==0xfffe) // error code, queue overrun return -1; return 0; } int Blobs::blobify() { uint32_t i, j, k; bool colorCode; CBlob *blob; uint16_t *blobsStart; uint16_t numBlobsStart, invalid, invalid2; uint16_t left, top, right, bottom; //uint32_t timer, timer2=0; if (runlengthAnalysis()<0) { for (i=0; inext, k++) { if ((colorCode && blob->GetArea()GetArea()<(int)m_minArea)) continue; blob->getBBox((short &)left, (short &)top, (short &)right, (short &)bottom); if (bottom-top<=1) // blobs that are 1 line tall continue; m_blobs[j + 0] = i+1; m_blobs[j + 1] = left; m_blobs[j + 2] = right; m_blobs[j + 3] = top; m_blobs[j + 4] = bottom; m_numBlobs++; j += 5; } //setTimer(&timer); if (!colorCode) // do not combine color code models { while(1) { invalid2 = combine2(blobsStart, m_numBlobs-numBlobsStart); if (invalid2==0) break; invalid += invalid2; } } //timer2 += getTimer(timer); } //setTimer(&timer); invalid += combine(m_blobs, m_numBlobs); if (m_ccMode!=DISABLED) { m_ccBlobs = (BlobB *)(m_blobs + m_numBlobs*5); // calculate number of codedblobs left processCC(); } if (invalid || m_ccMode!=DISABLED) { invalid2 = compress(m_blobs, m_numBlobs); m_numBlobs -= invalid2; } //timer2 += getTimer(timer); //cprintf("time=%d\n", timer2); // never seen this greater than 200us. or 1% of frame period // reset read indexes-- new frame m_blobReadIndex = 0; m_ccBlobReadIndex = 0; m_mutex = false; // free memory for (i=0; i0) cprintf("%d: blobs %d %d %d %d %d\n", frame, m_numBlobs, m_blobs[1], m_blobs[2], m_blobs[3], m_blobs[4]); else cprintf("%d: blobs 0\n", frame); frame++; #endif return 0; } #ifndef PIXY void Blobs::getRunlengths(uint32_t **qvals, uint32_t *len) { *qvals = m_qvals; *len = m_numQvals; } #endif uint16_t Blobs::getCCBlock(uint8_t *buf, uint32_t buflen) { uint16_t *buf16 = (uint16_t *)buf; uint16_t temp, width, height; uint16_t checksum; uint16_t len = 8; // default if (buflen<9*sizeof(uint16_t)) return 0; if (m_mutex || m_ccBlobReadIndex>=m_numCCBlobs) // we're copying, so no CC blocks for now.... return 0; if (m_blobReadIndex==0 && m_ccBlobReadIndex==0) // beginning of frame, mark it with empty block { buf16[0] = BL_BEGIN_MARKER; len++; buf16++; } // beginning of block buf16[0] = BL_BEGIN_MARKER_CC; // model temp = m_ccBlobs[m_ccBlobReadIndex].m_model; checksum = temp; buf16[2] = temp; // width width = m_ccBlobs[m_ccBlobReadIndex].m_right - m_ccBlobs[m_ccBlobReadIndex].m_left; checksum += width; buf16[5] = width; // height height = m_ccBlobs[m_ccBlobReadIndex].m_bottom - m_ccBlobs[m_ccBlobReadIndex].m_top; checksum += height; buf16[6] = height; // x center temp = m_ccBlobs[m_ccBlobReadIndex].m_left + width/2; checksum += temp; buf16[3] = temp; // y center temp = m_ccBlobs[m_ccBlobReadIndex].m_top + height/2; checksum += temp; buf16[4] = temp; temp = m_ccBlobs[m_ccBlobReadIndex].m_angle; checksum += temp; buf16[7] = temp; buf16[1] = checksum; // next blob m_ccBlobReadIndex++; return len*sizeof(uint16_t); } uint16_t Blobs::getBlock(uint8_t *buf, uint32_t buflen) { uint16_t *buf16 = (uint16_t *)buf; uint16_t temp, width, height; uint16_t checksum; uint16_t len = 7; // default int i = m_blobReadIndex*5; if (buflen<8*sizeof(uint16_t)) return 0; if (m_blobReadIndex>=m_numBlobs && m_ccMode!=DISABLED) return getCCBlock(buf, buflen); if (m_mutex || m_blobReadIndex>=m_numBlobs) // we're copying, so no blocks for now.... return 0; if (m_blobReadIndex==0) // beginning of frame, mark it with empty block { buf16[0] = BL_BEGIN_MARKER; len++; buf16++; } // beginning of block buf16[0] = BL_BEGIN_MARKER; // model temp = m_blobs[i]; checksum = temp; buf16[2] = temp; // width width = m_blobs[i+2] - m_blobs[i+1]; checksum += width; buf16[5] = width; // height height = m_blobs[i+4] - m_blobs[i+3]; checksum += height; buf16[6] = height; // x center temp = m_blobs[i+1] + width/2; checksum += temp; buf16[3] = temp; // y center temp = m_blobs[i+3] + height/2; checksum += temp; buf16[4] = temp; buf16[1] = checksum; // next blob m_blobReadIndex++; return len*sizeof(uint16_t); } BlobA *Blobs::getMaxBlob(uint16_t signature) { int i, j; uint32_t area, maxArea; BlobA *blob; BlobB *ccBlob; if (signature==0) // 0 means return the biggest regardless of signature number { // if we've already found it, return it if (m_maxBlob) return m_maxBlob; // look through all blobs looking for the blob with the biggest area for (i=0, maxArea=0; im_right - blob->m_left)*(blob->m_bottom - blob->m_top); if (area>maxArea) { maxArea = area; m_maxBlob = blob; } } for (i=0; im_right - ccBlob->m_left)*(ccBlob->m_bottom - ccBlob->m_top); if (area>maxArea) { maxArea = area; m_maxBlob = (BlobA *)ccBlob; } } return m_maxBlob; } else { for (i=0, j=0; i=right && top0<=top && bottom0>=bottom) { blobs[jj+0] = 0; // invalidate invalid++; } else if (left<=left0 && right>=right0 && top<=top0 && bottom>=bottom0) { blobs[ii+0] = 0; // invalidate invalid++; } } } return invalid; } uint16_t Blobs::combine2(uint16_t *blobs, uint16_t numBlobs) { uint16_t i, j, ii, jj, left0, right0, top0, bottom0; uint16_t left, right, top, bottom; uint16_t invalid; for (i=0, ii=0, invalid=0; i=right0 && left-right0<=m_mergeDist && ((top0<=top && top<=bottom0) || (top0<=bottom && bottom<=bottom0))) { blobs[ii+2] = right; blobs[jj+0] = 0; // invalidate invalid++; } else if (top<=top0 && top0-bottom<=m_mergeDist && ((left0<=left && left<=right0) || (left0<=right && right<=right0))) { blobs[ii+3] = top; blobs[jj+0] = 0; // invalidate invalid++; } else if (bottom>=bottom0 && top-bottom0<=m_mergeDist && ((left0<=left && left<=right0) || (left0<=right && right<=right0))) { blobs[ii+4] = bottom; blobs[jj+0] = 0; // invalidate invalid++; } #else // at least half of a side (the smaller adjacent side) has to overlap if (left<=left0 && left0-right<=m_mergeDist && ((top<=top0 && top0<=top+height) || (top+height<=bottom0 && bottom0<=bottom))) { blobs[ii+1] = left; blobs[jj+0] = 0; // invalidate invalid++; } else if (right>=right0 && left-right0<=m_mergeDist && ((top<=top0 && top0<=top+height) || (top+height<=bottom0 && bottom0<=bottom))) { blobs[ii+2] = right; blobs[jj+0] = 0; // invalidate invalid++; } else if (top<=top0 && top0-bottom<=m_mergeDist && ((left<=left0 && left0<=left+width) || (left+width<=right0 && right0<=right))) { blobs[ii+3] = top; blobs[jj+0] = 0; // invalidate invalid++; } else if (bottom>=bottom0 && top-bottom0<=m_mergeDist && ((left<=left0 && left0<=left+width) || (left+width<=right0 && right0<=right))) { blobs[ii+4] = bottom; blobs[jj+0] = 0; // invalidate invalid++; } #endif } } return invalid; } int16_t Blobs::distance(BlobA *blob0, BlobA *blob1) { int16_t left0, right0, top0, bottom0; int16_t left1, right1, top1, bottom1; left0 = blob0->m_left; right0 = blob0->m_right; top0 = blob0->m_top; bottom0 = blob0->m_bottom; left1 = blob1->m_left; right1 = blob1->m_right; top1 = blob1->m_top; bottom1 = blob1->m_bottom; if (left0>=left1 && ((top0<=top1 && top1<=bottom0) || (top0<=bottom1 && (bottom1<=bottom0 || top1<=top0)))) return left0-right1; if (left1>=left0 && ((top0<=top1 && top1<=bottom0) || (top0<=bottom1 && (bottom1<=bottom0 || top1<=top0)))) return left1-right0; if (top0>=top1 && ((left0<=left1 && left1<=right0) || (left0<=right1 && (right1<=right0 || left1<=left0)))) return top0-bottom1; if (top1>=top0 && ((left0<=left1 && left1<=right0) || (left0<=right1 && (right1<=right0 || left1<=left0)))) return top1-bottom0; return 0x7fff; // return a large number } bool Blobs::closeby(BlobA *blob0, BlobA *blob1) { // check to see if blobs are invalid or equal if (blob0->m_model==0 || blob1->m_model==0 || blob0->m_model==blob1->m_model) return false; // check to see that the blobs are from color code models. If they aren't both // color code blobs, we return false if (!CC_SIGNATURE(blob0->m_model&0x07) || !CC_SIGNATURE(blob1->m_model&0x07)) return false; return distance(blob0, blob1)<=m_maxCodedDist; } int16_t Blobs::distance(BlobA *blob0, BlobA *blob1, bool horiz) { int16_t dist; if (horiz) dist = (blob0->m_right+blob0->m_left)/2 - (blob1->m_right+blob1->m_left)/2; else dist = (blob0->m_bottom+blob0->m_top)/2 - (blob1->m_bottom+blob1->m_top)/2; if (dist<0) return -dist; else return dist; } int16_t Blobs::angle(BlobA *blob0, BlobA *blob1) { int acx, acy, bcx, bcy; float res; acx = (blob0->m_right + blob0->m_left)/2; acy = (blob0->m_bottom + blob0->m_top)/2; bcx = (blob1->m_right + blob1->m_left)/2; bcy = (blob1->m_bottom + blob1->m_top)/2; res = atan2((float)(acy-bcy), (float)(bcx-acx))*180/3.1415f; return (int16_t)res; } void Blobs::sort(BlobA *blobs[], uint16_t len, BlobA *firstBlob, bool horiz) { uint16_t i, td, distances[MAX_COLOR_CODE_MODELS*2]; bool done; BlobA *tb; // create list of distances for (i=0; idistances[i]) { // swap distances td = distances[i]; distances[i] = distances[i-1]; distances[i-1] = td; // swap blobs tb = blobs[i]; blobs[i] = blobs[i-1]; blobs[i-1] = tb; done = false; } } if (done) break; } } bool Blobs::analyzeDistances(BlobA *blobs0[], int16_t numBlobs0, BlobA *blobs[], int16_t numBlobs, BlobA **blobA, BlobA **blobB) { bool skip; bool result = false; int16_t dist, minDist, i, j, k; for (i=0, minDist=0x7fff; im_model&0x07)==(blobs[j]->m_model&0x07)) { skip = true; break; } } if (skip) continue; dist = distance(blobs0[i], blobs[j]); if (distm_right-blobs[i]->m_left) * (blobs[i]->m_bottom-blobs[i]->m_top); lowerArea = (area0*100)/(100+TOL); upperArea = area0 + (area0*TOL)/100; for (j=0, numEqual=0; j<*numBlobs; j++) { if (i==j) continue; area1 = (blobs[j]->m_right-blobs[j]->m_left) * (blobs[j]->m_bottom-blobs[j]->m_top); if (lowerArea<=area1 && area1<=upperArea) numEqual++; } if (numEqual>maxEqual) { maxEqual = numEqual; maxEqualArea = area0; set = true; } } if (!set) *numBlobs = 0; for (i=0, numNewBlobs=0; i<*numBlobs && numNewBlobsm_right-blobs[i]->m_left) * (blobs[i]->m_bottom-blobs[i]->m_top); lowerArea = (area0*100)/(100+TOL); upperArea = area0 + (area0*TOL)/100; if (lowerArea<=maxEqualArea && maxEqualArea<=upperArea) newBlobs[numNewBlobs++] = blobs[i]; #ifndef PIXY else if (*numBlobs>=5 && (blobs[i]->m_model&0x07)==2) DBG("eliminated!"); #endif } // copy new blobs over for (i=0; im_model&0x07)==(blobs[i]->m_model&0x07)) set = true; else break; } } if (set) { // copy new blobs over for (i=0; im_model<=CL_NUM_SIGNATURES && blob1->m_model<=CL_NUM_SIGNATURES) { count++; scount = count<<3; blob0->m_model |= scount; blob1->m_model |= scount; } else if (blob0->m_model>CL_NUM_SIGNATURES && blob1->m_model<=CL_NUM_SIGNATURES) { scount = blob0->m_model & ~0x07; blob1->m_model |= scount; } else if (blob1->m_model>CL_NUM_SIGNATURES && blob0->m_model<=CL_NUM_SIGNATURES) { scount = blob1->m_model & ~0x07; blob0->m_model |= scount; } } } } #if 1 // 2nd pass: merge blob clumps for (blob0=(BlobA *)m_blobs; blob0m_model<=CL_NUM_SIGNATURES) // skip normal blobs continue; scount = blob0->m_model&~0x07; for (blob1=(BlobA *)blob0+1; blob1m_model<=CL_NUM_SIGNATURES) continue; scount1 = blob1->m_model&~0x07; if (scount!=scount1 && closeby(blob0, blob1)) mergeClumps(scount, scount1); } } #endif // 3rd and final pass, find each blob clean it up and add it to the table endBlobB = (BlobB *)((BlobA *)m_blobs + MAX_BLOBS)-1; for (i=1, codedBlob = m_ccBlobs, m_numCCBlobs=0; i<=count && codedBlobm_model&~0x07)==scount) blobs[j++] = blob0; } #if 1 // cleanup blobs, deal with cases where there are more blobs than models cleanup(blobs, &j); #endif if (j<2) continue; // find left, right, top, bottom of color coded block for (k=0, left=right=top=bottom=avgWidth=avgHeight=0; km_model, blobs[k]->m_left, blobs[k]->m_right, blobs[k]->m_top, blobs[k]->m_bottom); if (blobs[left]->m_left > blobs[k]->m_left) left = k; if (blobs[top]->m_top > blobs[k]->m_top) top = k; if (blobs[right]->m_right < blobs[k]->m_right) right = k; if (blobs[bottom]->m_bottom < blobs[k]->m_bottom) bottom = k; avgWidth += blobs[k]->m_right - blobs[k]->m_left; avgHeight += blobs[k]->m_bottom - blobs[k]->m_top; } avgWidth /= j; avgHeight /= j; codedBlob->m_left = blobs[left]->m_left; codedBlob->m_right = blobs[right]->m_right; codedBlob->m_top = blobs[top]->m_top; codedBlob->m_bottom = blobs[bottom]->m_bottom; #if 1 // is it more horizontal than vertical? width = (blobs[right]->m_right - blobs[left]->m_left)*100; width /= avgWidth; // scale by average width because our swatches might not be square height = (blobs[bottom]->m_bottom - blobs[top]->m_top)*100; height /= avgHeight; // scale by average height because our swatches might not be square if (width > height) sort(blobs, j, blobs[left], true); else sort(blobs, j, blobs[top], false); #if 1 cleanup2(blobs, &j); if (j<2) continue; else if (j>5) j = 5; #endif // create new blob, compare the coded models, pick the smaller one for (k=0, codedModel0=0; km_model&0x07; } for (k=j-1, codedModel=0; k>=0; k--) { codedModel <<= 3; codedModel |= blobs[k]->m_model&0x07; blobs[k]->m_model = 0; // invalidate } if (codedModel0m_model = codedModel0; codedBlob->m_angle = angle(blobs[0], blobs[j-1]); } else { codedBlob->m_model = codedModel; codedBlob->m_angle = angle(blobs[j-1], blobs[0]); } #endif //DBG("cc %d %d %d %d %d", m_numCCBlobs, codedBlob->m_left, codedBlob->m_right, codedBlob->m_top, codedBlob->m_bottom); codedBlob++; m_numCCBlobs++; } // 3rd pass, invalidate blobs for (blob0=(BlobA *)m_blobs; blob0m_model>CL_NUM_SIGNATURES) blob0->m_model = 0; } else if (blob0->m_model>CL_NUM_SIGNATURES || CC_SIGNATURE(blob0->m_model)) blob0->m_model = 0; // invalidate-- not part of a color code } } void Blobs::endFrame() { int i; for (i=0; i