//---------------------------------------------------------------------------- void ImageUtility2::GetComponents(int numNeighbors, int const* delta, Image2<int>& image, std::vector<std::vector<size_t>>& components) { size_t const numPixels = image.GetNumPixels(); int* numElements = new int[numPixels]; size_t* vstack = new size_t[numPixels]; size_t i, numComponents = 0; int label = 2; for (i = 0; i < numPixels; ++i) { if (image[i] == 1) { int top = -1; vstack[++top] = i; int& count = numElements[numComponents + 1]; count = 0; while (top >= 0) { size_t v = vstack[top]; image[v] = -1; int j; for (j = 0; j < numNeighbors; ++j) { size_t adj = v + delta[j]; if (image[adj] == 1) { vstack[++top] = adj; break; } } if (j == numNeighbors) { image[v] = label; ++count; --top; } } ++numComponents; ++label; } } delete[] vstack; if (numComponents > 0) { components.resize(numComponents + 1); for (i = 1; i <= numComponents; ++i) { components[i].resize(numElements[i]); numElements[i] = 0; } for (i = 0; i < numPixels; ++i) { int value = image[i]; if (value != 0) { // Labels started at 2 to support the depth-first search, // so they need to be decremented for the correct labels. image[i] = --value; components[value][numElements[value]] = i; ++numElements[value]; } } } delete[] numElements; }
//---------------------------------------------------------------------------- bool ImageUtility2::ExtractBoundary(int x, int y, Image2<int>& image, std::vector<size_t>& boundary) { // Find a first boundary pixel. size_t const numPixels = image.GetNumPixels(); size_t i; for (i = image.GetIndex(x, y); i < numPixels; ++i) { if (image[i]) { break; } } if (i == numPixels) { // No boundary pixel found. return false; } int const dx[8] = { -1, 0, +1, +1, +1, 0, -1, -1 }; int const dy[8] = { -1, -1, -1, 0, +1, +1, +1, 0 }; // Create a new point list that contains the first boundary point. boundary.push_back(i); // The direction from background 0 to boundary pixel 1 is (dx[7],dy[7]). std::array<int,2> coord = image.GetCoordinates(i); int x0 = coord[0], y0 = coord[1]; int cx = x0, cy = y0; int nx = x0-1, ny = y0, dir = 7; // Traverse the boundary in clockwise order. Mark visited pixels as 2. image(cx, cy) = 2; bool notDone = true; while (notDone) { int j, nbr; for (j = 0, nbr = dir; j < 8; ++j, nbr = (nbr + 1) % 8) { nx = cx + dx[nbr]; ny = cy + dy[nbr]; if (image(nx, ny)) // next boundary pixel found { break; } } if (j == 8) // (cx,cy) is isolated { notDone = false; continue; } if (nx == x0 && ny == y0) // boundary traversal completed { notDone = false; continue; } // (nx,ny) is next boundary point, add point to list. Note that // the index for the pixel is computed for the original image, not // for the larger temporary image. boundary.push_back(image.GetIndex(nx, ny)); // Mark visited pixels as 2. image(nx, ny) = 2; // Start search for next point. cx = nx; cy = ny; dir = (j + 5 + dir) % 8; } return true; }
//---------------------------------------------------------------------------- void ImageUtility2::GetSkeleton(Image2<int>& image) { int const dim0 = image.GetDimension(0); int const dim1 = image.GetDimension(1); // Trim pixels, mark interior as 4. bool notDone = true; while (notDone) { if (MarkInterior(image, 4, Interior4)) { // No interior pixels, trimmed set is at most 2-pixels thick. notDone = false; continue; } if (ClearInteriorAdjacent(image, 4)) { // All remaining interior pixels are either articulation points // or part of blobs whose boundary pixels are all articulation // points. An example of the latter case is shown below. The // background pixels are marked with '.' rather than '0' for // readability. The interior pixels are marked with '4' and the // boundary pixels are marked with '1'. // // ......... // .....1... // ..1.1.1.. // .1.141... // ..14441.. // ..1441.1. // .1.11.1.. // ..1..1... // ......... // // This is a pathological problem where there are many small holes // (0-pixel with north, south, west, and east neighbors all // 1-pixels) that your application can try to avoid by an initial // pass over the image to fill in such holes. Of course, you do // have problems with checkerboard patterns... notDone = false; continue; } } // Trim pixels, mark interior as 3. notDone = true; while (notDone) { if (MarkInterior(image, 3, Interior3)) { // No interior pixels, trimmed set is at most 2-pixels thick. notDone = false; continue; } if (ClearInteriorAdjacent(image, 3)) { // All remaining 3-values can be safely removed since they are // not articulation points and the removal will not cause new // holes. for (int y = 0; y < dim1; ++y) { for (int x = 0; x < dim0; ++x) { if (image(x, y) == 3 && !IsArticulation(image, x, y)) { image(x, y) = 0; } } } notDone = false; continue; } } // Trim pixels, mark interior as 2. notDone = true; while (notDone) { if (MarkInterior(image, 2, Interior2)) { // No interior pixels, trimmed set is at most 1-pixel thick. // Call it a skeleton. notDone = false; continue; } if (ClearInteriorAdjacent(image, 2)) { // Removes 2-values that are not articulation points. for (int y = 0; y < dim1; ++y) { for (int x = 0; x < dim0; ++x) { if (image(x, y) == 2 && !IsArticulation(image, x, y)) { image(x, y) = 0; } } } notDone = false; continue; } } // Make the skeleton a binary image. size_t const numPixels = image.GetNumPixels(); for (size_t i = 0; i < numPixels; ++i) { if (image[i] != 0) { image[i] = 1; } } }
//---------------------------------------------------------------------------- bool SaveBMP32 (const std::string& name, const Image2<PixelBGRA8>& image) { FILE* outFile = fopen(name.c_str(), "wb"); if (!outFile) { assertion(false, "Cannot open file '%s' for write.\n", name.c_str()); return false; } const int numPixels = image.GetNumPixels(); const int numBytes = 4*image.GetNumPixels(); const int size = sizeOfBitMapFileHeader + sizeOfBitMapInfoHeader; PrivateBitMapFileHeader fileHeader; fileHeader.bfType = 0x4d42; fileHeader.bfSize = size + numBytes; fileHeader.bfReserved1 = 0; fileHeader.bfReserved2 = 0; fileHeader.bfOffBits = size; size_t numWritten = fwrite(&fileHeader.bfType, sizeof(unsigned short), 1, outFile); if (numWritten != 1) { assertion(false, "Failed to write (bfType).\n"); fclose(outFile); return false; } numWritten = fwrite(&fileHeader.bfSize, sizeof(unsigned long), 1, outFile); if (numWritten != 1) { assertion(false, "Failed to write (bfSize).\n"); fclose(outFile); return false; } numWritten = fwrite(&fileHeader.bfReserved1, sizeof(unsigned short), 1, outFile); if (numWritten != 1) { assertion(false, "Failed to write (bfReserved1).\n"); fclose(outFile); return false; } numWritten = fwrite(&fileHeader.bfReserved2, sizeof(unsigned short), 1, outFile); if (numWritten != 1) { assertion(false, "Failed to write (bfReserved2).\n"); fclose(outFile); return false; } numWritten = fwrite(&fileHeader.bfOffBits, sizeof(unsigned long), 1, outFile); if (numWritten != 1) { assertion(false, "Failed to write (bfOffBits).\n"); fclose(outFile); return false; } PrivateBitMapInfoHeader infoHeader; infoHeader.biSize = sizeOfBitMapInfoHeader; infoHeader.biWidth = image.GetDimension(0); infoHeader.biHeight = image.GetDimension(1); infoHeader.biPlanes = 1; infoHeader.biBitCount = 32; infoHeader.biCompression = BI_RGB; infoHeader.biSizeImage = 0; infoHeader.biXPelsPerMeter = 0; infoHeader.biYPelsPerMeter = 0; infoHeader.biClrUsed = 0; infoHeader.biClrImportant = 0; numWritten = fwrite(&infoHeader, sizeOfBitMapInfoHeader, 1, outFile); if (numWritten != 1) { assertion(false, "Failed to write (infoHeader).\n"); fclose(outFile); return false; } PixelBGRA8* source = image.GetPixels1D(); numWritten = fwrite(source, sizeof(PixelBGRA8), numPixels, outFile); if (numWritten != (size_t)numPixels) { assertion(false, "Failed to write (source).\n"); fclose(outFile); return false; } fclose(outFile); return true; }