#include #include #include #include "imageskeletonextractor.h" // This is an implementation of the following paper: // // T. Y. ZHANG and C. Y. SUEN ImageSkeletonExtractor::~ImageSkeletonExtractor() { delete m_image; delete m_grayscaleImage; } void ImageSkeletonExtractor::setImage(QImage *image) { delete m_image; m_image = image; } QImage *ImageSkeletonExtractor::takeResultGrayscaleImage() { QImage *resultImage = m_grayscaleImage; m_grayscaleImage = nullptr; return resultImage; } bool ImageSkeletonExtractor::firstSubiterationSatisfied(int i, int j) { if (!isBlack(i, j)) return false; auto blackNeighbors = countBlackNeighbors(i, j); if (blackNeighbors < 2 || blackNeighbors > 6) return false; auto neighborTransitions = countNeighborTransitions(i, j); if (1 != neighborTransitions) return false; if (isBlack(i + neighborOffsets[P2].first, j + neighborOffsets[P2].second) && isBlack(i + neighborOffsets[P4].first, j + neighborOffsets[P4].second) && isBlack(i + neighborOffsets[P6].first, j + neighborOffsets[P6].second)) { return false; } if (isBlack(i + neighborOffsets[P4].first, j + neighborOffsets[P4].second) && isBlack(i + neighborOffsets[P6].first, j + neighborOffsets[P6].second) && isBlack(i + neighborOffsets[P8].first, j + neighborOffsets[P8].second)) { return false; } return true; } bool ImageSkeletonExtractor::secondSubiterationSatisfied(int i, int j) { if (!isBlack(i, j)) return false; auto blackNeighbors = countBlackNeighbors(i, j); if (blackNeighbors < 2 || blackNeighbors > 6) return false; auto neighborTransitions = countNeighborTransitions(i, j); if (1 != neighborTransitions) return false; if (isBlack(i + neighborOffsets[P2].first, j + neighborOffsets[P2].second) && isBlack(i + neighborOffsets[P4].first, j + neighborOffsets[P4].second) && isBlack(i + neighborOffsets[P8].first, j + neighborOffsets[P8].second)) { return false; } if (isBlack(i + neighborOffsets[P2].first, j + neighborOffsets[P2].second) && isBlack(i + neighborOffsets[P6].first, j + neighborOffsets[P6].second) && isBlack(i + neighborOffsets[P8].first, j + neighborOffsets[P8].second)) { return false; } return true; } void ImageSkeletonExtractor::calculateAreaAndBlackPixels() { m_area = 0; m_blackPixels.clear(); for (int i = 1; i < (int)m_grayscaleImage->width() - 1; ++i) { for (int j = 1; j < (int)m_grayscaleImage->height() - 1; ++j) { if (isBlack(i, j)) { ++m_area; m_blackPixels.insert({i, j}); } } } } const std::set> &ImageSkeletonExtractor::getBlackPixels() { return m_blackPixels; } int ImageSkeletonExtractor::getArea() { return m_area; } void ImageSkeletonExtractor::extract() { m_grayscaleImage = new QImage(m_image->convertToFormat(QImage::Format_Grayscale8)); calculateAreaAndBlackPixels(); while (true) { std::vector> firstSatisfied; for (int i = 1; i < (int)m_grayscaleImage->width() - 1; ++i) { for (int j = 1; j < (int)m_grayscaleImage->height() - 1; ++j) { if (firstSubiterationSatisfied(i, j)) firstSatisfied.push_back(std::make_pair(i, j)); } } for (const auto &it: firstSatisfied) setWhite(it.first, it.second); std::vector> secondSatisfied; for (int i = 1; i < (int)m_grayscaleImage->width() - 1; ++i) { for (int j = 1; j < (int)m_grayscaleImage->height() - 1; ++j) { if (secondSubiterationSatisfied(i, j)) secondSatisfied.push_back(std::make_pair(i, j)); } } for (const auto &it: secondSatisfied) setWhite(it.first, it.second); if (firstSatisfied.empty() && secondSatisfied.empty()) break; } } void ImageSkeletonExtractor::getSkeleton(std::vector> *skeleton) { if (nullptr == m_grayscaleImage) return; std::map, std::vector>> links; for (int i = 1; i < (int)m_grayscaleImage->width() - 1; ++i) { for (int j = 1; j < (int)m_grayscaleImage->height() - 1; ++j) { if (!isBlack(i, j)) continue; auto ij = std::make_pair(i, j); auto p2 = std::make_pair(i + neighborOffsets[P2].first, j + neighborOffsets[P2].second); bool hasP3 = true; bool hasP5 = true; bool hasP7 = true; bool hasP9 = true; if (isBlack(p2.first, p2.second)) { links[ij].push_back(p2); hasP3 = false; hasP9 = false; } auto p4 = std::make_pair(i + neighborOffsets[P4].first, j + neighborOffsets[P4].second); if (isBlack(p4.first, p4.second)) { links[ij].push_back(p4); hasP3 = false; hasP5 = false; } auto p6 = std::make_pair(i + neighborOffsets[P6].first, j + neighborOffsets[P6].second); if (isBlack(p6.first, p6.second)) { links[ij].push_back(p6); hasP5 = false; hasP7 = false; } auto p8 = std::make_pair(i + neighborOffsets[P8].first, j + neighborOffsets[P8].second); if (isBlack(p8.first, p8.second)) { links[ij].push_back(p8); hasP7 = false; hasP9 = false; } if (hasP3) { auto p3 = std::make_pair(i + neighborOffsets[P3].first, j + neighborOffsets[P3].second); if (isBlack(p3.first, p3.second)) { links[ij].push_back(p3); } } if (hasP5) { auto p5 = std::make_pair(i + neighborOffsets[P5].first, j + neighborOffsets[P5].second); if (isBlack(p5.first, p5.second)) { links[ij].push_back(p5); } } if (hasP7) { auto p7 = std::make_pair(i + neighborOffsets[P7].first, j + neighborOffsets[P7].second); if (isBlack(p7.first, p7.second)) { links[ij].push_back(p7); } } if (hasP9) { auto p9 = std::make_pair(i + neighborOffsets[P9].first, j + neighborOffsets[P9].second); if (isBlack(p9.first, p9.second)) { links[ij].push_back(p9); } } } } auto calculateRouteLength = [&](const std::pair &branch, const std::pair &start) { std::set> visited; visited.insert(branch); std::queue> waitPoints; waitPoints.push(start); size_t addLength = 0; while (!waitPoints.empty()) { auto point = waitPoints.front(); waitPoints.pop(); if (visited.find(point) != visited.end()) continue; visited.insert(point); auto findLink = links.find(point); if (findLink == links.end()) break; if (findLink->second.size() > 2) { addLength = links.size(); // This will make sure the branch node is not been removed break; } for (const auto &it: findLink->second) { if (visited.find(it) != visited.end()) continue; waitPoints.push(it); } } return visited.size() + addLength; }; for (auto &it: links) { if (it.second.size() > 2) { std::vector, size_t>> routes; routes.reserve(it.second.size()); for (size_t i = 0; i < it.second.size(); ++i) { routes.push_back(std::make_pair(it.second[i], calculateRouteLength(it.first, it.second[i]))); } std::sort(routes.begin(), routes.end(), [](const std::pair, size_t> &first, const std::pair, size_t> &second) { return first.second < second.second; }); it.second = std::vector> {routes[routes.size() - 2].first, routes[routes.size() - 1].first}; } } std::queue> waitPoints; for (const auto &it: links) { if (1 == it.second.size()) { waitPoints.push(it.first); break; } } std::set> visited; while (!waitPoints.empty()) { auto point = waitPoints.front(); waitPoints.pop(); if (visited.find(point) != visited.end()) continue; visited.insert(point); skeleton->push_back(point); auto findLink = links.find(point); if (findLink == links.end()) break; for (const auto &it: findLink->second) { if (visited.find(it) != visited.end()) continue; waitPoints.push(it); } } }