We aim to develop a real-time edge detection system on a chip for a digital camera. We design a model of canny edge detector application and represent it in the SpecC system-level description language (SLDL) so that the design model can be used for implementation as a System-on-Chip (SoC) suitable for use in a digital camera. Then, we use System-on-Chip Design Environment (SCE), a refinement-based framework, to analyze our system. Besides, we assign a processor, hardware units and buses to meet realtime requirements.
Test Bench Structure
DUT Structure
- Step 1. Gaussian smooth: smooth the image with a Gaussian filter
- Step 2. Compute the first derivative of the image in both horizontal vertical direction. The edge strength is found as the magnitude of the gradient.
- Step 3. Define edge direction.
- Step 4. Perform non-maximal suppression. It suppresses the pixels using a mask of 3×3 pixels passing through the image depends on the edge’s angle.
- Step 5. Hysteresis threshold.
The Canny edge detector algorithm takes an input image, and an output image is produced by the five steps. The output image only shows the edges of the objects in the image. The test bench is used to verify the correctness of the Canny algorithm. The top-level behavior (Main) consisting of Stimulus, Platforrm, and Monitor behaviors. The Platforrm behavior contains an input unit DataIn, an output unit DataOut, and the actual design under test (DUT).
In order to improve the performance, we use parallel and pipeline to accelerate the computation. In our simulation results, gaussian smooth contains the highest amount of computation except the execution time of reading or writing a image. Therefore, we intend to parallelize this function so that we can speed up the overall computation. We decompose the behavior Gaussian Smooth into three types of behaviors: a preparation step (Prep), the horizontal image
blurring (BlurX), and the vertical image blurring (BlurY). Moreover, we try to reduce the computation time, the BlurX and BlurY are separated into four behaviors respectively, and they are executed parallelly.
We use three PCs to evaluate original Canny algorithm without any parallel or pipeline. We perform Canny program 100 times in each machine using the 320 x 240 image. As a result, the average execution time of writing accounts for 68 percent (65.44 ms). Because SCE(System-on-Chip Design Environment) cannot estimate the I/O speed, we use following command to evaluate the actual I/O speed in three PCs:
time sh -c "dd if=/dev/zero of=output.dd bs=74k count=1; sync"
, where the size of a 320x240 image is around 74kb
Conclusion
We design a model of canny edge detector application and describe it in the SpecC system-level description language so that the created design model can be used for implementation as a System-on-Chip suitable for use in a digital camera. The initial execution time is around 1.36s before we assign HW_Standard units for blur parts; the response time cannot satisfy a real-time requirement. After we allocate 8 HW_Standard units, the computation time, 551.471 ms, is still larger than 33.3ms. Thus, we make the assumption that the ALU operations in DUT only use fixed-point operations. It can accelerate the computation time about 2.54 times. However, it still does not satisfy the real-time constraint. Then, we assume there are 5-stage pipeline in DUT. The ideal result will be 144.6ms. However, ARM7_TDMI only supports 3-stage pipeline. We need to find an alternate processor. Now, we cannot reduce the computation time without increasing CPU frequency. Therefore, the clock rate of ARM7_TDMI should be more than 560 MHz to meet the real-time requirement.
Source code reference: Mike Heath, University of South Florida
Reference:
Embedded Systems Methodology Group
SpecC Reference Compiler
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> #include <sim.sh> #include <c_typed_queue.sh> /* make the templates available */ #include <c_typed_double_handshake.sh> #define VERBOSE 0 #define NOEDGE 255 #define POSSIBLE_EDGE 128 #define EDGE 0 #define BOOSTBLURFACTOR 90.0 #define COLS 320 #define ROWS 240 #define SIZE COLS*ROWS #define FILENAME "golfcart.pgm" #define SIGMA 0.6 #define TLOW 0.3 #define THIGH 0.8 #define WINSIZE 21 typedef unsigned char img[SIZE]; /* define our communication data types */ typedef short int imgs[SIZE]; DEFINE_I_TYPED_SENDER(img, img) // creates interface i_img_sender DEFINE_I_TYPED_RECEIVER(img, img) // creates interface i_img_receiver DEFINE_I_TYPED_TRANCEIVER(img, img) // creates interface i_img_tranceiver DEFINE_C_TYPED_QUEUE(img, img) // creates channel c_img_queue behavior Platform(i_img_receiver ImgIn, i_img_sender ImgOut); behavior DataOut(i_img_receiver ImgIn, i_img_sender ImgOut); behavior DataIn(i_img_receiver ImgIn, i_img_sender ImgOut); behavior Monitor(i_img_receiver ImgIn); behavior Stimulus(i_img_sender ImgOut); behavior DUT(i_img_receiver ImgIn, i_img_sender ImgOut); behavior Gaussian_Smooth (i_img_receiver ImgIn, inout imgs smoothedim); behavior Derivative_X_Y(in imgs smoothedim, inout imgs delta_x, inout imgs delta_y); behavior Magnitude_X_Y(inout imgs delta_x, inout imgs delta_y, inout imgs magnitude); behavior Non_Max_Supp(in imgs delta_x, in imgs delta_y, inout imgs magnitude, inout img nms); behavior Apply_Hysteresis(in imgs magnitude, in img nms, i_img_sender ImgOut); //-------------------------------------------------------------- behavior Main() { c_img_queue q1(2ul), q2(2ul); Stimulus stimulus(q1); Platform platform(q1, q2); Monitor monitor(q2); int main() { par { stimulus.main(); platform.main(); monitor.main(); } return 0; } }; behavior Platform(i_img_receiver ImgIn, i_img_sender ImgOut) { c_img_queue q1(2ul), q2(2ul); DataIn din(ImgIn, q1); DUT canny(q1, q2); DataOut dout(q2, ImgOut); void main() { par { din.main(); canny.main(); dout.main(); } } }; behavior Monitor(i_img_receiver ImgIn) { unsigned char edge[SIZE]; /****************************************************************************** * Function: write_pgm_image * Purpose: This function writes an image in PGM format. The file is either * written to the file specified by outfilename or to standard output if * outfilename = NULL. A comment can be written to the header if coment != NULL. ******************************************************************************/ int write_pgm_image(char *outfilename, unsigned char *image, int rows, int cols, const char *comment, int maxval) { FILE *fp; /*************************************************************************** * Open the output image file for writing if a filename was given. If no * filename was provided, set fp to write to standard output. ***************************************************************************/ if(outfilename == NULL) fp = stdout; else { if((fp = fopen(outfilename, "w")) == NULL) { fprintf(stderr, "Error writing the file %s in write_pgm_image().\n", outfilename); return(0); } } /*************************************************************************** * Write the header information to the PGM file. ***************************************************************************/ fprintf(fp, "P5\n%d %d\n", cols, rows); if(comment != NULL) if(strlen(comment) <= 70) fprintf(fp, "# %s\n", comment); fprintf(fp, "%d\n", maxval); /*************************************************************************** * Write the image data to the file. ***************************************************************************/ if((unsigned)rows != fwrite(image, cols, rows, fp)) { fprintf(stderr, "Error writing the image data in write_pgm_image().\n"); if(fp != stdout) fclose(fp); return(0); } if(fp != stdout) fclose(fp); return(1); } void main() { char outfilename[128]; /* Name of the output "edge" image */ ImgIn.receive(&edge); /**************************************************************************** * Write out the edge image to a file. ****************************************************************************/ sprintf(outfilename, "%s_s_%3.2f_l_%3.2f_h_%3.2f.pgm", FILENAME, SIGMA, TLOW, THIGH); if(VERBOSE) printf("Writing the edge iname in the file %s.\n", outfilename); if(write_pgm_image(outfilename, edge, ROWS, COLS,"", 255) == 0) { fprintf(stderr, "Error writing the edge image, %s.\n", outfilename); exit(1); } } }; behavior Stimulus(i_img_sender ImgOut) { unsigned char image[SIZE]; /****************************************************************************** * Function: read_pgm_image * Purpose: This function reads in an image in PGM format. The image can be * read in from either a file or from standard input. The image is only read * from standard input when infilename = NULL. Because the PGM format includes * the number of columns and the number of rows in the image, these are read * from the file. Memory to store the image is allocated OUTSIDE this function. * The found image size is checked against the expected rows and cols. * All comments in the header are discarded in the process of reading the * image. Upon failure, this function returns 0, upon sucess it returns 1. ******************************************************************************/ int read_pgm_image(const char *infilename, unsigned char *image0, int rows, int cols) { FILE *fp; char buf[71]; int r, c; /*************************************************************************** * Open the input image file for reading if a filename was given. If no * filename was provided, set fp to read from standard input. ***************************************************************************/ if(infilename == NULL) fp = stdin; else { if((fp = fopen(infilename, "r")) == NULL) { fprintf(stderr, "Error reading the file %s in read_pgm_image().\n", infilename); return(0); } } /*************************************************************************** * Verify that the image is in PGM format, read in the number of columns * and rows in the image and scan past all of the header information. ***************************************************************************/ fgets(buf, 70, fp); if(strncmp(buf,"P5",2) != 0) { fprintf(stderr, "The file %s is not in PGM format in ", infilename); fprintf(stderr, "read_pgm_image().\n"); if(fp != stdin) fclose(fp); return(0); } do { fgets(buf, 70, fp); }while(buf[0] == '#'); /* skip all comment lines */ sscanf(buf, "%d %d", &c, &r); if(c != cols || r != rows) { fprintf(stderr, "The file %s is not a %d by %d image in ", infilename, cols, rows); fprintf(stderr, "read_pgm_image().\n"); if(fp != stdin) fclose(fp); return(0); } do { fgets(buf, 70, fp); }while(buf[0] == '#'); /* skip all comment lines */ if((unsigned)rows != fread(image0, cols, rows, fp)) { fprintf(stderr, "Error reading the image data in read_pgm_image().\n"); if(fp != stdin) fclose(fp); return(0); } if(fp != stdin) fclose(fp); return(1); } void main() { /**************************************************************************** * Read in the image. This read function allocates memory for the image. ****************************************************************************/ if(VERBOSE) printf("Reading the image %s.\n", FILENAME); if(read_pgm_image(FILENAME, image, ROWS, COLS) == 0) { fprintf(stderr, "Error reading the input image, %s.\n", FILENAME); exit(1); } ImgOut.send(image); } }; /******************************************************************************* * DUT: To perform canny edge detection. *******************************************************************************/ behavior DUT(i_img_receiver ImgIn, i_img_sender ImgOut) { imgs smoothedim; imgs delta_x; imgs delta_y; imgs magnitude; img nms; Gaussian_Smooth gaussian_smooth(ImgIn, smoothedim); Derivative_X_Y derivative_x_y(smoothedim, delta_x, delta_y); Magnitude_X_Y magnitude_x_y(delta_x, delta_y, magnitude); Non_Max_Supp non_max_supp(delta_x, delta_y, magnitude,nms); Apply_Hysteresis apply_hysteresis(magnitude,nms, ImgOut); void main() { gaussian_smooth.main(); //(image, ROWS, COLS, SIGMA, smoothedim); derivative_x_y.main(); //(smoothedim, rows, cols, &delta_x, &delta_y); magnitude_x_y.main(); //(delta_x, delta_y, rows, cols, &magnitude); non_max_supp.main(); //(magnitude, delta_x, delta_y, rows, cols, nms); apply_hysteresis.main();//(magnitude, nms, rows, cols, tlow, thigh, edge0); } }; behavior Prep (i_img_receiver ImgIn, i_img_sender ImgOut, inout float kernel[WINSIZE], inout int center) { void main() { int i;//, center; float x, fx, sum=0.0; float sigma; unsigned char image[SIZE]; int windowsize; ImgIn.receive(&image); ImgOut.send(image); sigma = SIGMA; windowsize = 1 + 2 * ceil(2.5 * sigma); center = (windowsize) / 2; for(i=0;i<(windowsize);i++) { x = (float)(i - center); fx = pow(2.71828, -0.5*x*x/(sigma*sigma)) / (sigma * sqrt(6.2831853)); kernel[i] = fx; sum += fx; } for(i=0;i<(windowsize);i++) kernel[i] /= sum; } }; behavior BlurX (i_img_receiver ImgIn, i_img_sender ImgOut, inout float kernel[WINSIZE],inout float tempim[SIZE], in int center, in imgs smoothedimg, in int start, in int end) { void main() { int c,cc,r, cols; float dot, sum; unsigned char image[SIZE]; ImgIn.receive(&image); for(r=start;r<end;r++) { cols = COLS; for(c=0;c<cols;c++) { dot = 0.0; sum = 0.0; for(cc=(-center);cc<=center;cc++) { if(((c+cc) >= 0) && ((c+cc) < cols)) { dot += (float)image[r*cols+(c+cc)] * kernel[center+cc]; sum += kernel[center+cc]; } } tempim[r*cols+c] = dot/sum; } } ImgOut.send(image); } }; //inout float kernel[WINSIZE] behavior BlurY (i_img_receiver ImgIn, i_img_sender ImgOut,inout float kernel[WINSIZE], inout float tempim[SIZE], in int center,inout imgs smoothedimg,in int start, in int end) { void main() { int c, r,rr,rows,cols; float dot, sum; unsigned char image[SIZE]; ImgIn.receive(&image); rows = ROWS; cols = COLS; for(c=start;c<end;c++) { for(r=0;r<rows;r++) { sum = 0.0; dot = 0.0; for(rr=(-center);rr<=center;rr++) { if(((r+rr) >= 0) && ((r+rr) < rows)) { dot += tempim[(r+rr)*cols+c] * kernel[center+rr]; sum += kernel[center+rr]; } } smoothedimg[r*cols+c] = (short int)(dot*BOOSTBLURFACTOR/sum + 0.5); } } ImgOut.send(image); } }; behavior Gaussian_Smooth (i_img_receiver ImgIn, inout imgs smoothedim) { unsigned char image[SIZE]; c_img_queue q1(2ul), q2(2ul), q3(2ul), q4(2ul),q5(2ul), q6(2ul), q7(2ul), q8(2ul),q9(2ul); float kernel[WINSIZE]; int center; float tempim[SIZE]; Prep prep(ImgIn,q1, kernel, center); BlurX bx1(q1,q2,kernel,tempim,center,smoothedim,0,60), bx2(q2,q3,kernel,tempim,center,smoothedim,60,120), bx3(q3,q4,kernel,tempim,center,smoothedim,120,180), bx4(q4,q5,kernel,tempim,center,smoothedim,180,240); BlurY by1(q5,q6,kernel,tempim,center,smoothedim,0,80), by2(q6,q7,kernel,tempim,center,smoothedim,80,160), by3(q7,q8,kernel,tempim,center,smoothedim,160,240), by4(q8,q9,kernel,tempim,center,smoothedim,240,320); void main() { par { prep.main(); bx1.main(); bx2.main(); bx3.main(); bx4.main(); by1.main(); by2.main(); by3.main(); by4.main(); } } }; behavior Derivative_X_Y(in imgs smoothedim, inout imgs delta_x, inout imgs delta_y) { /******************************************************************************* * PROCEDURE: derrivative_x_y * PURPOSE: Compute the first derivative of the image in both the x any y * directions. The differential filters that are used are: * * -1 * dx = -1 0 +1 and dy = 0 * +1 * *******************************************************************************/ void derivative_x_y(short int *smoothedimg, int rows, int cols, short int *deltax, short int *deltay) { int r, c, pos; /**************************************************************************** * Compute the x-derivative. Adjust the derivative at the borders to avoid * losing pixels. ****************************************************************************/ if(VERBOSE) printf(" Computing the X-direction derivative.\n"); for(r=0;r<rows;r++) { pos = r * cols; deltax[pos] = smoothedimg[pos+1] - smoothedimg[pos]; pos++; for(c=1;c<(cols-1);c++,pos++) deltax[pos] = smoothedimg[pos+1] - smoothedimg[pos-1]; deltax[pos] = smoothedimg[pos] - smoothedimg[pos-1]; } /**************************************************************************** * Compute the y-derivative. Adjust the derivative at the borders to avoid * losing pixels. ****************************************************************************/ if(VERBOSE) printf(" Computing the Y-direction derivative.\n"); for(c=0;c<cols;c++) { pos = c; deltay[pos] = smoothedimg[pos+cols] - smoothedimg[pos]; pos += cols; for(r=1;r<(rows-1);r++,pos+=cols) deltay[pos] = smoothedimg[pos+cols] - smoothedimg[pos-cols]; deltay[pos] = smoothedimg[pos] - smoothedimg[pos-cols]; } } void main() { derivative_x_y(smoothedim, ROWS, COLS, delta_x, delta_y); } }; behavior Magnitude_X_Y(inout imgs delta_x, inout imgs delta_y, inout imgs magnitude) { /******************************************************************************* * PROCEDURE: magnitude_x_y * PURPOSE: Compute the magnitude of the gradient. This is the square root of * the sum of the squared derivative values. *******************************************************************************/ void magnitude_x_y(short int *deltax, short int *deltay, int rows, int cols, short int *mag) { int r, c, pos, sq1, sq2; for(r=0,pos=0;r<rows;r++) { for(c=0;c<cols;c++,pos++) { sq1 = (int)deltax[pos] * (int)deltax[pos]; sq2 = (int)deltay[pos] * (int)deltay[pos]; mag[pos] = (short)(0.5 + sqrt((float)sq1 + (float)sq2)); } } } void main() { magnitude_x_y(delta_x, delta_y, ROWS, COLS, magnitude); } }; behavior Non_Max_Supp(in imgs delta_x, in imgs delta_y, inout imgs magnitude, inout img nms) { /******************************************************************************* * PROCEDURE: non_max_supp * PURPOSE: This routine applies non-maximal suppression to the magnitude of * the gradient image. *******************************************************************************/ non_max_supp(short *mag, short *gradx, short *grady, int nrows, int ncols, unsigned char *result) { int rowcount, colcount,count; short *magrowptr,*magptr; short *gxrowptr,*gxptr; short *gyrowptr,*gyptr,z1,z2; short m00,gx,gy; float mag1,mag2,xperp,yperp; unsigned char *resultrowptr, *resultptr; /**************************************************************************** * Zero the edges of the result image. ****************************************************************************/ for(count=0,resultrowptr=result,resultptr=result+ncols*(nrows-1); count<ncols; resultptr++,resultrowptr++,count++) *resultrowptr = *resultptr = (unsigned char) 0; for(count=0,resultptr=result,resultrowptr=result+ncols-1; count<nrows; count++,resultptr+=ncols,resultrowptr+=ncols) *resultptr = *resultrowptr = (unsigned char) 0; /**************************************************************************** * Suppress non-maximum points. ****************************************************************************/ for(rowcount=1,magrowptr=mag+ncols+1,gxrowptr=gradx+ncols+1, gyrowptr=grady+ncols+1,resultrowptr=result+ncols+1; rowcount<nrows-2; rowcount++,magrowptr+=ncols,gyrowptr+=ncols,gxrowptr+=ncols, resultrowptr+=ncols) { for(colcount=1,magptr=magrowptr,gxptr=gxrowptr,gyptr=gyrowptr, resultptr=resultrowptr;colcount<ncols-2; colcount++,magptr++,gxptr++,gyptr++,resultptr++) { m00 = *magptr; if(m00 == 0) *resultptr = (unsigned char) NOEDGE; else { xperp = -(gx = *gxptr)/((float)m00); yperp = (gy = *gyptr)/((float)m00); } if(gx >= 0) { if(gy >= 0) { if (gx >= gy) { /* 111 */ /* Left point */ z1 = *(magptr - 1); z2 = *(magptr - ncols - 1); mag1 = (m00 - z1)*xperp + (z2 - z1)*yperp; /* Right point */ z1 = *(magptr + 1); z2 = *(magptr + ncols + 1); mag2 = (m00 - z1)*xperp + (z2 - z1)*yperp; } else { /* 110 */ /* Left point */ z1 = *(magptr - ncols); z2 = *(magptr - ncols - 1); mag1 = (z1 - z2)*xperp + (z1 - m00)*yperp; /* Right point */ z1 = *(magptr + ncols); z2 = *(magptr + ncols + 1); mag2 = (z1 - z2)*xperp + (z1 - m00)*yperp; } } else { if (gx >= -gy) { /* 101 */ /* Left point */ z1 = *(magptr - 1); z2 = *(magptr + ncols - 1); mag1 = (m00 - z1)*xperp + (z1 - z2)*yperp; /* Right point */ z1 = *(magptr + 1); z2 = *(magptr - ncols + 1); mag2 = (m00 - z1)*xperp + (z1 - z2)*yperp; } else { /* 100 */ /* Left point */ z1 = *(magptr + ncols); z2 = *(magptr + ncols - 1); mag1 = (z1 - z2)*xperp + (m00 - z1)*yperp; /* Right point */ z1 = *(magptr - ncols); z2 = *(magptr - ncols + 1); mag2 = (z1 - z2)*xperp + (m00 - z1)*yperp; } } } else { if ((gy = *gyptr) >= 0) { if (-gx >= gy) { /* 011 */ /* Left point */ z1 = *(magptr + 1); z2 = *(magptr - ncols + 1); mag1 = (z1 - m00)*xperp + (z2 - z1)*yperp; /* Right point */ z1 = *(magptr - 1); z2 = *(magptr + ncols - 1); mag2 = (z1 - m00)*xperp + (z2 - z1)*yperp; } else { /* 010 */ /* Left point */ z1 = *(magptr - ncols); z2 = *(magptr - ncols + 1); mag1 = (z2 - z1)*xperp + (z1 - m00)*yperp; /* Right point */ z1 = *(magptr + ncols); z2 = *(magptr + ncols - 1); mag2 = (z2 - z1)*xperp + (z1 - m00)*yperp; } } else { if (-gx > -gy) { /* 001 */ /* Left point */ z1 = *(magptr + 1); z2 = *(magptr + ncols + 1); mag1 = (z1 - m00)*xperp + (z1 - z2)*yperp; /* Right point */ z1 = *(magptr - 1); z2 = *(magptr - ncols - 1); mag2 = (z1 - m00)*xperp + (z1 - z2)*yperp; } else { /* 000 */ /* Left point */ z1 = *(magptr + ncols); z2 = *(magptr + ncols + 1); mag1 = (z2 - z1)*xperp + (m00 - z1)*yperp; /* Right point */ z1 = *(magptr - ncols); z2 = *(magptr - ncols - 1); mag2 = (z2 - z1)*xperp + (m00 - z1)*yperp; } } } /* Now determine if the current point is a maximum point */ if ((mag1 > 0.0) || (mag2 > 0.0)) *resultptr = (unsigned char) NOEDGE; else { if (mag2 == 0.0) *resultptr = (unsigned char) NOEDGE; else *resultptr = (unsigned char) POSSIBLE_EDGE; } } } return 0; } void main() { non_max_supp(magnitude, delta_x, delta_y, ROWS, COLS, nms); } }; behavior Apply_Hysteresis(in imgs magnitude, in img nms, i_img_sender ImgOut) { unsigned char edge[SIZE]; /******************************************************************************* * PROCEDURE: follow_edges * PURPOSE: This procedure edges is a recursive routine that traces edgs along * all paths whose magnitude values remain above some specifyable lower * threshhold. *******************************************************************************/ void follow_edges(unsigned char *edgemapptr, short *edgemagptr, short lowval, int cols) { note _SCC_ANALYSIS_IgnoreParThAnalyzeRecursiveFunction=true; short *tempmagptr; unsigned char *tempmapptr; int i; int x[8] = {1,1,0,-1,-1,-1,0,1}, y[8] = {0,1,1,1,0,-1,-1,-1}; for(i=0;i<8;i++) { tempmapptr = edgemapptr - y[i]*cols + x[i]; tempmagptr = edgemagptr - y[i]*cols + x[i]; if((*tempmapptr == POSSIBLE_EDGE) && (*tempmagptr > lowval)) { *tempmapptr = (unsigned char) EDGE; follow_edges(tempmapptr,tempmagptr, lowval, cols); } } } /******************************************************************************* * PROCEDURE: apply_hysteresis * PURPOSE: This routine finds edges that are above some high threshhold or * are connected to a high pixel by a path of pixels greater than a low * threshold. *******************************************************************************/ void apply_hysteresis(short int *mag, unsigned char *nmsimg, int rows, int cols, float tlow, float thigh, unsigned char *edge0) { int r, c, pos, numedges, highcount, lowthreshold, highthreshold, hist[32768]; short int maximum_mag; /**************************************************************************** * Initialize the edge map to possible edges everywhere the non-maximal * suppression suggested there could be an edge except for the border. At * the border we say there can not be an edge because it makes the * follow_edges algorithm more efficient to not worry about tracking an * edge off the side of the image. ****************************************************************************/ for(r=0,pos=0;r<rows;r++) { for(c=0;c<cols;c++,pos++) { if(nmsimg[pos] == POSSIBLE_EDGE) edge0[pos] = POSSIBLE_EDGE; else edge0[pos] = NOEDGE; } } for(r=0,pos=0;r<rows;r++,pos+=cols) { edge0[pos] = NOEDGE; edge0[pos+cols-1] = NOEDGE; } pos = (rows-1) * cols; for(c=0;c<cols;c++,pos++) { edge0[c] = NOEDGE; edge0[pos] = NOEDGE; } /**************************************************************************** * Compute the histogram of the magnitude image. Then use the histogram to * compute hysteresis thresholds. ****************************************************************************/ for(r=0;r<32768;r++) hist[r] = 0; for(r=0,pos=0;r<rows;r++) { for(c=0;c<cols;c++,pos++) { if(edge0[pos] == POSSIBLE_EDGE) hist[mag[pos]]++; } } /**************************************************************************** * Compute the number of pixels that passed the nonmaximal suppression. ****************************************************************************/ for(r=1,numedges=0;r<32768;r++) { if(hist[r] != 0) maximum_mag = r; numedges += hist[r]; } highcount = (int)(numedges * thigh + 0.5); /**************************************************************************** * Compute the high threshold value as the (100 * thigh) percentage point * in the magnitude of the gradient histogram of all the pixels that passes * non-maximal suppression. Then calculate the low threshold as a fraction * of the computed high threshold value. John Canny said in his paper * "A Computational Approach to Edge Detection" that "The ratio of the * high to low threshold in the implementation is in the range two or three * to one." That means that in terms of this implementation, we should * choose tlow ~= 0.5 or 0.33333. ****************************************************************************/ r = 1; numedges = hist[1]; while((r<(maximum_mag-1)) && (numedges < highcount)) { r++; numedges += hist[r]; } highthreshold = r; lowthreshold = (int)(highthreshold * tlow + 0.5); if(VERBOSE) { printf("The input low and high fractions of %f and %f computed to\n", tlow, thigh); printf("magnitude of the gradient threshold values of: %d %d\n", lowthreshold, highthreshold); } /**************************************************************************** * This loop looks for pixels above the highthreshold to locate edges and * then calls follow_edges to continue the edge. ****************************************************************************/ for(r=0,pos=0;r<rows;r++) { for(c=0;c<cols;c++,pos++) { if((edge0[pos] == POSSIBLE_EDGE) && (mag[pos] >= highthreshold)) { edge0[pos] = EDGE; follow_edges((edge0+pos), (mag+pos), lowthreshold, cols); } } } /**************************************************************************** * Set all the remaining possible edges to non-edges. ****************************************************************************/ for(r=0,pos=0;r<rows;r++) { for(c=0;c<cols;c++,pos++) if(edge0[pos] != EDGE) edge0[pos] = NOEDGE; } } void main() { apply_hysteresis(magnitude, nms, ROWS, COLS, TLOW, THIGH, edge); ImgOut.send(edge); } }; behavior DataIn(i_img_receiver ImgIn, i_img_sender ImgOut) { unsigned char image[SIZE]; void main() { ImgIn.receive(&image); ImgOut.send(image); } }; behavior DataOut(i_img_receiver ImgIn, i_img_sender ImgOut) { unsigned char image[SIZE]; void main() { ImgIn.receive(&image); ImgOut.send(image); } };