/* * popWSPixel() * * Input: lh (priority queue) * stack (of reusable WSPixels) * &val (<return> pixel value) * &x, &y (<return> pixel coordinates) * &index (<return> label for set to which pixel belongs) * Return: void * * Notes: * (1) This is a wrapper for removing a WSPixel from a heap, * which returns the WSPixel data and saves the WSPixel * on the storage stack. */ static void popWSPixel(L_HEAP *lh, L_STACK *stack, l_int32 *pval, l_int32 *px, l_int32 *py, l_int32 *pindex) { L_WSPIXEL *wsp; PROCNAME("popWSPixel"); if (!lh) { L_ERROR("lheap not defined\n", procName); return; } if (!stack) { L_ERROR("stack not defined\n", procName); return; } if (!pval || !px || !py || !pindex) { L_ERROR("data can't be returned\n", procName); return; } if ((wsp = (L_WSPIXEL *) lheapRemove(lh)) == NULL) return; *pval = (l_int32) wsp->val; *px = wsp->x; *py = wsp->y; *pindex = wsp->index; lstackAdd(stack, wsp); /* save for re-use */ return; }
/*! * 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; }