int FeaturesMiner::findBlackBorder(int orientation) { if (image == NULL) { return -1; } int sum; unsigned int val; if (orientation == HORIZONTAL) { for (int i = 0; i < width; i++) { val = sum = 0; for (int j = 0; j < height; j++) { pixGetPixel(image, i, j, &val); sum += val; } if ((sum) >= 200 * height) { return i; } } } else if (orientation == VERTICAL) { for (int i = 0; i < height; i++) { val = sum = 0; for (int j = 0; j < width; j++) { pixGetPixel(image, j, i, &val); sum += val; } if ((sum) >= 200 * width) { return i; } } } return -1; }
/*! * pixMeanInRectangle() * * Input: pix (8 bpp) * box (region to compute mean value) * pixma (mean accumulator) * &val (<return> mean value * Return: 0 if OK, 1 on error * * Notes: * (1) This function is intended to be used for many rectangles * on the same image. It can find the mean within a * rectangle in O(1), independent of the size of the rectangle. */ l_int32 pixMeanInRectangle(PIX *pixs, BOX *box, PIX *pixma, l_float32 *pval) { l_int32 w, h, bx, by, bw, bh; l_uint32 val00, val01, val10, val11; l_float32 norm; BOX *boxc; PROCNAME("pixMeanInRectangle"); if (!pval) return ERROR_INT("&val not defined", procName, 1); *pval = 0.0; if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs not defined", procName, 1); if (!box) return ERROR_INT("box not defined", procName, 1); if (!pixma) return ERROR_INT("pixma not defined", procName, 1); /* Clip rectangle to image */ pixGetDimensions(pixs, &w, &h, NULL); boxc = boxClipToRectangle(box, w, h); boxGetGeometry(boxc, &bx, &by, &bw, &bh); boxDestroy(&boxc); if (bw == 0 || bh == 0) return ERROR_INT("no pixels in box", procName, 1); /* Use up to 4 points in the accumulator */ norm = 1.0 / (bw * bh); if (bx > 0 && by > 0) { pixGetPixel(pixma, bx + bw - 1, by + bh - 1, &val11); pixGetPixel(pixma, bx + bw - 1, by - 1, &val10); pixGetPixel(pixma, bx - 1, by + bh - 1, &val01); pixGetPixel(pixma, bx - 1, by - 1, &val00); *pval = norm * (val11 - val01 + val00 - val10); } else if (by > 0) { /* bx == 0 */ pixGetPixel(pixma, bw - 1, by + bh - 1, &val11); pixGetPixel(pixma, bw - 1, by - 1, &val10); *pval = norm * (val11 - val10); } else if (bx > 0) { /* by == 0 */ pixGetPixel(pixma, bx + bw - 1, bh - 1, &val11); pixGetPixel(pixma, bx - 1, bh - 1, &val01); *pval = norm * (val11 - val01); } else { /* bx == 0 && by == 0 */ pixGetPixel(pixma, bw - 1, bh - 1, &val11); *pval = norm * val11; } return 0; }
// Helper to compute edge offsets for all the blobs on the list. // See coutln.h for an explanation of edge offsets. void BLOBNBOX::ComputeEdgeOffsets(Pix* thresholds, Pix* grey, BLOBNBOX_LIST* blobs) { int grey_height = 0; int thr_height = 0; int scale_factor = 1; if (thresholds != NULL && grey != NULL) { grey_height = pixGetHeight(grey); thr_height = pixGetHeight(thresholds); scale_factor = IntCastRounded(static_cast<double>(grey_height) / thr_height); } BLOBNBOX_IT blob_it(blobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { BLOBNBOX* blob = blob_it.data(); if (blob->cblob() != NULL) { // Get the threshold that applies to this blob. l_uint32 threshold = 128; if (thresholds != NULL && grey != NULL) { const TBOX& box = blob->cblob()->bounding_box(); // Transform the coordinates if required. TPOINT pt((box.left() + box.right()) / 2, (box.top() + box.bottom()) / 2); pixGetPixel(thresholds, pt.x / scale_factor, thr_height - 1 - pt.y / scale_factor, &threshold); } blob->cblob()->ComputeEdgeOffsets(threshold, grey); } } }
/*! * kernelCreateFromPix() * * Input: pix * cy, cx (origin of kernel) * Return: kernel, or null on error * * Notes: * (1) The origin must be positive and within the dimensions of the pix. */ L_KERNEL * kernelCreateFromPix(PIX *pix, l_int32 cy, l_int32 cx) { l_int32 i, j, w, h, d; l_uint32 val; L_KERNEL *kel; PROCNAME("kernelCreateFromPix"); if (!pix) return (L_KERNEL *)ERROR_PTR("pix not defined", procName, NULL); pixGetDimensions(pix, &w, &h, &d); if (d != 8) return (L_KERNEL *)ERROR_PTR("pix not 8 bpp", procName, NULL); if (cy < 0 || cx < 0 || cy >= h || cx >= w) return (L_KERNEL *)ERROR_PTR("(cy, cx) invalid", procName, NULL); kel = kernelCreate(h, w); kernelSetOrigin(kel, cy, cx); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pix, j, i, &val); kernelSetElement(kel, i, j, (l_float32)val); } } return kel; }
main(int argc, char **argv) { l_int32 i, j, w, h, same, width, height, cx, cy; l_uint32 val; PIX *pixs, *pixse, *pixd1, *pixd2; SEL *sel; static char mainName[] = "rasterop_reg"; if (argc != 1) return ERROR_INT(" Syntax: rasterop_reg", mainName, 1); pixs = pixRead("feyn.tif"); for (width = 1; width <= 25; width += 3) { for (height = 1; height <= 25; height += 4) { cx = width / 2; cy = height / 2; /* Dilate using an actual sel */ sel = selCreateBrick(height, width, cy, cx, SEL_HIT); pixd1 = pixDilate(NULL, pixs, sel); /* Dilate using a pix as a sel */ pixse = pixCreate(width, height, 1); pixSetAll(pixse); pixd2 = pixCopy(NULL, pixs); w = pixGetWidth(pixs); h = pixGetHeight(pixs); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val); if (val) pixRasterop(pixd2, j - cx, i - cy, width, height, PIX_SRC | PIX_DST, pixse, 0, 0); } } pixEqual(pixd1, pixd2, &same); if (same == 1) fprintf(stderr, "Correct for (%d,%d)\n", width, height); else { fprintf(stderr, "Error: results are different!\n"); fprintf(stderr, "SE: width = %d, height = %d\n", width, height); pixWrite("/tmp/junkout1", pixd1, IFF_PNG); pixWrite("/tmp/junkout2", pixd2, IFF_PNG); return 1; } pixDestroy(&pixse); pixDestroy(&pixd1); pixDestroy(&pixd2); selDestroy(&sel); } } pixDestroy(&pixs); return 0; }
/*! * pixOtsuThreshOnBackgroundNorm() * * Input: pixs (8 bpp grayscale; not colormapped) * pixim (<optional> 1 bpp 'image' mask; can be null) * sx, sy (tile size in pixels) * thresh (threshold for determining foreground) * mincount (min threshold on counts in a tile) * bgval (target bg val; typ. > 128) * smoothx (half-width of block convolution kernel width) * smoothy (half-width of block convolution kernel height) * scorefract (fraction of the max Otsu score; typ. 0.1) * &thresh (<optional return> threshold value that was * used on the normalized image) * Return: pixd (1 bpp thresholded image), or null on error * * Notes: * (1) This does background normalization followed by Otsu * thresholding. Otsu binarization attempts to split the * image into two roughly equal sets of pixels, and it does * a very poor job when there are large amounts of dark * background. By doing a background normalization first, * to get the background near 255, we remove this problem. * Then we use a modified Otsu to estimate the best global * threshold on the normalized image. * (2) See pixBackgroundNorm() for meaning and typical values * of input parameters. For a start, you can try: * sx, sy = 10, 15 * thresh = 100 * mincount = 50 * bgval = 255 * smoothx, smoothy = 2 */ PIX * pixOtsuThreshOnBackgroundNorm(PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 bgval, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, l_int32 *pthresh) { l_int32 w, h; l_uint32 val; PIX *pixn, *pixt, *pixd; PROCNAME("pixOtsuThreshOnBackgroundNorm"); if (pthresh) *pthresh = 0; if (!pixs || pixGetDepth(pixs) != 8) return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL); if (pixGetColormap(pixs)) return (PIX *)ERROR_PTR("pixs is colormapped", procName, NULL); if (sx < 4 || sy < 4) return (PIX *)ERROR_PTR("sx and sy must be >= 4", procName, NULL); if (mincount > sx * sy) { L_WARNING("mincount too large for tile size\n", procName); mincount = (sx * sy) / 3; } pixn = pixBackgroundNorm(pixs, pixim, NULL, sx, sy, thresh, mincount, bgval, smoothx, smoothy); if (!pixn) return (PIX *)ERROR_PTR("pixn not made", procName, NULL); /* Just use 1 tile for a global threshold, which is stored * as a single pixel in pixt. */ pixGetDimensions(pixn, &w, &h, NULL); pixOtsuAdaptiveThreshold(pixn, w, h, 0, 0, scorefract, &pixt, &pixd); pixDestroy(&pixn); if (pixt && pthresh) { pixGetPixel(pixt, 0, 0, &val); *pthresh = val; } pixDestroy(&pixt); if (!pixd) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); else return pixd; }
main(int argc, char **argv) { char *filein; l_int32 i, j, w, h, d, sampling; l_float32 rank, rval; l_uint32 val; NUMA *na, *nah, *nar, *nav; PIX *pix; static char mainName[] = "numaranktest"; if (argc != 3) exit(ERROR_INT(" Syntax: numaranktest filein sampling", mainName, 1)); filein = argv[1]; sampling = atoi(argv[2]); if ((pix = pixRead(filein)) == NULL) exit(ERROR_INT("pix not made", mainName, 1)); pixGetDimensions(pix, &w, &h, &d); if (d != 8) return ERROR_INT("d != 8 bpp", mainName, 1); na = numaCreate(0); for (i = 0; i < h; i += sampling) { for (j = 0; j < w; j += sampling) { pixGetPixel(pix, j, i, &val); numaAddNumber(na, val); } } nah = numaMakeHistogramClipped(na, BIN_SIZE, 255); nar = numaCreate(0); for (rval = 0.0; rval < 256.0; rval += 2.56) { numaHistogramGetRankFromVal(nah, rval, &rank); numaAddNumber(nar, rank); } gplotSimple1(nar, GPLOT_X11, "/tmp/junkroot1", "rank vs val"); nav = numaCreate(0); for (rank = 0.0; rank <= 1.0; rank += 0.01) { numaHistogramGetValFromRank(nah, rank, &rval); numaAddNumber(nav, rval); } gplotSimple1(nav, GPLOT_X11, "/tmp/junkroot2", "val vs rank"); pixDestroy(&pix); numaDestroy(&na); numaDestroy(&nah); numaDestroy(&nar); numaDestroy(&nav); return 0; }
/*! * localSearchForBackground() * * Input: &x, &y (starting position for search; return found position) * maxrad (max distance to search from starting location) * Return: 0 if bg pixel found; 1 if not found */ static l_int32 localSearchForBackground(PIX *pix, l_int32 *px, l_int32 *py, l_int32 maxrad) { l_int32 x, y, w, h, r, i, j; l_uint32 val; x = *px; y = *py; pixGetPixel(pix, x, y, &val); if (val == 0) return 0; /* For each value of r, restrict the search to the boundary * pixels in a square centered on (x,y), clipping to the * image boundaries if necessary. */ pixGetDimensions(pix, &w, &h, NULL); for (r = 1; r < maxrad; r++) { for (i = -r; i <= r; i++) { if (y + i < 0 || y + i >= h) continue; for (j = -r; j <= r; j++) { if (x + j < 0 || x + j >= w) continue; if (L_ABS(i) != r && L_ABS(j) != r) /* not on "r ring" */ continue; pixGetPixel(pix, x + j, y + i, &val); if (val == 0) { *px = x + j; *py = y + i; return 0; } } } } return 1; }
int main(){ int i,j,k, n,pixl, count=0; PIX *pixs; glob_t *flist; FILE *file=fopen("train_file.trn","w"); for(n=0;n<10;n++){ flist=list_files(n); if(flist->gl_pathc==0) { fprintf(stderr,"No files in directory %d... Exiting...\n",n); exit(1); } for(i=0;i<flist->gl_pathc;i++){ pixs=loadimage(flist->gl_pathv[i]); if(pixs==NULL){ fprintf(stderr,"Pix error... Exiting...\n"); exit(1); } for(j=0;j<pixGetHeight(pixs);j++){ for(k=0;k<pixGetWidth(pixs);k++){ pixGetPixel(pixs,k,j,&pixl); fprintf(file,"%d",pixl); /* funny transformation of rows/columns. TODO: works with intels, but others??? */ } fprintf(file,"\n"); } count++; fprintf(file," %d\n",n); } globfree(flist); free(flist); } fclose(file); // *file=fopen("train_file.trn","r"); FILE *file1=fopen("train_file_header.trn","w"); insert_header(file1,count); fclose(file1); return (0); }
/*! * adjacentOnPixelInRaster() * * Input: pixs (1 bpp) * x, y (current pixel) * xa, ya (adjacent ON pixel, found by simple CCW search) * Return: 1 if a pixel is found; 0 otherwise or on error * * Notes: * (1) Search is in 4-connected directions first; then on diagonals. * This allows traversal along a 4-connected boundary. */ l_int32 adjacentOnPixelInRaster(PIX *pixs, l_int32 x, l_int32 y, l_int32 *pxa, l_int32 *pya) { l_int32 w, h, i, xa, ya, found; l_int32 xdel[] = {-1, 0, 1, 0, -1, 1, 1, -1}; l_int32 ydel[] = {0, 1, 0, -1, 1, 1, -1, -1}; l_uint32 val; PROCNAME("adjacentOnPixelInRaster"); if (!pixs) return ERROR_INT("pixs not defined", procName, 0); if (pixGetDepth(pixs) != 1) return ERROR_INT("pixs not 1 bpp", procName, 0); w = pixGetWidth(pixs); h = pixGetHeight(pixs); found = 0; for (i = 0; i < 8; i++) { xa = x + xdel[i]; ya = y + ydel[i]; if (xa < 0 || xa >= w || ya < 0 || ya >= h) continue; pixGetPixel(pixs, xa, ya, &val); if (val == 1) { found = 1; *pxa = xa; *pya = ya; break; } } return found; }
char* _process_frame_white_basic(struct lib_hardsubx_ctx *ctx, AVFrame *frame, int width, int height, int index) { //printf("frame : %04d\n", index); PIX *im; PIX *edge_im; PIX *lum_im; PIX *feat_im; char *subtitle_text=NULL; im = pixCreate(width,height,32); lum_im = pixCreate(width,height,32); feat_im = pixCreate(width,height,32); int i,j; for(i=(3*height)/4;i<height;i++) { for(j=0;j<width;j++) { int p=j*3+i*frame->linesize[0]; int r=frame->data[0][p]; int g=frame->data[0][p+1]; int b=frame->data[0][p+2]; pixSetRGBPixel(im,j,i,r,g,b); float L,A,B; rgb_to_lab((float)r,(float)g,(float)b,&L,&A,&B); if(L > ctx->lum_thresh) pixSetRGBPixel(lum_im,j,i,255,255,255); else pixSetRGBPixel(lum_im,j,i,0,0,0); } } //Handle the edge image edge_im = pixCreate(width,height,8); edge_im = pixConvertRGBToGray(im,0.0,0.0,0.0); edge_im = pixSobelEdgeFilter(edge_im, L_VERTICAL_EDGES); edge_im = pixDilateGray(edge_im, 21, 11); edge_im = pixThresholdToBinary(edge_im,50); for(i=3*(height/4);i<height;i++) { for(j=0;j<width;j++) { unsigned int p1,p2,p3; pixGetPixel(edge_im,j,i,&p1); // pixGetPixel(pixd,j,i,&p2); pixGetPixel(lum_im,j,i,&p3); if(p1==0&&p3>0) pixSetRGBPixel(feat_im,j,i,255,255,255); else pixSetRGBPixel(feat_im,j,i,0,0,0); } } if(ctx->detect_italics) { ctx->ocr_mode = HARDSUBX_OCRMODE_WORD; } // TESSERACT OCR FOR THE FRAME HERE switch(ctx->ocr_mode) { case HARDSUBX_OCRMODE_WORD: if(ctx->conf_thresh > 0) subtitle_text = get_ocr_text_wordwise_threshold(ctx, lum_im, ctx->conf_thresh); else subtitle_text = get_ocr_text_wordwise(ctx, lum_im); break; case HARDSUBX_OCRMODE_LETTER: if(ctx->conf_thresh > 0) subtitle_text = get_ocr_text_letterwise_threshold(ctx, lum_im, ctx->conf_thresh); else subtitle_text = get_ocr_text_letterwise(ctx, lum_im); break; case HARDSUBX_OCRMODE_FRAME: if(ctx->conf_thresh > 0) subtitle_text = get_ocr_text_simple_threshold(ctx, lum_im, ctx->conf_thresh); else subtitle_text = get_ocr_text_simple(ctx, lum_im); break; default: fatal(EXIT_MALFORMED_PARAMETER,"Invalid OCR Mode"); } pixDestroy(&lum_im); pixDestroy(&im); pixDestroy(&edge_im); pixDestroy(&feat_im); return subtitle_text; }
char *_process_frame_color_basic(struct lib_hardsubx_ctx *ctx, AVFrame *frame, int width, int height, int index) { char *subtitle_text=NULL; PIX *im; im = pixCreate(width,height,32); PIX *hue_im = pixCreate(width,height,32); int i,j; for(i=0;i<height;i++) { for(j=0;j<width;j++) { int p=j*3+i*frame->linesize[0]; int r=frame->data[0][p]; int g=frame->data[0][p+1]; int b=frame->data[0][p+2]; pixSetRGBPixel(im,j,i,r,g,b); float H,S,V; rgb_to_hsv((float)r,(float)g,(float)b,&H,&S,&V); if(abs(H-ctx->hue)<20) { pixSetRGBPixel(hue_im,j,i,r,g,b); } } } PIX *edge_im = pixCreate(width,height,8),*edge_im_2 = pixCreate(width,height,8); edge_im = pixConvertRGBToGray(im,0.0,0.0,0.0); edge_im = pixSobelEdgeFilter(edge_im, L_VERTICAL_EDGES); edge_im = pixDilateGray(edge_im, 21, 1); edge_im = pixThresholdToBinary(edge_im,50); PIX *pixd = pixCreate(width,height,1); pixSauvolaBinarize(pixConvertRGBToGray(hue_im,0.0,0.0,0.0), 15, 0.3, 1, NULL, NULL, NULL, &pixd); edge_im_2 = pixConvertRGBToGray(hue_im,0.0,0.0,0.0); edge_im_2 = pixDilateGray(edge_im_2, 5, 5); PIX *feat_im = pixCreate(width,height,32); for(i=3*(height/4);i<height;i++) { for(j=0;j<width;j++) { unsigned int p1,p2,p3,p4; pixGetPixel(edge_im,j,i,&p1); pixGetPixel(pixd,j,i,&p2); // pixGetPixel(hue_im,j,i,&p3); pixGetPixel(edge_im_2,j,i,&p4); if(p1==0&&p2==0&&p4>0)//if(p4>0&&p1==0)//if(p2==0&&p1==0&&p3>0) { pixSetRGBPixel(feat_im,j,i,255,255,255); } } } if(ctx->detect_italics) { ctx->ocr_mode = HARDSUBX_OCRMODE_WORD; } // TESSERACT OCR FOR THE FRAME HERE switch(ctx->ocr_mode) { case HARDSUBX_OCRMODE_WORD: if(ctx->conf_thresh > 0) subtitle_text = get_ocr_text_wordwise_threshold(ctx, feat_im, ctx->conf_thresh); else subtitle_text = get_ocr_text_wordwise(ctx, feat_im); break; case HARDSUBX_OCRMODE_LETTER: if(ctx->conf_thresh > 0) subtitle_text = get_ocr_text_letterwise_threshold(ctx, feat_im, ctx->conf_thresh); else subtitle_text = get_ocr_text_letterwise(ctx, feat_im); break; case HARDSUBX_OCRMODE_FRAME: if(ctx->conf_thresh > 0) subtitle_text = get_ocr_text_simple_threshold(ctx, feat_im, ctx->conf_thresh); else subtitle_text = get_ocr_text_simple(ctx, feat_im); break; default: fatal(EXIT_MALFORMED_PARAMETER,"Invalid OCR Mode"); } pixDestroy(&feat_im); pixDestroy(&im); pixDestroy(&edge_im); pixDestroy(&hue_im); return subtitle_text; }
/*! * pixMaskedThreshOnBackgroundNorm() * * Input: pixs (8 bpp grayscale; not colormapped) * pixim (<optional> 1 bpp 'image' mask; can be null) * sx, sy (tile size in pixels) * thresh (threshold for determining foreground) * mincount (min threshold on counts in a tile) * smoothx (half-width of block convolution kernel width) * smoothy (half-width of block convolution kernel height) * scorefract (fraction of the max Otsu score; typ. ~ 0.1) * &thresh (<optional return> threshold value that was * used on the normalized image) * Return: pixd (1 bpp thresholded image), or null on error * * Notes: * (1) This begins with a standard background normalization. * Additionally, there is a flexible background norm, that * will adapt to a rapidly varying background, and this * puts white pixels in the background near regions with * significant foreground. The white pixels are turned into * a 1 bpp selection mask by binarization followed by dilation. * Otsu thresholding is performed on the input image to get an * estimate of the threshold in the non-mask regions. * The background normalized image is thresholded with two * different values, and the result is combined using * the selection mask. * (2) Note that the numbers 255 (for bgval target) and 190 (for * thresholding on pixn) are tied together, and explicitly * defined in this function. * (3) See pixBackgroundNorm() for meaning and typical values * of input parameters. For a start, you can try: * sx, sy = 10, 15 * thresh = 100 * mincount = 50 * smoothx, smoothy = 2 */ PIX * pixMaskedThreshOnBackgroundNorm(PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, l_int32 *pthresh) { l_int32 w, h; l_uint32 val; PIX *pixn, *pixm, *pixd, *pixt1, *pixt2, *pixt3, *pixt4; PROCNAME("pixMaskedThreshOnBackgroundNorm"); if (pthresh) *pthresh = 0; if (!pixs || pixGetDepth(pixs) != 8) return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL); if (pixGetColormap(pixs)) return (PIX *)ERROR_PTR("pixs is colormapped", procName, NULL); if (sx < 4 || sy < 4) return (PIX *)ERROR_PTR("sx and sy must be >= 4", procName, NULL); if (mincount > sx * sy) { L_WARNING("mincount too large for tile size\n", procName); mincount = (sx * sy) / 3; } /* Standard background normalization */ pixn = pixBackgroundNorm(pixs, pixim, NULL, sx, sy, thresh, mincount, 255, smoothx, smoothy); if (!pixn) return (PIX *)ERROR_PTR("pixn not made", procName, NULL); /* Special background normalization for adaptation to quickly * varying background. Threshold on the very light parts, * which tend to be near significant edges, and dilate to * form a mask over regions that are typically text. The * dilation size is chosen to cover the text completely, * except for very thick fonts. */ pixt1 = pixBackgroundNormFlex(pixs, 7, 7, 1, 1, 20); pixt2 = pixThresholdToBinary(pixt1, 240); pixInvert(pixt2, pixt2); pixm = pixMorphSequence(pixt2, "d21.21", 0); pixDestroy(&pixt1); pixDestroy(&pixt2); /* Use Otsu to get a global threshold estimate for the image, * which is stored as a single pixel in pixt3. */ pixGetDimensions(pixs, &w, &h, NULL); pixOtsuAdaptiveThreshold(pixs, w, h, 0, 0, scorefract, &pixt3, NULL); if (pixt3 && pthresh) { pixGetPixel(pixt3, 0, 0, &val); *pthresh = val; } pixDestroy(&pixt3); /* Threshold the background normalized images differentially, * using a high value correlated with the background normalization * for the part of the image under the mask (i.e., near the * darker, thicker foreground), and a value that depends on the Otsu * threshold for the rest of the image. This gives a solid * (high) thresholding for the foreground parts of the image, * while allowing the background and light foreground to be * reasonably well cleaned using a threshold adapted to the * input image. */ pixd = pixThresholdToBinary(pixn, val + 30); /* for bg and light fg */ pixt4 = pixThresholdToBinary(pixn, 190); /* for heavier fg */ pixCombineMasked(pixd, pixt4, pixm); pixDestroy(&pixt4); pixDestroy(&pixm); pixDestroy(&pixn); if (!pixd) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); else return pixd; }
int main(int argc, char **argv) { l_int32 i, j, w, h, same, width, height, cx, cy; l_uint32 val; BOX *box; PIX *pix0, *pixs, *pixse, *pixd1, *pixd2; SEL *sel; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pix0 = pixRead("feyn-fract.tif"); box = boxCreate(293, 37, pixGetWidth(pix0) - 691, pixGetHeight(pix0) -145); pixs = pixClipRectangle(pix0, box, NULL); boxDestroy(&box); if (rp->display) pixDisplay(pixs, 100, 100); /* Test 63 different sizes */ for (width = 1; width <= 25; width += 3) { /* 9 values */ for (height = 1; height <= 25; height += 4) { /* 7 values */ cx = width / 2; cy = height / 2; /* Dilate using an actual sel */ sel = selCreateBrick(height, width, cy, cx, SEL_HIT); pixd1 = pixDilate(NULL, pixs, sel); /* Dilate using a pix as a sel */ pixse = pixCreate(width, height, 1); pixSetAll(pixse); pixd2 = pixCopy(NULL, pixs); w = pixGetWidth(pixs); h = pixGetHeight(pixs); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val); if (val) pixRasterop(pixd2, j - cx, i - cy, width, height, PIX_SRC | PIX_DST, pixse, 0, 0); } } pixEqual(pixd1, pixd2, &same); regTestCompareValues(rp, 1, same, 0.0); /* 0 - 62 */ if (same == 0) { fprintf(stderr, "Results differ for SE (width,height) = (%d,%d)\n", width, height); } pixDestroy(&pixse); pixDestroy(&pixd1); pixDestroy(&pixd2); selDestroy(&sel); } } pixDestroy(&pix0); pixDestroy(&pixs); return regTestCleanup(rp); }
/*! * pixOtsuAdaptiveThreshold() * * Input: pixs (8 bpp) * sx, sy (desired tile dimensions; actual size may vary) * smoothx, smoothy (half-width of convolution kernel applied to * threshold array: use 0 for no smoothing) * scorefract (fraction of the max Otsu score; typ. 0.1; * use 0.0 for standard Otsu) * &pixth (<optional return> array of threshold values * found for each tile) * &pixd (<optional return> thresholded input pixs, based on * the threshold array) * Return: 0 if OK, 1 on error * * Notes: * (1) The Otsu method finds a single global threshold for an image. * This function allows a locally adapted threshold to be * found for each tile into which the image is broken up. * (2) The array of threshold values, one for each tile, constitutes * a highly downscaled image. This array is optionally * smoothed using a convolution. The full width and height of the * convolution kernel are (2 * @smoothx + 1) and (2 * @smoothy + 1). * (3) The minimum tile dimension allowed is 16. If such small * tiles are used, it is recommended to use smoothing, because * without smoothing, each small tile determines the splitting * threshold independently. A tile that is entirely in the * image bg will then hallucinate fg, resulting in a very noisy * binarization. The smoothing should be large enough that no * tile is only influenced by one type (fg or bg) of pixels, * because it will force a split of its pixels. * (4) To get a single global threshold for the entire image, use * input values of @sx and @sy that are larger than the image. * For this situation, the smoothing parameters are ignored. * (5) The threshold values partition the image pixels into two classes: * one whose values are less than the threshold and another * whose values are greater than or equal to the threshold. * This is the same use of 'threshold' as in pixThresholdToBinary(). * (6) The scorefract is the fraction of the maximum Otsu score, which * is used to determine the range over which the histogram minimum * is searched. See numaSplitDistribution() for details on the * underlying method of choosing a threshold. * (7) This uses enables a modified version of the Otsu criterion for * splitting the distribution of pixels in each tile into a * fg and bg part. The modification consists of searching for * a minimum in the histogram over a range of pixel values where * the Otsu score is within a defined fraction, @scorefract, * of the max score. To get the original Otsu algorithm, set * @scorefract == 0. */ l_int32 pixOtsuAdaptiveThreshold(PIX *pixs, l_int32 sx, l_int32 sy, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, PIX **ppixth, PIX **ppixd) { l_int32 w, h, nx, ny, i, j, thresh; l_uint32 val; PIX *pixt, *pixb, *pixthresh, *pixth, *pixd; PIXTILING *pt; PROCNAME("pixOtsuAdaptiveThreshold"); if (!ppixth && !ppixd){ return ERROR_INT("neither &pixth nor &pixd defined", procName, 1); LOGE("neither &pixth nor &pixd defined"); } if (ppixth) *ppixth = NULL; if (ppixd) *ppixd = NULL; if (!pixs || pixGetDepth(pixs) != 8){ return ERROR_INT("pixs not defined or not 8 bpp", procName, 1); LOGE("pixs not defined or not 8 bpp"); } if (sx < 16 || sy < 16){ return ERROR_INT("sx and sy must be >= 16", procName, 1); LOGE("sx and sy must be >= 16"); } /* Compute the threshold array for the tiles */ pixGetDimensions(pixs, &w, &h, NULL); nx = L_MAX(1, w / sx); ny = L_MAX(1, h / sy); smoothx = L_MIN(smoothx, (nx - 1) / 2); smoothy = L_MIN(smoothy, (ny - 1) / 2); pt = pixTilingCreate(pixs, nx, ny, 0, 0, 0, 0); pixthresh = pixCreate(nx, ny, 8); for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { pixt = pixTilingGetTile(pt, i, j); pixSplitDistributionFgBg(pixt, scorefract, 1, &thresh, NULL, NULL, 0); pixSetPixel(pixthresh, j, i, thresh); /* see note (4) */ pixDestroy(&pixt); } } /* Optionally smooth the threshold array */ if (smoothx > 0 || smoothy > 0) pixth = pixBlockconv(pixthresh, smoothx, smoothy); else pixth = pixClone(pixthresh); pixDestroy(&pixthresh); /* Optionally apply the threshold array to binarize pixs */ if (ppixd) { pixd = pixCreate(w, h, 1); for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { pixt = pixTilingGetTile(pt, i, j); pixGetPixel(pixth, j, i, &val); pixb = pixThresholdToBinary(pixt, val); pixTilingPaintTile(pixd, i, j, pixb, pt); pixDestroy(&pixt); pixDestroy(&pixb); } } *ppixd = pixd; } if (ppixth) *ppixth = pixth; else pixDestroy(&pixth); pixTilingDestroy(&pt); return 0; }
int main(int argc, char **argv) { l_int32 w, h, ystart, yend, y, ymax, ymid, i, window, sum1, sum2, rankx; l_uint32 uval; l_float32 ave, rankval, maxvar, variance, norm, conf, angle, radangle; NUMA *na1; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7; PIXA *pixa; static char mainName[] = "findbinding"; if (argc != 1) return ERROR_INT(" Syntax: findbinding", mainName, 1); lept_mkdir("lept/binding"); pixa = pixaCreate(0); pix1 = pixRead("binding-example.45.jpg"); pix2 = pixConvertTo8(pix1, 0); /* Find the skew angle */ pix3 = pixConvertTo1(pix2, 150); pixFindSkewSweepAndSearch(pix3, &angle, &conf, 2, 2, 7.0, 1.0, 0.01); fprintf(stderr, "angle = %f, conf = %f\n", angle, conf); /* Deskew, bringing in black pixels at the edges */ if (L_ABS(angle) < 0.1 || conf < 1.5) { pix4 = pixClone(pix2); } else { radangle = 3.1416 * angle / 180.0; pix4 = pixRotate(pix2, radangle, L_ROTATE_AREA_MAP, L_BRING_IN_BLACK, 0, 0); } /* Rotate 90 degrees to make binding horizontal */ pix5 = pixRotateOrth(pix4, 1); /* Sort pixels in each row by their gray value. * Dark pixels on the left, light ones on the right. */ pix6 = pixRankRowTransform(pix5); pixDisplay(pix5, 0, 0); pixDisplay(pix6, 550, 0); pixaAddPix(pixa, pix4, L_COPY); pixaAddPix(pixa, pix5, L_COPY); pixaAddPix(pixa, pix6, L_COPY); /* Make an a priori estimate of the y-interval within which the * binding will be found. The search will be done in this interval. */ pixGetDimensions(pix6, &w, &h, NULL); ystart = 0.25 * h; yend = 0.75 * h; /* Choose a very light rank value; close to white, which * corresponds to a column in pix6 near the right side. */ rankval = 0.98; rankx = (l_int32)(w * rankval); /* Investigate variance in a small window (vertical, size = 5) * of the pixels in that column. These are the %rankval * pixels in each raster of pix6. Find the y-location of * maximum variance. */ window = 5; norm = 1.0 / window; maxvar = 0.0; na1 = numaCreate(0); numaSetParameters(na1, ystart, 1); for (y = ystart; y <= yend; y++) { sum1 = sum2 = 0; for (i = 0; i < window; i++) { pixGetPixel(pix6, rankx, y + i, &uval); sum1 += uval; sum2 += uval * uval; } ave = norm * sum1; variance = norm * sum2 - ave * ave; numaAddNumber(na1, variance); ymid = y + window / 2; if (variance > maxvar) { maxvar = variance; ymax = ymid; } } /* Plot the windowed variance as a function of the y-value * of the window location */ fprintf(stderr, "maxvar = %f, ymax = %d\n", maxvar, ymax); gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/binding/root", NULL); pix7 = pixRead("/tmp/lept/binding/root.png"); pixDisplay(pix7, 0, 800); pixaAddPix(pixa, pix7, L_COPY); /* Superimpose the variance plot over the image. * The variance peak is at the binding. */ pixRenderPlotFromNumaGen(&pix5, na1, L_VERTICAL_LINE, 3, w - 120, 100, 1, 0x0000ff00); pixDisplay(pix5, 1050, 0); pixaAddPix(pixa, pix5, L_COPY); /* Bundle the results up in a pdf */ fprintf(stderr, "Writing pdf output file: /tmp/lept/binding/binding.pdf\n"); pixaConvertToPdf(pixa, 45, 1.0, 0, 0, "Binding locator", "/tmp/lept/binding/binding.pdf"); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); pixDestroy(&pix6); pixDestroy(&pix7); pixaDestroy(&pixa); numaDestroy(&na1); return 0; }
int main(int argc, char **argv) { char *filein, *fileout; l_int32 i; l_uint32 val; l_float32 size; PIX *pixs, *pixd, *pixm, *pixmi, *pixt1, *pixt2, *pixt3; static char mainName[] = "seedfilltest"; if (argc != 3) return ERROR_INT(" Syntax: seedfilltest filein fileout", mainName, 1); filein = argv[1]; fileout = argv[2]; pixd = NULL; if ((pixm = pixRead(filein)) == NULL) return ERROR_INT("pixm not made", mainName, 1); pixmi = pixInvert(NULL, pixm); size = pixGetWidth(pixm) * pixGetHeight(pixm); pixs = pixCreateTemplate(pixm); for (i = 0; i < 100; i++) { pixGetPixel(pixm, XS + 5 * i, YS + 5 * i, &val); if (val == 0) break; } if (i == 100) return ERROR_INT("no seed pixel found", mainName, 1); pixSetPixel(pixs, XS + 5 * i, YS + 5 * i, 1); #if 0 /* hole filling; use "hole-filler.png" */ pixt1 = pixHDome(pixmi, 100, 4); pixt2 = pixThresholdToBinary(pixt1, 10); /* pixInvert(pixt1, pixt1); */ pixDisplay(pixt1, 100, 500); pixDisplay(pixt2, 600, 500); pixt3 = pixHolesByFilling(pixt2, 4); pixDilateBrick(pixt3, pixt3, 7, 7); pixd = pixConvertTo8(pixt3, FALSE); pixDisplay(pixd, 0, 100); pixSeedfillGray(pixd, pixmi, CONNECTIVITY); pixInvert(pixd, pixd); pixDisplay(pixmi, 500, 100); pixDisplay(pixd, 1000, 100); pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); #endif #if 0 /* hole filling; use "hole-filler.png" */ pixt1 = pixThresholdToBinary(pixm, 110); pixInvert(pixt1, pixt1); pixDisplay(pixt1, 100, 500); pixt2 = pixHolesByFilling(pixt1, 4); pixd = pixConvertTo8(pixt2, FALSE); pixDisplay(pixd, 0, 100); pixSeedfillGray(pixd, pixmi, CONNECTIVITY); pixInvert(pixd, pixd); pixDisplay(pixmi, 500, 100); pixDisplay(pixd, 1000, 100); pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); #endif #if 0 /* hole filling; use "hole-filler.png" */ pixd = pixInvert(NULL, pixm); pixAddConstantGray(pixd, -50); pixDisplay(pixd, 0, 100); /* pixt1 = pixThresholdToBinary(pixd, 20); pixDisplayWithTitle(pixt1, 600, 600, "pixt1", DFLAG); */ pixSeedfillGray(pixd, pixmi, CONNECTIVITY); /* pixInvert(pixd, pixd); */ pixDisplay(pixmi, 500, 100); pixDisplay(pixd, 1000, 100); pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); #endif #if 0 /* test in-place seedfill for speed */ pixd = pixClone(pixs); startTimer(); pixSeedfillBinary(pixs, pixs, pixmi, CONNECTIVITY); fprintf(stderr, "Filling rate: %7.4f Mpix/sec\n", (size/1000000.) / stopTimer()); pixWrite(fileout, pixd, IFF_PNG); pixOr(pixd, pixd, pixm); pixWrite("/tmp/junkout1.png", pixd, IFF_PNG); #endif #if 0 /* test seedfill to dest for speed */ pixd = pixCreateTemplate(pixm); startTimer(); for (i = 0; i < NTIMES; i++) { pixSeedfillBinary(pixd, pixs, pixmi, CONNECTIVITY); } fprintf(stderr, "Filling rate: %7.4f Mpix/sec\n", (size/1000000.) * NTIMES / stopTimer()); pixWrite(fileout, pixd, IFF_PNG); pixOr(pixd, pixd, pixm); pixWrite("/tmp/junkout1.png", pixd, IFF_PNG); #endif /* use same connectivity to compare with the result of the * slow parallel operation */ #if 1 pixDestroy(&pixd); pixd = pixSeedfillMorph(pixs, pixmi, 100, CONNECTIVITY); pixOr(pixd, pixd, pixm); pixWrite("/tmp/junkout2.png", pixd, IFF_PNG); #endif pixDestroy(&pixs); pixDestroy(&pixm); pixDestroy(&pixmi); pixDestroy(&pixd); return 0; }
int main(int argc, char **argv) { l_int32 i, j, k, w, h, w2, w4, w8, w16, w32, wpl; l_int32 count1, count2, count3; l_uint32 val32, val1, val2; l_uint32 *data1, *line1, *data2, *line2; void **lines1, **linet1, **linet2; PIX *pixs, *pix1, *pix2; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("feyn-fract.tif"); pixGetDimensions(pixs, &w, &h, NULL); data1 = pixGetData(pixs); wpl = pixGetWpl(pixs); lines1 = pixGetLinePtrs(pixs, NULL); /* Get timing for the 3 different methods */ startTimer(); for (k = 0; k < 10; k++) { count1 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (GET_DATA_BIT(lines1[i], j)) count1++; } } } fprintf(stderr, "Time with line ptrs = %5.3f sec, count1 = %d\n", stopTimer(), count1); startTimer(); for (k = 0; k < 10; k++) { count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; for (j = 0; j < w; j++) { if (l_getDataBit(line1, j)) count2++; } } } fprintf(stderr, "Time with l_get* = %5.3f sec, count2 = %d\n", stopTimer(), count2); startTimer(); for (k = 0; k < 10; k++) { count3 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val32); count3 += val32; } } } fprintf(stderr, "Time with pixGetPixel() = %5.3f sec, count3 = %d\n", stopTimer(), count3); pix1 = pixCreateTemplate(pixs); linet1 = pixGetLinePtrs(pix1, NULL); pix2 = pixCreateTemplate(pixs); data2 = pixGetData(pix2); linet2 = pixGetLinePtrs(pix2, NULL); /* ------------------------------------------------- */ /* Test different methods for 1 bpp */ /* ------------------------------------------------- */ count1 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { val1 = GET_DATA_BIT(lines1[i], j); count1 += val1; if (val1) SET_DATA_BIT(linet1[i], j); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w; j++) { val2 = l_getDataBit(line1, j); count2 += val2; if (val2) l_setDataBit(line2, j); } } CompareResults(pixs, pix1, pix2, count1, count2, "1 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 2 bpp */ /* ------------------------------------------------- */ count1 = 0; w2 = w / 2; for (i = 0; i < h; i++) { for (j = 0; j < w2; j++) { val1 = GET_DATA_DIBIT(lines1[i], j); count1 += val1; val1 += 0xbbbbbbbc; SET_DATA_DIBIT(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w2; j++) { val2 = l_getDataDibit(line1, j); count2 += val2; val2 += 0xbbbbbbbc; l_setDataDibit(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "2 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 4 bpp */ /* ------------------------------------------------- */ count1 = 0; w4 = w / 4; for (i = 0; i < h; i++) { for (j = 0; j < w4; j++) { val1 = GET_DATA_QBIT(lines1[i], j); count1 += val1; val1 += 0xbbbbbbb0; SET_DATA_QBIT(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w4; j++) { val2 = l_getDataQbit(line1, j); count2 += val2; val2 += 0xbbbbbbb0; l_setDataQbit(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "4 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 8 bpp */ /* ------------------------------------------------- */ count1 = 0; w8 = w / 8; for (i = 0; i < h; i++) { for (j = 0; j < w8; j++) { val1 = GET_DATA_BYTE(lines1[i], j); count1 += val1; val1 += 0xbbbbbb00; SET_DATA_BYTE(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w8; j++) { val2 = l_getDataByte(line1, j); count2 += val2; val2 += 0xbbbbbb00; l_setDataByte(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "8 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 16 bpp */ /* ------------------------------------------------- */ count1 = 0; w16 = w / 16; for (i = 0; i < h; i++) { for (j = 0; j < w16; j++) { val1 = GET_DATA_TWO_BYTES(lines1[i], j); count1 += val1; val1 += 0xbbbb0000; SET_DATA_TWO_BYTES(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w16; j++) { val2 = l_getDataTwoBytes(line1, j); count2 += val2; val2 += 0xbbbb0000; l_setDataTwoBytes(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "16 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 32 bpp */ /* ------------------------------------------------- */ count1 = 0; w32 = w / 32; for (i = 0; i < h; i++) { for (j = 0; j < w32; j++) { val1 = GET_DATA_FOUR_BYTES(lines1[i], j); count1 += val1 & 0xfff; SET_DATA_FOUR_BYTES(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w32; j++) { val2 = l_getDataFourBytes(line1, j); count2 += val2 & 0xfff; l_setDataFourBytes(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "32 bpp", rp); pixDestroy(&pixs); pixDestroy(&pix1); pixDestroy(&pix2); lept_free(lines1); lept_free(linet1); lept_free(linet2); return regTestCleanup(rp); }
void _display_frame(struct lib_hardsubx_ctx *ctx, AVFrame *frame, int width, int height, int timestamp) { // Debug: Display the frame after processing PIX *im; im = pixCreate(width,height,32); PIX *hue_im = pixCreate(width,height,32); int i,j; for(i=0;i<height;i++) { for(j=0;j<width;j++) { int p=j*3+i*frame->linesize[0]; int r=frame->data[0][p]; int g=frame->data[0][p+1]; int b=frame->data[0][p+2]; pixSetRGBPixel(im,j,i,r,g,b); float H,S,V; rgb_to_hsv((float)r,(float)g,(float)b,&H,&S,&V); if(abs(H-ctx->hue)<20) { pixSetRGBPixel(hue_im,j,i,r,g,b); } } } PIX *edge_im = pixCreate(width,height,8),*edge_im_2 = pixCreate(width,height,8); edge_im = pixConvertRGBToGray(im,0.0,0.0,0.0); edge_im = pixSobelEdgeFilter(edge_im, L_VERTICAL_EDGES); edge_im = pixDilateGray(edge_im, 21, 1); edge_im = pixThresholdToBinary(edge_im,50); PIX *pixd = pixCreate(width,height,1); pixSauvolaBinarize(pixConvertRGBToGray(hue_im,0.0,0.0,0.0), 15, 0.3, 1, NULL, NULL, NULL, &pixd); edge_im_2 = pixConvertRGBToGray(hue_im,0.0,0.0,0.0); edge_im_2 = pixDilateGray(edge_im_2, 5, 5); PIX *feat_im = pixCreate(width,height,32); for(i=3*(height/4);i<height;i++) { for(j=0;j<width;j++) { unsigned int p1,p2,p3,p4; pixGetPixel(edge_im,j,i,&p1); pixGetPixel(pixd,j,i,&p2); // pixGetPixel(hue_im,j,i,&p3); pixGetPixel(edge_im_2,j,i,&p4); if(p1==0&&p2==0&&p4>0)//if(p4>0&&p1==0)//if(p2==0&&p1==0&&p3>0) { pixSetRGBPixel(feat_im,j,i,255,255,255); } } } char *txt=NULL; // txt = get_ocr_text_simple(ctx, feat_im); // txt=get_ocr_text_wordwise_threshold(ctx, feat_im, ctx->conf_thresh); // if(txt != NULL)printf("%s\n", txt); pixDestroy(&im); pixDestroy(&edge_im); pixDestroy(&feat_im); pixDestroy(&edge_im_2); pixDestroy(&pixd); }
/*! * pixSearchBinaryMaze() * * Input: pixs (1 bpp, maze) * xi, yi (beginning point; use same initial point * that was used to generate the maze) * xf, yf (end point, or close to it) * &ppixd (<optional return> maze with path illustrated, or * if no path possible, the part of the maze * that was searched) * Return: pta (shortest path), or null if either no path * exists or on error * * Notes: * (1) Because of the overhead in calling pixGetPixel() and * pixSetPixel(), we have used raster line pointers and the * GET_DATA* and SET_DATA* macros for many of the pix accesses. * (2) Commentary: * The goal is to find the shortest path between beginning and * end points, without going through walls, and there are many * ways to solve this problem. * We use a queue to implement a breadth-first search. Two auxiliary * "image" data structures can be used: one to mark the visited * pixels and one to give the direction to the parent for each * visited pixels. The first structure is used to avoid putting * pixels on the queue more than once, and the second is used * for retracing back to the origin, like the breadcrumbs in * Hansel and Gretel. Each pixel taken off the queue is destroyed * after it is used to locate the allowed neighbors. In fact, * only one distance image is required, if you initialize it * to some value that signifies "not yet visited." (We use * a binary image for marking visited pixels because it is clearer.) * This method for a simple search of a binary maze is implemented in * searchBinaryMaze(). * An alternative method would store the (manhattan) distance * from the start point with each pixel on the queue. The children * of each pixel get a distance one larger than the parent. These * values can be stored in an auxiliary distance map image * that is constructed simultaneously with the search. Once the * end point is reached, the distance map is used to backtrack * along a minimum path. There may be several equal length * minimum paths, any one of which can be chosen this way. */ PTA * pixSearchBinaryMaze(PIX *pixs, l_int32 xi, l_int32 yi, l_int32 xf, l_int32 yf, PIX **ppixd) { l_int32 i, j, x, y, w, h, d, found; l_uint32 val, rpixel, gpixel, bpixel; void **lines1, **linem1, **linep8, **lined32; MAZEEL *el, *elp; PIX *pixd; /* the shortest path written on the maze image */ PIX *pixm; /* for bookkeeping, to indicate pixels already visited */ PIX *pixp; /* for bookkeeping, to indicate direction to parent */ L_QUEUE *lq; PTA *pta; PROCNAME("pixSearchBinaryMaze"); if (ppixd) *ppixd = NULL; if (!pixs) return (PTA *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 1) return (PTA *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (xi <= 0 || xi >= w) return (PTA *)ERROR_PTR("xi not valid", procName, NULL); if (yi <= 0 || yi >= h) return (PTA *)ERROR_PTR("yi not valid", procName, NULL); pixGetPixel(pixs, xi, yi, &val); if (val != 0) return (PTA *)ERROR_PTR("(xi,yi) not bg pixel", procName, NULL); pixd = NULL; pta = NULL; /* Find a bg pixel near input point (xf, yf) */ localSearchForBackground(pixs, &xf, &yf, 5); #if DEBUG_MAZE fprintf(stderr, "(xi, yi) = (%d, %d), (xf, yf) = (%d, %d)\n", xi, yi, xf, yf); #endif /* DEBUG_MAZE */ pixm = pixCreate(w, h, 1); /* initialized to OFF */ pixp = pixCreate(w, h, 8); /* direction to parent stored as enum val */ lines1 = pixGetLinePtrs(pixs, NULL); linem1 = pixGetLinePtrs(pixm, NULL); linep8 = pixGetLinePtrs(pixp, NULL); lq = lqueueCreate(0); /* Prime the queue with the first pixel; it is OFF */ el = mazeelCreate(xi, yi, 0); /* don't need direction here */ pixSetPixel(pixm, xi, yi, 1); /* mark visited */ lqueueAdd(lq, el); /* Fill up the pix storing directions to parents, * stopping when we hit the point (xf, yf) */ found = FALSE; while (lqueueGetCount(lq) > 0) { elp = (MAZEEL *)lqueueRemove(lq); x = elp->x; y = elp->y; if (x == xf && y == yf) { found = TRUE; FREE(elp); break; } if (x > 0) { /* check to west */ val = GET_DATA_BIT(linem1[y], x - 1); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y], x - 1); /* mark visited */ val = GET_DATA_BIT(lines1[y], x - 1); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST); /* parent E */ el = mazeelCreate(x - 1, y, 0); lqueueAdd(lq, el); } } } if (y > 0) { /* check north */ val = GET_DATA_BIT(linem1[y - 1], x); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y - 1], x); /* mark visited */ val = GET_DATA_BIT(lines1[y - 1], x); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH); /* parent S */ el = mazeelCreate(x, y - 1, 0); lqueueAdd(lq, el); } } } if (x < w - 1) { /* check east */ val = GET_DATA_BIT(linem1[y], x + 1); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y], x + 1); /* mark visited */ val = GET_DATA_BIT(lines1[y], x + 1); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST); /* parent W */ el = mazeelCreate(x + 1, y, 0); lqueueAdd(lq, el); } } } if (y < h - 1) { /* check south */ val = GET_DATA_BIT(linem1[y + 1], x); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y + 1], x); /* mark visited */ val = GET_DATA_BIT(lines1[y + 1], x); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH); /* parent N */ el = mazeelCreate(x, y + 1, 0); lqueueAdd(lq, el); } } } FREE(elp); } lqueueDestroy(&lq, TRUE); pixDestroy(&pixm); FREE(linem1); if (ppixd) { pixd = pixUnpackBinary(pixs, 32, 1); *ppixd = pixd; } composeRGBPixel(255, 0, 0, &rpixel); /* start point */ composeRGBPixel(0, 255, 0, &gpixel); composeRGBPixel(0, 0, 255, &bpixel); /* end point */ if (!found) { L_INFO(" No path found", procName); if (pixd) { /* paint all visited locations */ lined32 = pixGetLinePtrs(pixd, NULL); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { val = GET_DATA_BYTE(linep8[i], j); if (val != 0 && pixd) SET_DATA_FOUR_BYTES(lined32[i], j, gpixel); } } FREE(lined32); } } else { /* write path onto pixd */ L_INFO(" Path found", procName); pta = ptaCreate(0); x = xf; y = yf; while (1) { ptaAddPt(pta, x, y); if (x == xi && y == yi) break; if (pixd) pixSetPixel(pixd, x, y, gpixel); pixGetPixel(pixp, x, y, &val); if (val == DIR_NORTH) y--; else if (val == DIR_SOUTH) y++; else if (val == DIR_EAST) x++; else if (val == DIR_WEST) x--; } } if (pixd) { pixSetPixel(pixd, xi, yi, rpixel); pixSetPixel(pixd, xf, yf, bpixel); } pixDestroy(&pixp); FREE(lines1); FREE(linep8); return pta; }
/*! * generateBinaryMaze() * * Input: w, h (size of maze) * xi, yi (initial location) * wallps (probability that a pixel to the side is ON) * ranis (ratio of prob that pixel in forward direction * is a wall to the probability that pixel in * side directions is a wall) * Return: pix, or null on error * * Notes: * (1) We have two input probability factors that determine the * density of walls and average length of straight passages. * When ranis < 1.0, you are more likely to generate a wall * to the side than going forward. Enter 0.0 for either if * you want to use the default values. * (2) This is a type of percolation problem, and exhibits * different phases for different parameters wallps and ranis. * For larger values of these parameters, regions in the maze * are not explored because the maze generator walls them * off and cannot get through. The boundary between the * two phases in this two-dimensional parameter space goes * near these values: * wallps ranis * 0.35 1.00 * 0.40 0.85 * 0.45 0.70 * 0.50 0.50 * 0.55 0.40 * 0.60 0.30 * 0.65 0.25 * 0.70 0.19 * 0.75 0.15 * 0.80 0.11 * (3) Because here is a considerable amount of overhead in calling * pixGetPixel() and pixSetPixel(), this function can be sped * up with little effort using raster line pointers and the * GET_DATA* and SET_DATA* macros. */ PIX * generateBinaryMaze(l_int32 w, l_int32 h, l_int32 xi, l_int32 yi, l_float32 wallps, l_float32 ranis) { l_int32 x, y, dir; l_uint32 val; l_float32 frand, wallpf, testp; MAZEEL *el, *elp; PIX *pixd; /* the destination maze */ PIX *pixm; /* for bookkeeping, to indicate pixels already visited */ L_QUEUE *lq; /* On Windows, seeding is apparently necessary to get decent mazes. * Windows rand() returns a value up to 2^15 - 1, whereas unix * rand() returns a value up to 2^31 - 1. Therefore the generated * mazes will differ on the two platforms. */ #ifdef _WIN32 srand(28*333); #endif /* _WIN32 */ if (w < MIN_MAZE_WIDTH) w = MIN_MAZE_WIDTH; if (h < MIN_MAZE_HEIGHT) h = MIN_MAZE_HEIGHT; if (xi <= 0 || xi >= w) xi = w / 6; if (yi <= 0 || yi >= h) yi = h / 5; if (wallps < 0.05 || wallps > 0.95) wallps = DEFAULT_WALL_PROBABILITY; if (ranis < 0.05 || ranis > 1.0) ranis = DEFAULT_ANISOTROPY_RATIO; wallpf = wallps * ranis; #if DEBUG_MAZE fprintf(stderr, "(w, h) = (%d, %d), (xi, yi) = (%d, %d)\n", w, h, xi, yi); fprintf(stderr, "Using: prob(wall) = %7.4f, anisotropy factor = %7.4f\n", wallps, ranis); #endif /* DEBUG_MAZE */ /* These are initialized to OFF */ pixd = pixCreate(w, h, 1); pixm = pixCreate(w, h, 1); lq = lqueueCreate(0); /* Prime the queue with the first pixel; it is OFF */ el = mazeelCreate(xi, yi, START_LOC); pixSetPixel(pixm, xi, yi, 1); /* mark visited */ lqueueAdd(lq, el); /* While we're at it ... */ while (lqueueGetCount(lq) > 0) { elp = (MAZEEL *)lqueueRemove(lq); x = elp->x; y = elp->y; dir = elp->dir; if (x > 0) { /* check west */ pixGetPixel(pixm, x - 1, y, &val); if (val == 0) { /* not yet visited */ pixSetPixel(pixm, x - 1, y, 1); /* mark visited */ frand = (l_float32)rand() / (l_float32)RAND_MAX; testp = wallps; if (dir == DIR_WEST) testp = wallpf; if (frand <= testp) { /* make it a wall */ pixSetPixel(pixd, x - 1, y, 1); } else { /* not a wall */ el = mazeelCreate(x - 1, y, DIR_WEST); lqueueAdd(lq, el); } } } if (y > 0) { /* check north */ pixGetPixel(pixm, x, y - 1, &val); if (val == 0) { /* not yet visited */ pixSetPixel(pixm, x, y - 1, 1); /* mark visited */ frand = (l_float32)rand() / (l_float32)RAND_MAX; testp = wallps; if (dir == DIR_NORTH) testp = wallpf; if (frand <= testp) { /* make it a wall */ pixSetPixel(pixd, x, y - 1, 1); } else { /* not a wall */ el = mazeelCreate(x, y - 1, DIR_NORTH); lqueueAdd(lq, el); } } } if (x < w - 1) { /* check east */ pixGetPixel(pixm, x + 1, y, &val); if (val == 0) { /* not yet visited */ pixSetPixel(pixm, x + 1, y, 1); /* mark visited */ frand = (l_float32)rand() / (l_float32)RAND_MAX; testp = wallps; if (dir == DIR_EAST) testp = wallpf; if (frand <= testp) { /* make it a wall */ pixSetPixel(pixd, x + 1, y, 1); } else { /* not a wall */ el = mazeelCreate(x + 1, y, DIR_EAST); lqueueAdd(lq, el); } } } if (y < h - 1) { /* check south */ pixGetPixel(pixm, x, y + 1, &val); if (val == 0) { /* not yet visited */ pixSetPixel(pixm, x, y + 1, 1); /* mark visited */ frand = (l_float32)rand() / (l_float32)RAND_MAX; testp = wallps; if (dir == DIR_SOUTH) testp = wallpf; if (frand <= testp) { /* make it a wall */ pixSetPixel(pixd, x, y + 1, 1); } else { /* not a wall */ el = mazeelCreate(x, y + 1, DIR_SOUTH); lqueueAdd(lq, el); } } } FREE(elp); } lqueueDestroy(&lq, TRUE); pixDestroy(&pixm); return pixd; }
/*! * wshedApply() * * Input: wshed (generated from wshedCreate()) * Return: 0 if OK, 1 on error * * Iportant note: * (1) This is buggy. It seems to locate watersheds that are * duplicates. The watershed extraction after complete fill * grabs some regions belonging to existing watersheds. * See prog/watershedtest.c for testing. */ l_int32 wshedApply(L_WSHED *wshed) { char two_new_watersheds[] = "Two new watersheds"; char seed_absorbed_into_seeded_basin[] = "Seed absorbed into seeded basin"; char one_new_watershed_label[] = "One new watershed (label)"; char one_new_watershed_index[] = "One new watershed (index)"; char minima_absorbed_into_seeded_basin[] = "Minima absorbed into seeded basin"; char minima_absorbed_by_filler_or_another[] = "Minima absorbed by filler or another"; l_int32 nseeds, nother, nboth, arraysize; l_int32 i, j, val, x, y, w, h, index, mindepth; l_int32 imin, imax, jmin, jmax, cindex, clabel, nindex; l_int32 hindex, hlabel, hmin, hmax, minhindex, maxhindex; l_int32 *lut; l_uint32 ulabel, uval; void **lines8, **linelab32; NUMA *nalut, *nalevels, *nash, *namh, *nasi; NUMA **links; L_HEAP *lh; PIX *pixmin, *pixsd; PIXA *pixad; L_STACK *rstack; PTA *ptas, *ptao; PROCNAME("wshedApply"); if (!wshed) return ERROR_INT("wshed not defined", procName, 1); /* ------------------------------------------------------------ * * Initialize priority queue and pixlab with seeds and minima * * ------------------------------------------------------------ */ lh = lheapCreate(0, L_SORT_INCREASING); /* remove lowest values first */ rstack = lstackCreate(0); /* for reusing the WSPixels */ pixGetDimensions(wshed->pixs, &w, &h, NULL); lines8 = wshed->lines8; /* wshed owns this */ linelab32 = wshed->linelab32; /* ditto */ /* Identify seed (marker) pixels, 1 for each c.c. in pixm */ pixSelectMinInConnComp(wshed->pixs, wshed->pixm, &ptas, &nash); pixsd = pixGenerateFromPta(ptas, w, h); nseeds = ptaGetCount(ptas); for (i = 0; i < nseeds; i++) { ptaGetIPt(ptas, i, &x, &y); uval = GET_DATA_BYTE(lines8[y], x); pushWSPixel(lh, rstack, (l_int32) uval, x, y, i); } wshed->ptas = ptas; nasi = numaMakeConstant(1, nseeds); /* indicator array */ wshed->nasi = nasi; wshed->nash = nash; wshed->nseeds = nseeds; /* Identify minima that are not seeds. Use these 4 steps: * (1) Get the local minima, which can have components * of arbitrary size. This will be a clipping mask. * (2) Get the image of the actual seeds (pixsd) * (3) Remove all elements of the clipping mask that have a seed. * (4) Shrink each of the remaining elements of the minima mask * to a single pixel. */ pixLocalExtrema(wshed->pixs, 200, 0, &pixmin, NULL); pixRemoveSeededComponents(pixmin, pixsd, pixmin, 8, 2); pixSelectMinInConnComp(wshed->pixs, pixmin, &ptao, &namh); nother = ptaGetCount(ptao); for (i = 0; i < nother; i++) { ptaGetIPt(ptao, i, &x, &y); uval = GET_DATA_BYTE(lines8[y], x); pushWSPixel(lh, rstack, (l_int32) uval, x, y, nseeds + i); } wshed->namh = namh; /* ------------------------------------------------------------ * * Initialize merging lookup tables * * ------------------------------------------------------------ */ /* nalut should always give the current after-merging index. * links are effectively backpointers: they are numas associated with * a dest index of all indices in nalut that point to that index. */ mindepth = wshed->mindepth; nboth = nseeds + nother; arraysize = 2 * nboth; wshed->arraysize = arraysize; nalut = numaMakeSequence(0, 1, arraysize); lut = numaGetIArray(nalut); wshed->lut = lut; /* wshed owns this */ links = (NUMA **) CALLOC(arraysize, sizeof(NUMA * )); wshed->links = links; /* wshed owns this */ nindex = nseeds + nother; /* the next unused index value */ /* ------------------------------------------------------------ * * Fill the basins, using the priority queue * * ------------------------------------------------------------ */ pixad = pixaCreate(nseeds); wshed->pixad = pixad; /* wshed owns this */ nalevels = numaCreate(nseeds); wshed->nalevels = nalevels; /* wshed owns this */ L_INFO("nseeds = %d, nother = %d\n", procName, nseeds, nother); while (lheapGetCount(lh) > 0) { popWSPixel(lh, rstack, &val, &x, &y, &index); /* fprintf(stderr, "x = %d, y = %d, index = %d\n", x, y, index); */ ulabel = GET_DATA_FOUR_BYTES(linelab32[y], x); if (ulabel == MAX_LABEL_VALUE) clabel = ulabel; else clabel = lut[ulabel]; cindex = lut[index]; if (clabel == cindex) continue; /* have already seen this one */ if (clabel == MAX_LABEL_VALUE) { /* new one; assign index and try to * propagate to all neighbors */ SET_DATA_FOUR_BYTES(linelab32[y], x, cindex); imin = L_MAX(0, y - 1); imax = L_MIN(h - 1, y + 1); jmin = L_MAX(0, x - 1); jmax = L_MIN(w - 1, x + 1); for (i = imin; i <= imax; i++) { for (j = jmin; j <= jmax; j++) { if (i == y && j == x) continue; uval = GET_DATA_BYTE(lines8[i], j); pushWSPixel(lh, rstack, (l_int32) uval, j, i, cindex); } } } else { /* pixel is already labeled (differently); must resolve */ /* If both indices are seeds, check if the min height is * greater than mindepth. If so, we have two new watersheds; * locate them and assign to both regions a new index * for further waterfill. If not, absorb the shallower * watershed into the deeper one and continue filling it. */ pixGetPixel(pixsd, x, y, &uval); if (clabel < nseeds && cindex < nseeds) { wshedGetHeight(wshed, val, clabel, &hlabel); wshedGetHeight(wshed, val, cindex, &hindex); hmin = L_MIN(hlabel, hindex); hmax = L_MAX(hlabel, hindex); if (hmin == hmax) { hmin = hlabel; hmax = hindex; } if (wshed->debug) { fprintf(stderr, "clabel,hlabel = %d,%d\n", clabel, hlabel); fprintf(stderr, "hmin = %d, hmax = %d\n", hmin, hmax); fprintf(stderr, "cindex,hindex = %d,%d\n", cindex, hindex); if (hmin < mindepth) fprintf(stderr, "Too shallow!\n"); } if (hmin >= mindepth) { debugWshedMerge(wshed, two_new_watersheds, x, y, clabel, cindex); wshedSaveBasin(wshed, cindex, val - 1); wshedSaveBasin(wshed, clabel, val - 1); numaSetValue(nasi, cindex, 0); numaSetValue(nasi, clabel, 0); if (wshed->debug) fprintf(stderr, "nindex = %d\n", nindex); debugPrintLUT(lut, nindex, wshed->debug); mergeLookup(wshed, clabel, nindex); debugPrintLUT(lut, nindex, wshed->debug); mergeLookup(wshed, cindex, nindex); debugPrintLUT(lut, nindex, wshed->debug); nindex++; } else /* extraneous seed within seeded basin; absorb */ { debugWshedMerge(wshed, seed_absorbed_into_seeded_basin, x, y, clabel, cindex); } maxhindex = clabel; /* TODO: is this part of above 'else'? */ minhindex = cindex; if (hindex > hlabel) { maxhindex = cindex; minhindex = clabel; } mergeLookup(wshed, minhindex, maxhindex); } else if (clabel < nseeds && cindex >= nboth) { /* If one index is a seed and the other is a merge of * 2 watersheds, generate a single watershed. */ debugWshedMerge(wshed, one_new_watershed_label, x, y, clabel, cindex); wshedSaveBasin(wshed, clabel, val - 1); numaSetValue(nasi, clabel, 0); mergeLookup(wshed, clabel, cindex); } else if (cindex < nseeds && clabel >= nboth) { debugWshedMerge(wshed, one_new_watershed_index, x, y, clabel, cindex); wshedSaveBasin(wshed, cindex, val - 1); numaSetValue(nasi, cindex, 0); mergeLookup(wshed, cindex, clabel); } else if (clabel < nseeds) { /* cindex from minima; absorb */ /* If one index is a seed and the other is from a minimum, * merge the minimum wshed into the seed wshed. */ debugWshedMerge(wshed, minima_absorbed_into_seeded_basin, x, y, clabel, cindex); mergeLookup(wshed, cindex, clabel); } else if (cindex < nseeds) { /* clabel from minima; absorb */ debugWshedMerge(wshed, minima_absorbed_into_seeded_basin, x, y, clabel, cindex); mergeLookup(wshed, clabel, cindex); } else { /* If neither index is a seed, just merge */ debugWshedMerge(wshed, minima_absorbed_by_filler_or_another, x, y, clabel, cindex); mergeLookup(wshed, clabel, cindex); } } } #if 0 /* Use the indicator array to save any watersheds that fill * to the maximum value. This seems to screw things up! */ for (i = 0; i < nseeds; i++) { numaGetIValue(nasi, i, &ival); if (ival == 1) { wshedSaveBasin(wshed, lut[i], val - 1); numaSetValue(nasi, i, 0); } } #endif numaDestroy(&nalut); pixDestroy(&pixmin); pixDestroy(&pixsd); ptaDestroy(&ptao); lheapDestroy(&lh, TRUE); lstackDestroy(&rstack, TRUE); return 0; }
char* _process_frame_tickertext(struct lib_hardsubx_ctx *ctx, AVFrame *frame, int width, int height, int index) { PIX *im; PIX *edge_im; PIX *lum_im; PIX *feat_im; char *subtitle_text=NULL; im = pixCreate(width,height,32); lum_im = pixCreate(width,height,32); feat_im = pixCreate(width,height,32); int i,j; for(i=(92*height)/100;i<height;i++) { for(j=0;j<width;j++) { int p=j*3+i*frame->linesize[0]; int r=frame->data[0][p]; int g=frame->data[0][p+1]; int b=frame->data[0][p+2]; pixSetRGBPixel(im,j,i,r,g,b); float L,A,B; rgb_to_lab((float)r,(float)g,(float)b,&L,&A,&B); if(L > ctx->lum_thresh) pixSetRGBPixel(lum_im,j,i,255,255,255); else pixSetRGBPixel(lum_im,j,i,0,0,0); } } //Handle the edge image edge_im = pixCreate(width,height,8); edge_im = pixConvertRGBToGray(im,0.0,0.0,0.0); edge_im = pixSobelEdgeFilter(edge_im, L_VERTICAL_EDGES); edge_im = pixDilateGray(edge_im, 21, 11); edge_im = pixThresholdToBinary(edge_im,50); for(i=92*(height/100);i<height;i++) { for(j=0;j<width;j++) { unsigned int p1,p2,p3; pixGetPixel(edge_im,j,i,&p1); // pixGetPixel(pixd,j,i,&p2); pixGetPixel(lum_im,j,i,&p3); if(p1==0&&p3>0) pixSetRGBPixel(feat_im,j,i,255,255,255); else pixSetRGBPixel(feat_im,j,i,0,0,0); } } // Tesseract OCR for the ticker text here subtitle_text = get_ocr_text_simple(ctx, lum_im); char write_path[100]; sprintf(write_path,"./lum_im%04d.jpg",index); pixWrite(write_path,lum_im,IFF_JFIF_JPEG); sprintf(write_path,"./im%04d.jpg",index); pixWrite(write_path,im,IFF_JFIF_JPEG); pixDestroy(&lum_im); pixDestroy(&im); pixDestroy(&edge_im); pixDestroy(&feat_im); return subtitle_text; }
/*! * pixSearchGrayMaze() * * Input: pixs (1 bpp, maze) * xi, yi (beginning point; use same initial point * that was used to generate the maze) * xf, yf (end point, or close to it) * &ppixd (<optional return> maze with path illustrated, or * if no path possible, the part of the maze * that was searched) * Return: pta (shortest path), or null if either no path * exists or on error * * Commentary: * Consider first a slight generalization of the binary maze * search problem. Suppose that you can go through walls, * but the cost is higher (say, an increment of 3 to go into * a wall pixel rather than 1)? You're still trying to find * the shortest path. One way to do this is with an ordered * queue, and a simple way to visualize an ordered queue is as * a set of stacks, each stack being marked with the distance * of each pixel in the stack from the start. We place the * start pixel in stack 0, pop it, and process its 4 children. * Each pixel is given a distance that is incremented from that * of its parent (0 in this case), depending on if it is a wall * pixel or not. That value may be recorded on a distance map, * according to the algorithm below. For children of the first * pixel, those not on a wall go in stack 1, and wall * children go in stack 3. Stack 0 being emptied, the process * then continues with pixels being popped from stack 1. * Here is the algorithm for each child pixel. The pixel's * distance value, were it to be placed on a stack, is compared * with the value for it that is on the distance map. There * are three possible cases: * (1) If the pixel has not yet been registered, it is pushed * on its stack and the distance is written to the map. * (2) If it has previously been registered with a higher distance, * the distance on the map is relaxed to that of the * current pixel, which is then placed on its stack. * (3) If it has previously been registered with an equal * or lower value, the pixel is discarded. * The pixels are popped and processed successively from * stack 1, and when stack 1 is empty, popping starts on stack 2. * This continues until the destination pixel is popped off * a stack. The minimum path is then derived from the distance map, * going back from the end point as before. This is just Dijkstra's * algorithm for a directed graph; here, the underlying graph * (consisting of the pixels and four edges connecting each pixel * to its 4-neighbor) is a special case of a directed graph, where * each edge is bi-directional. The implementation of this generalized * maze search is left as an exercise to the reader. * * Let's generalize a bit further. Suppose the "maze" is just * a grayscale image -- think of it as an elevation map. The cost * of moving on this surface depends on the height, or the gradient, * or whatever you want. All that is required is that the cost * is specified and non-negative on each link between adjacent * pixels. Now the problem becomes: find the least cost path * moving on this surface between two specified end points. * For example, if the cost across an edge between two pixels * depends on the "gradient", you can use: * cost = 1 + L_ABS(deltaV) * where deltaV is the difference in value between two adjacent * pixels. If the costs are all integers, we can still use an array * of stacks to avoid ordering the queue (e.g., by using a heap sort.) * This is a neat problem, because you don't even have to build a * maze -- you can can use it on any grayscale image! * * Rather than using an array of stacks, a more practical * approach is to implement with a priority queue, which is * a queue that is sorted so that the elements with the largest * (or smallest) key values always come off first. The * priority queue is efficiently implemented as a heap, and * this is how we do it. Suppose you run the algorithm * using a priority queue, doing the bookkeeping with an * auxiliary image data structure that saves the distance of * each pixel put on the queue as before, according to the method * described above. We implement it as a 2-way choice by * initializing the distance array to a large value and putting * a pixel on the queue if its distance is less than the value * found on the array. When you finally pop the end pixel from * the queue, you're done, and you can trace the path backward, * either always going downhill or using an auxiliary image to * give you the direction to go at each step. This is implemented * here in searchGrayMaze(). * * Do we really have to use a sorted queue? Can we solve this * generalized maze with an unsorted queue of pixels? (Or even * an unsorted stack, doing a depth-first search (DFS)?) * Consider a different algorithm for this generalized maze, where * we travel again breadth first, but this time use a single, * unsorted queue. An auxiliary image is used as before to * store the distances and to determine if pixels get pushed * on the stack or dropped. As before, we must allow pixels * to be revisited, with relaxation of the distance if a shorter * path arrives later. As a result, we will in general have * multiple instances of the same pixel on the stack with different * distances. However, because the queue is not ordered, some of * these pixels will be popped when another instance with a lower * distance is still on the stack. Here, we're just popping them * in the order they go on, rather than setting up a priority * based on minimum distance. Thus, unlike the priority queue, * when a pixel is popped we have to check the distance map to * see if a pixel with a lower distance has been put on the queue, * and, if so, we discard the pixel we just popped. So the * "while" loop looks like this: * - pop a pixel from the queue * - check its distance against the distance stored in the * distance map; if larger, discard * - otherwise, for each of its neighbors: * - compute its distance from the start pixel * - compare this distance with that on the distance map: * - if the distance map value higher, relax the distance * and push the pixel on the queue * - if the distance map value is lower, discard the pixel * * How does this loop terminate? Before, with an ordered queue, * it terminates when you pop the end pixel. But with an unordered * queue (or stack), the first time you hit the end pixel, the * distance is not guaranteed to be correct, because the pixels * along the shortest path may not have yet been visited and relaxed. * Because the shortest path can theoretically go anywhere, * we must keep going. How do we know when to stop? Dijkstra * uses an ordered queue to systematically remove nodes from * further consideration. (Each time a pixel is popped, we're * done with it; it's "finalized" in the Dijkstra sense because * we know the shortest path to it.) However, with an unordered * queue, the brute force answer is: stop when the queue * (or stack) is empty, because then every pixel in the image * has been assigned its minimum "distance" from the start pixel. * * This is similar to the situation when you use a stack for the * simpler uniform-step problem: with breadth-first search (BFS) * the pixels on the queue are automatically ordered, so you are * done when you locate the end pixel as a neighbor of a popped pixel; * whereas depth-first search (DFS), using a stack, requires, * in general, a search of every accessible pixel. Further, if * a pixel is revisited with a smaller distance, that distance is * recorded and the pixel is put on the stack again. * * But surely, you ask, can't we stop sooner? What if the * start and end pixels are very close to each other? * OK, suppose they are, and you have very high walls and a * long snaking level path that is actually the minimum cost. * That long path can wind back and forth across the entire * maze many times before ending up at the end point, which * could be just over a wall from the start. With the unordered * queue, you very quickly get a high distance for the end * pixel, which will be relaxed to the minimum distance only * after all the pixels of the path have been visited and placed * on the queue, multiple times for many of them. So that's the * price for not ordering the queue! */ PTA * pixSearchGrayMaze(PIX *pixs, l_int32 xi, l_int32 yi, l_int32 xf, l_int32 yf, PIX **ppixd) { l_int32 x, y, w, h, d; l_uint32 val, valr, vals, rpixel, gpixel, bpixel; void **lines8, **liner32, **linep8; l_int32 cost, dist, distparent, sival, sivals; MAZEEL *el, *elp; PIX *pixd; /* optionally plot the path on this RGB version of pixs */ PIX *pixr; /* for bookkeeping, to indicate the minimum distance */ /* to pixels already visited */ PIX *pixp; /* for bookkeeping, to indicate direction to parent */ L_HEAP *lh; PTA *pta; PROCNAME("pixSearchGrayMaze"); if (ppixd) *ppixd = NULL; if (!pixs) return (PTA *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 8) return (PTA *)ERROR_PTR("pixs not 8 bpp", procName, NULL); if (xi <= 0 || xi >= w) return (PTA *)ERROR_PTR("xi not valid", procName, NULL); if (yi <= 0 || yi >= h) return (PTA *)ERROR_PTR("yi not valid", procName, NULL); pixd = NULL; pta = NULL; pixr = pixCreate(w, h, 32); pixSetAll(pixr); /* initialize to max value */ pixp = pixCreate(w, h, 8); /* direction to parent stored as enum val */ lines8 = pixGetLinePtrs(pixs, NULL); linep8 = pixGetLinePtrs(pixp, NULL); liner32 = pixGetLinePtrs(pixr, NULL); lh = lheapCreate(0, L_SORT_INCREASING); /* always remove closest pixels */ /* Prime the heap with the first pixel */ pixGetPixel(pixs, xi, yi, &val); el = mazeelCreate(xi, yi, 0); /* don't need direction here */ el->distance = 0; pixGetPixel(pixs, xi, yi, &val); el->val = val; pixSetPixel(pixr, xi, yi, 0); /* distance is 0 */ lheapAdd(lh, el); /* Breadth-first search with priority queue (implemented by a heap), labeling direction to parents in pixp and minimum distance to visited pixels in pixr. Stop when we pull the destination point (xf, yf) off the queue. */ while (lheapGetCount(lh) > 0) { elp = (MAZEEL *)lheapRemove(lh); if (!elp) return (PTA *)ERROR_PTR("heap broken!!", procName, NULL); x = elp->x; y = elp->y; if (x == xf && y == yf) { /* exit condition */ FREE(elp); break; } distparent = (l_int32)elp->distance; val = elp->val; sival = val; if (x > 0) { /* check to west */ vals = GET_DATA_BYTE(lines8[y], x - 1); valr = GET_DATA_FOUR_BYTES(liner32[y], x - 1); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y], x - 1, dist); /* new dist */ SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST); /* parent to E */ el = mazeelCreate(x - 1, y, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (y > 0) { /* check north */ vals = GET_DATA_BYTE(lines8[y - 1], x); valr = GET_DATA_FOUR_BYTES(liner32[y - 1], x); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y - 1], x, dist); /* new dist */ SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH); /* parent to S */ el = mazeelCreate(x, y - 1, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (x < w - 1) { /* check east */ vals = GET_DATA_BYTE(lines8[y], x + 1); valr = GET_DATA_FOUR_BYTES(liner32[y], x + 1); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y], x + 1, dist); /* new dist */ SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST); /* parent to W */ el = mazeelCreate(x + 1, y, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (y < h - 1) { /* check south */ vals = GET_DATA_BYTE(lines8[y + 1], x); valr = GET_DATA_FOUR_BYTES(liner32[y + 1], x); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y + 1], x, dist); /* new dist */ SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH); /* parent to N */ el = mazeelCreate(x, y + 1, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } FREE(elp); } lheapDestroy(&lh, TRUE); if (ppixd) { pixd = pixConvert8To32(pixs); *ppixd = pixd; } composeRGBPixel(255, 0, 0, &rpixel); /* start point */ composeRGBPixel(0, 255, 0, &gpixel); composeRGBPixel(0, 0, 255, &bpixel); /* end point */ x = xf; y = yf; pta = ptaCreate(0); while (1) { /* write path onto pixd */ ptaAddPt(pta, x, y); if (x == xi && y == yi) break; if (pixd) pixSetPixel(pixd, x, y, gpixel); pixGetPixel(pixp, x, y, &val); if (val == DIR_NORTH) y--; else if (val == DIR_SOUTH) y++; else if (val == DIR_EAST) x++; else if (val == DIR_WEST) x--; pixGetPixel(pixr, x, y, &val); #if DEBUG_PATH fprintf(stderr, "(x,y) = (%d, %d); dist = %d\n", x, y, val); #endif /* DEBUG_PATH */ } if (pixd) { pixSetPixel(pixd, xi, yi, rpixel); pixSetPixel(pixd, xf, yf, bpixel); } pixDestroy(&pixp); pixDestroy(&pixr); FREE(lines8); FREE(linep8); FREE(liner32); return pta; }
int main(int argc, char **argv) { char *infile; l_int32 w, d, threshval, ival, newval; l_uint32 val; PIX *pixs, *pixg, *pixg2; PIX *pix1, *pix2; PIXA *pixa; static char mainName[] = "binarize_set"; if (argc != 2) return ERROR_INT(" Syntax: binarize_set infile", mainName, 1); infile = argv[1]; pixa = pixaCreate(5); pixs = pixRead(infile); pixGetDimensions(pixs, &w, NULL, &d); pixSaveTiled(pixs, pixa, 1.0, 1, 50, 32); pixDisplay(pixs, 100, 0); #if ALL /* 1. Standard background normalization with a global threshold. */ pixg = pixConvertTo8(pixs, 0); pix1 = pixBackgroundNorm(pixg, NULL, NULL, 10, 15, 100, 50, 255, 2, 2); pix2 = pixThresholdToBinary(pix1, 160); pixWrite("/tmp/binar1.png", pix2, IFF_PNG); pixDisplay(pix2, 100, 0); pixSaveTiled(pix2, pixa, 1.0, 1, 50, 32); pixDestroy(&pixg); pixDestroy(&pix1); pixDestroy(&pix2); #endif #if ALL /* 2. Background normalization followed by Otsu thresholding. Otsu * binarization attempts to split the image into two roughly equal * sets of pixels, and it does a very poor job when there are large * amounts of dark background. By doing a background normalization * first (to get the background near 255), we remove this problem. * Then we use a modified Otsu to estimate the best global * threshold on the normalized image. */ pixg = pixConvertTo8(pixs, 0); pix1 = pixOtsuThreshOnBackgroundNorm(pixg, NULL, 10, 15, 100, 50, 255, 2, 2, 0.10, &threshval); fprintf(stderr, "thresh val = %d\n", threshval); pixSaveTiled(pix1, pixa, 1.0, 1, 50, 32); pixWrite("/tmp/binar2.png", pix1, IFF_PNG); pixDisplay(pix1, 100, 200); pixDestroy(&pixg); pixDestroy(&pix1); #endif #if ALL /* 3. Background normalization with Otsu threshold estimation and * masking for threshold selection. */ pixg = pixConvertTo8(pixs, 0); pix1 = pixMaskedThreshOnBackgroundNorm(pixg, NULL, 10, 15, 100, 50, 2, 2, 0.10, &threshval); fprintf(stderr, "thresh val = %d\n", threshval); pixSaveTiled(pix1, pixa, 1.0, 1, 50, 32); pixWrite("/tmp/binar3.png", pix1, IFF_PNG); pixDisplay(pix1, 100, 400); pixDestroy(&pixg); pixDestroy(&pix1); #endif #if ALL /* 4. Background normalization followed by Sauvola binarization */ if (d == 32) pixg = pixConvertRGBToGray(pixs, 0.2, 0.7, 0.1); else pixg = pixConvertTo8(pixs, 0); pixg2 = pixContrastNorm(NULL, pixg, 20, 20, 130, 2, 2); pixSauvolaBinarizeTiled(pixg2, 25, 0.40, 1, 1, NULL, &pix1); pixSaveTiled(pix1, pixa, 1.0, 1, 50, 32); pixWrite("/tmp/binar4.png", pix1, IFF_PNG); pixDisplay(pix1, 100, 600); pixDestroy(&pixg); pixDestroy(&pixg2); pixDestroy(&pix1); #endif #if ALL /* 5. Contrast normalization followed by background normalization, and * thresholding. */ if (d == 32) pixg = pixConvertRGBToGray(pixs, 0.2, 0.7, 0.1); else pixg = pixConvertTo8(pixs, 0); pixOtsuAdaptiveThreshold(pixg, 5000, 5000, 0, 0, 0.1, &pix1, NULL); pixGetPixel(pix1, 0, 0, &val); ival = (l_int32) val; newval = ival + (l_int32)(0.6 * (110 - ival)); fprintf(stderr, "th1 = %d, th2 = %d\n", ival, newval); pixDestroy(&pix1); pixContrastNorm(pixg, pixg, 50, 50, 130, 2, 2); pixg2 = pixBackgroundNorm(pixg, NULL, NULL, 20, 20, 70, 40, 200, 2, 2); ival = L_MIN(ival, 110); pix1 = pixThresholdToBinary(pixg2, ival); pixSaveTiled(pix1, pixa, 1.0, 1, 50, 32); pixWrite("/tmp/binar5.png", pix1, IFF_PNG); pixDisplay(pix1, 100, 800); pixDestroy(&pixg); pixDestroy(&pixg2); pixDestroy(&pix1); #endif pix1 = pixaDisplayTiledInRows(pixa, 32, w + 100, 1.0, 0, 30, 2); pixWrite("/tmp/binar6.png", pix1, IFF_PNG); pixDisplay(pix1, 1000, 0); pixDestroy(&pix1); pixaDestroy(&pixa); pixDestroy(&pixs); return 0; }
main(int argc, char **argv) { l_int32 x, y, i, j, k, w, h, w2, w4, w8, w16, w32, wpl, nerrors; l_int32 count1, count2, count3, ret, val1, val2; l_uint32 val32; l_uint32 *data, *line, *line1, *line2, *data1, *data2; void **lines1, **linet1, **linet2; PIX *pixs, *pixt1, *pixt2; static char mainName[] = "lowaccess_reg"; pixs = pixRead("feyn.tif"); /* width divisible by 16 */ pixGetDimensions(pixs, &w, &h, NULL); data = pixGetData(pixs); wpl = pixGetWpl(pixs); lines1 = pixGetLinePtrs(pixs, NULL); /* Get timing for the 3 different methods */ startTimer(); for (k = 0; k < 10; k++) { count1 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (GET_DATA_BIT(lines1[i], j)) count1++; } } } fprintf(stderr, "Time with line ptrs = %5.3f sec, count1 = %d\n", stopTimer(), count1); startTimer(); for (k = 0; k < 10; k++) { count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { if (l_getDataBit(line, j)) count2++; } } } fprintf(stderr, "Time with l_get* = %5.3f sec, count2 = %d\n", stopTimer(), count2); startTimer(); for (k = 0; k < 10; k++) { count3 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val32); count3 += val32; } } } fprintf(stderr, "Time with pixGetPixel() = %5.3f sec, count3 = %d\n", stopTimer(), count3); pixt1 = pixCreateTemplate(pixs); data1 = pixGetData(pixt1); linet1 = pixGetLinePtrs(pixt1, NULL); pixt2 = pixCreateTemplate(pixs); data2 = pixGetData(pixt2); linet2 = pixGetLinePtrs(pixt2, NULL); nerrors = 0; /* Test different methods for 1 bpp */ count1 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { val1 = GET_DATA_BIT(lines1[i], j); count1 += val1; if (val1) SET_DATA_BIT(linet1[i], j); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w; j++) { val2 = l_getDataBit(line, j); count2 += val2; if (val2) l_setDataBit(line2, j); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "1 bpp"); nerrors += ret; /* Test different methods for 2 bpp */ count1 = 0; w2 = w / 2; for (i = 0; i < h; i++) { for (j = 0; j < w2; j++) { val1 = GET_DATA_DIBIT(lines1[i], j); count1 += val1; val1 += 0xbbbbbbbc; SET_DATA_DIBIT(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w2; j++) { val2 = l_getDataDibit(line, j); count2 += val2; val2 += 0xbbbbbbbc; l_setDataDibit(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "2 bpp"); nerrors += ret; /* Test different methods for 4 bpp */ count1 = 0; w4 = w / 4; for (i = 0; i < h; i++) { for (j = 0; j < w4; j++) { val1 = GET_DATA_QBIT(lines1[i], j); count1 += val1; val1 += 0xbbbbbbb0; SET_DATA_QBIT(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w4; j++) { val2 = l_getDataQbit(line, j); count2 += val2; val2 += 0xbbbbbbb0; l_setDataQbit(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "4 bpp"); nerrors += ret; /* Test different methods for 8 bpp */ count1 = 0; w8 = w / 8; for (i = 0; i < h; i++) { for (j = 0; j < w8; j++) { val1 = GET_DATA_BYTE(lines1[i], j); count1 += val1; val1 += 0xbbbbbb00; SET_DATA_BYTE(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w8; j++) { val2 = l_getDataByte(line, j); count2 += val2; val2 += 0xbbbbbb00; l_setDataByte(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "8 bpp"); nerrors += ret; /* Test different methods for 16 bpp */ count1 = 0; w16 = w / 16; for (i = 0; i < h; i++) { for (j = 0; j < w16; j++) { val1 = GET_DATA_TWO_BYTES(lines1[i], j); count1 += val1; val1 += 0xbbbb0000; SET_DATA_TWO_BYTES(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w16; j++) { val2 = l_getDataTwoBytes(line, j); count2 += val2; val2 += 0xbbbb0000; l_setDataTwoBytes(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "16 bpp"); nerrors += ret; /* Test different methods for 32 bpp */ count1 = 0; w32 = w / 32; for (i = 0; i < h; i++) { for (j = 0; j < w32; j++) { val1 = GET_DATA_FOUR_BYTES(lines1[i], j); count1 += val1 & 0xfff; SET_DATA_FOUR_BYTES(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w32; j++) { val2 = l_getDataFourBytes(line, j); count2 += val2 & 0xfff; l_setDataFourBytes(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "32 bpp"); nerrors += ret; if (!nerrors) fprintf(stderr, "**** No errors ****\n"); else fprintf(stderr, "**** %d errors found! ****\n", nerrors); pixDestroy(&pixs); pixDestroy(&pixt1); pixDestroy(&pixt2); lept_free(lines1); lept_free(linet1); lept_free(linet2); return 0; }
/*! * pixVarianceInRectangle() * * Input: pix (8 bpp) * box (region to compute variance and/or root variance) * pix_ma (mean accumulator) * dpix_msa (mean square accumulator) * &var (<optional return> variance) * &rvar (<optional return> root variance) * Return: 0 if OK, 1 on error * * Notes: * (1) This function is intended to be used for many rectangles * on the same image. It can find the variance and/or the * square root of the variance within a rectangle in O(1), * independent of the size of the rectangle. */ l_int32 pixVarianceInRectangle(PIX *pixs, BOX *box, PIX *pix_ma, DPIX *dpix_msa, l_float32 *pvar, l_float32 *prvar) { l_int32 w, h, bx, by, bw, bh; l_uint32 val00, val01, val10, val11; l_float64 dval00, dval01, dval10, dval11, mval, msval, var, norm; BOX *boxc; PROCNAME("pixVarianceInRectangle"); if (!pvar && !prvar) return ERROR_INT("neither &var nor &rvar defined", procName, 1); if (pvar) *pvar = 0.0; if (prvar) *prvar = 0.0; if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs not defined", procName, 1); if (!box) return ERROR_INT("box not defined", procName, 1); if (!pix_ma) return ERROR_INT("pix_ma not defined", procName, 1); if (!dpix_msa) return ERROR_INT("dpix_msa not defined", procName, 1); /* Clip rectangle to image */ pixGetDimensions(pixs, &w, &h, NULL); boxc = boxClipToRectangle(box, w, h); boxGetGeometry(boxc, &bx, &by, &bw, &bh); boxDestroy(&boxc); if (bw == 0 || bh == 0) return ERROR_INT("no pixels in box", procName, 1); /* Use up to 4 points in the accumulators */ norm = 1.0 / (bw * bh); if (bx > 0 && by > 0) { pixGetPixel(pix_ma, bx + bw - 1, by + bh - 1, &val11); pixGetPixel(pix_ma, bx + bw - 1, by - 1, &val10); pixGetPixel(pix_ma, bx - 1, by + bh - 1, &val01); pixGetPixel(pix_ma, bx - 1, by - 1, &val00); dpixGetPixel(dpix_msa, bx + bw - 1, by + bh - 1, &dval11); dpixGetPixel(dpix_msa, bx + bw - 1, by - 1, &dval10); dpixGetPixel(dpix_msa, bx - 1, by + bh - 1, &dval01); dpixGetPixel(dpix_msa, bx - 1, by - 1, &dval00); mval = norm * (val11 - val01 + val00 - val10); msval = norm * (dval11 - dval01 + dval00 - dval10); var = (msval - mval * mval); if (pvar) *pvar = (l_float32) var; if (prvar) *prvar = (l_float32)(sqrt(var)); } else if (by > 0) { /* bx == 0 */ pixGetPixel(pix_ma, bw - 1, by + bh - 1, &val11); pixGetPixel(pix_ma, bw - 1, by - 1, &val10); dpixGetPixel(dpix_msa, bw - 1, by + bh - 1, &dval11); dpixGetPixel(dpix_msa, bw - 1, by - 1, &dval10); mval = norm * (val11 - val10); msval = norm * (dval11 - dval10); var = (msval - mval * mval); if (pvar) *pvar = (l_float32) var; if (prvar) *prvar = (l_float32)(sqrt(var)); } else if (bx > 0) { /* by == 0 */ pixGetPixel(pix_ma, bx + bw - 1, bh - 1, &val11); pixGetPixel(pix_ma, bx - 1, bh - 1, &val01); dpixGetPixel(dpix_msa, bx + bw - 1, bh - 1, &dval11); dpixGetPixel(dpix_msa, bx - 1, bh - 1, &dval01); mval = norm * (val11 - val01); msval = norm * (dval11 - dval01); var = (msval - mval * mval); if (pvar) *pvar = (l_float32) var; if (prvar) *prvar = (l_float32)(sqrt(var)); } else { /* bx == 0 && by == 0 */ pixGetPixel(pix_ma, bw - 1, bh - 1, &val11); dpixGetPixel(dpix_msa, bw - 1, bh - 1, &dval11); mval = norm * val11; msval = norm * dval11; var = (msval - mval * mval); if (pvar) *pvar = (l_float32) var; if (prvar) *prvar = (l_float32)(sqrt(var)); } return 0; }
/*! * pixGenerateSelRandom() * * Input: pix (1 bpp, typically small, to be used as a pattern) * hitfract (fraction of allowable fg pixels that are hits) * missfract (fraction of allowable bg pixels that are misses) * distance (min distance from boundary pixel; use 0 for default) * toppix (number of extra pixels of bg added above) * botpix (number of extra pixels of bg added below) * leftpix (number of extra pixels of bg added to left) * rightpix (number of extra pixels of bg added to right) * &pixe (<optional return> input pix expanded by extra pixels) * Return: sel (hit-miss for input pattern), or null on error * * Notes: * (1) Either of hitfract and missfract can be zero. If both are zero, * the sel would be empty, and NULL is returned. * (2) No elements are selected that are less than 'distance' pixels away * from a boundary pixel of the same color. This makes the * match much more robust to edge noise. Valid inputs of * 'distance' are 0, 1, 2, 3 and 4. If distance is either 0 or * greater than 4, we reset it to the default value. * (3) The 4 numbers for adding rectangles of pixels outside the fg * can be use if the pattern is expected to be surrounded by bg * (white) pixels. On the other hand, if the pattern may be near * other fg (black) components on some sides, use 0 for those sides. * (4) The input pix, as extended by the extra pixels on selected sides, * can optionally be returned. For debugging, call * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed * on the generating bitmap. */ SEL * pixGenerateSelRandom(PIX *pixs, l_float32 hitfract, l_float32 missfract, l_int32 distance, l_int32 toppix, l_int32 botpix, l_int32 leftpix, l_int32 rightpix, PIX **ppixe) { l_int32 ws, hs, w, h, x, y, i, j, thresh; l_uint32 val; PIX *pixt1, *pixt2, *pixfg, *pixbg; SEL *seld, *sel; PROCNAME("pixGenerateSelRandom"); if (ppixe) *ppixe = NULL; if (!pixs) return (SEL *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (hitfract <= 0.0 && missfract <= 0.0) return (SEL *)ERROR_PTR("no hits or misses", procName, NULL); if (hitfract > 1.0 || missfract > 1.0) return (SEL *)ERROR_PTR("fraction can't be > 1.0", procName, NULL); if (distance <= 0) distance = DEFAULT_DISTANCE_TO_BOUNDARY; if (distance > MAX_DISTANCE_TO_BOUNDARY) { L_WARNING("distance too large; setting to max value", procName); distance = MAX_DISTANCE_TO_BOUNDARY; } /* Locate the foreground */ pixClipToForeground(pixs, &pixt1, NULL); if (!pixt1) return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL); ws = pixGetWidth(pixt1); hs = pixGetHeight(pixt1); w = ws; h = hs; /* Crop out a region including the foreground, and add pixels * on sides depending on the side flags */ if (toppix || botpix || leftpix || rightpix) { x = y = 0; if (toppix) { h += toppix; y = toppix; } if (botpix) h += botpix; if (leftpix) { w += leftpix; x = leftpix; } if (rightpix) w += rightpix; pixt2 = pixCreate(w, h, 1); pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0); } else pixt2 = pixClone(pixt1); if (ppixe) *ppixe = pixClone(pixt2); pixDestroy(&pixt1); /* Identify fg and bg pixels that are at least 'distance' pixels * away from the boundary pixels in their set */ seld = selCreateBrick(2 * distance + 1, 2 * distance + 1, distance, distance, SEL_HIT); pixfg = pixErode(NULL, pixt2, seld); pixbg = pixDilate(NULL, pixt2, seld); pixInvert(pixbg, pixbg); selDestroy(&seld); pixDestroy(&pixt2); /* Generate the sel from a random selection of these points */ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE); if (hitfract > 0.0) { thresh = (l_int32)(hitfract * (l_float64)RAND_MAX); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixfg, j, i, &val); if (val) { if (rand() < thresh) selSetElement(sel, i, j, SEL_HIT); } } } } if (missfract > 0.0) { thresh = (l_int32)(missfract * (l_float64)RAND_MAX); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixbg, j, i, &val); if (val) { if (rand() < thresh) selSetElement(sel, i, j, SEL_MISS); } } } } pixDestroy(&pixfg); pixDestroy(&pixbg); return sel; }
main(int argc, char **argv) { char *filein; l_int32 count; CCBORDA *ccba, *ccba2; PIX *pixs, *pixd, *pixd2, *pixd3; PIX *pixt, *pixc, *pixc2; static char mainName[] = "ccbordtest"; if (argc != 2) exit(ERROR_INT(" Syntax: ccbordtest filein", mainName, 1)); filein = argv[1]; if ((pixs = pixRead(filein)) == NULL) exit(ERROR_INT("pixs not made", mainName, 1)); fprintf(stderr, "Get border representation..."); startTimer(); ccba = pixGetAllCCBorders(pixs); fprintf(stderr, "%6.3f sec\n", stopTimer()); #if 0 /* get global locs directly and display borders */ fprintf(stderr, "Convert from local to global locs..."); startTimer(); ccbaGenerateGlobalLocs(ccba); fprintf(stderr, "%6.3f sec\n", stopTimer()); fprintf(stderr, "Display border representation..."); startTimer(); pixd = ccbaDisplayBorder(ccba); fprintf(stderr, "%6.3f sec\n", stopTimer()); pixWrite("/tmp/junkborder1.png", pixd, IFF_PNG); #else /* get step chain code, then global coords, and display borders */ fprintf(stderr, "Get step chain code..."); startTimer(); ccbaGenerateStepChains(ccba); fprintf(stderr, "%6.3f sec\n", stopTimer()); fprintf(stderr, "Convert from step chain to global locs..."); startTimer(); ccbaStepChainsToPixCoords(ccba, CCB_GLOBAL_COORDS); fprintf(stderr, "%6.3f sec\n", stopTimer()); fprintf(stderr, "Display border representation..."); startTimer(); pixd = ccbaDisplayBorder(ccba); fprintf(stderr, "%6.3f sec\n", stopTimer()); pixWrite("/tmp/junkborder1.png", pixd, IFF_PNG); #endif /* check if border pixels are in original set */ fprintf(stderr, "Check if border pixels are in original set ...\n"); pixt = pixSubtract(NULL, pixd, pixs); pixCountPixels(pixt, &count, NULL); if (count == 0) fprintf(stderr, " all border pixels are in original set\n"); else fprintf(stderr, " %d border pixels are not in original set\n", count); pixDestroy(&pixt); /* display image */ fprintf(stderr, "Reconstruct image ..."); startTimer(); /* pixc = ccbaDisplayImage1(ccba); */ pixc = ccbaDisplayImage2(ccba); fprintf(stderr, "%6.3f sec\n", stopTimer()); pixWrite("/tmp/junkrecon1.png", pixc, IFF_PNG); /* check with original to see if correct */ fprintf(stderr, "Check with original to see if correct ...\n"); pixXor(pixc, pixc, pixs); pixCountPixels(pixc, &count, NULL); if (count == 0) fprintf(stderr, " perfect direct recon\n"); else { l_int32 w, h, i, j; l_uint32 val; fprintf(stderr, " %d pixels in error in recon\n", count); #if 1 w = pixGetWidth(pixc); h = pixGetHeight(pixc); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixc, j, i, &val); if (val == 1) fprintf(stderr, "bad pixel at (%d, %d)\n", j, i); } } pixWrite("/tmp/junkbadpixels.png", pixc, IFF_PNG); #endif } /*----------------------------------------------------------* * write to file (compressed) and read back * *----------------------------------------------------------*/ fprintf(stderr, "Write serialized step data..."); startTimer(); ccbaWrite("/tmp/junkstepout", ccba); fprintf(stderr, "%6.3f sec\n", stopTimer()); fprintf(stderr, "Read serialized step data..."); startTimer(); ccba2 = ccbaRead("/tmp/junkstepout"); fprintf(stderr, "%6.3f sec\n", stopTimer()); /* display the border pixels again */ fprintf(stderr, "Convert from step chain to global locs..."); startTimer(); ccbaStepChainsToPixCoords(ccba2, CCB_GLOBAL_COORDS); fprintf(stderr, "%6.3f sec\n", stopTimer()); fprintf(stderr, "Display border representation..."); startTimer(); pixd2 = ccbaDisplayBorder(ccba2); fprintf(stderr, "%6.3f sec\n", stopTimer()); pixWrite("/tmp/junkborder2.png", pixd2, IFF_PNG); /* check if border pixels are same as first time */ pixXor(pixd2, pixd2, pixd); pixCountPixels(pixd2, &count, NULL); if (count == 0) fprintf(stderr, " perfect w/r border recon\n"); else { l_int32 w, h, i, j, val; fprintf(stderr, " %d pixels in error in w/r recon\n", count); } pixDestroy(&pixd2); /* display image again */ fprintf(stderr, "Convert from step chain to local coords..."); startTimer(); ccbaStepChainsToPixCoords(ccba2, CCB_LOCAL_COORDS); fprintf(stderr, "%6.3f sec\n", stopTimer()); fprintf(stderr, "Reconstruct image from file ..."); startTimer(); /* pixc2 = ccbaDisplayImage1(ccba2); */ pixc2 = ccbaDisplayImage2(ccba2); fprintf(stderr, "%6.3f sec\n", stopTimer()); pixWrite("/tmp/junkrecon2.png", pixc2, IFF_PNG); /* check with original to see if correct */ fprintf(stderr, "Check with original to see if correct ...\n"); pixXor(pixc2, pixc2, pixs); pixCountPixels(pixc2, &count, NULL); if (count == 0) fprintf(stderr, " perfect image recon\n"); else { l_int32 w, h, i, j; l_uint32 val; fprintf(stderr, " %d pixels in error in image recon\n", count); #if 1 w = pixGetWidth(pixc2); h = pixGetHeight(pixc2); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixc2, j, i, &val); if (val == 1) fprintf(stderr, "bad pixel at (%d, %d)\n", j, i); } } pixWrite("/tmp/junkbadpixels2.png", pixc2, IFF_PNG); #endif } /*----------------------------------------------------------* * make, display and check single path border for svg * *----------------------------------------------------------*/ /* make local single path border for svg */ fprintf(stderr, "Make local single path borders for svg ..."); startTimer(); ccbaGenerateSinglePath(ccba); fprintf(stderr, "%6.3f sec\n", stopTimer()); /* generate global single path border */ fprintf(stderr, "Generate global single path borders ..."); startTimer(); ccbaGenerateSPGlobalLocs(ccba, CCB_SAVE_TURNING_PTS); fprintf(stderr, "%6.3f sec\n", stopTimer()); /* display border pixels from single path */ fprintf(stderr, "Display border from single path..."); startTimer(); pixd3 = ccbaDisplaySPBorder(ccba); fprintf(stderr, "%6.3f sec\n", stopTimer()); pixWrite("/tmp/junkborder3.png", pixd3, IFF_PNG); /* check if border pixels are in original set */ fprintf(stderr, "Check if border pixels are in original set ...\n"); pixt = pixSubtract(NULL, pixd3, pixs); pixCountPixels(pixt, &count, NULL); if (count == 0) fprintf(stderr, " all border pixels are in original set\n"); else fprintf(stderr, " %d border pixels are not in original set\n", count); pixDestroy(&pixt); pixDestroy(&pixd3); /* output in svg file format */ fprintf(stderr, "Write output in svg file format ...\n"); startTimer(); ccbaWriteSVG("/tmp/junksvg", ccba); fprintf(stderr, "%6.3f sec\n", stopTimer()); ccbaDestroy(&ccba2); ccbaDestroy(&ccba); pixDestroy(&pixs); pixDestroy(&pixd); pixDestroy(&pixc); pixDestroy(&pixc2); return 0; }
/*! * pixGetRunsOnLine() * * Input: pixs (1 bpp) * x1, y1, x2, y2 * Return: numa, or null on error * * Notes: * (1) Action: this function uses the bresenham algorithm to compute * the pixels along the specified line. It returns a Numa of the * runlengths of the fg (black) and bg (white) runs, always * starting with a white run. * (2) If the first pixel on the line is black, the length of the * first returned run (which is white) is 0. */ NUMA * pixGetRunsOnLine(PIX *pixs, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2) { l_int32 w, h, x, y, npts; l_int32 i, runlen, preval; l_uint32 val; NUMA *numa; PTA *pta; PROCNAME("pixGetRunsOnLine"); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs not 1 bpp", procName, NULL); w = pixGetWidth(pixs); h = pixGetHeight(pixs); if (x1 < 0 || x1 >= w) return (NUMA *)ERROR_PTR("x1 not valid", procName, NULL); if (x2 < 0 || x2 >= w) return (NUMA *)ERROR_PTR("x2 not valid", procName, NULL); if (y1 < 0 || y1 >= h) return (NUMA *)ERROR_PTR("y1 not valid", procName, NULL); if (y2 < 0 || y2 >= h) return (NUMA *)ERROR_PTR("y2 not valid", procName, NULL); if ((pta = generatePtaLine(x1, y1, x2, y2)) == NULL) return (NUMA *)ERROR_PTR("pta not made", procName, NULL); if ((npts = ptaGetCount(pta)) == 0) return (NUMA *)ERROR_PTR("pta has no pts", procName, NULL); if ((numa = numaCreate(0)) == NULL) return (NUMA *)ERROR_PTR("numa not made", procName, NULL); for (i = 0; i < npts; i++) { ptaGetIPt(pta, i, &x, &y); pixGetPixel(pixs, x, y, &val); if (i == 0) { if (val == 1) { /* black pixel; append white run of size 0 */ numaAddNumber(numa, 0); } preval = val; runlen = 1; continue; } if (val == preval) { /* extend current run */ preval = val; runlen++; } else { /* end previous run */ numaAddNumber(numa, runlen); preval = val; runlen = 1; } } numaAddNumber(numa, runlen); /* append last run */ ptaDestroy(&pta); return numa; }