Ответ 1
Ваша проблема с approxPolyDP
обусловлена форматированием ввода в approxPolyDP
.
Описание
approxPolyDP
ожидает, что его вход будет вектором Point
s. Эти точки определяют полигональную кривую, которая будет обрабатываться с помощью approxPolyDP
. Кривая может быть открытой или закрытой, которая может управляться флагом.
Очень важно упорядочить точки в списке. Точно так же, как один прослеживает многоугольник вручную, каждая последующая точка в векторе должна быть следующей вершиной многоугольника, по часовой стрелке или против часовой стрелки.
Если список точек хранится в растровом порядке (отсортированный по Y, а затем X), то point[k]
и point[k+1]
необязательно относятся к одной и той же кривой. Это является причиной проблемы.
Эта проблема объясняется иллюстрациями в OpenCV - Как извлечь ребра из результата Canny Function?. Цитата из Михаил: "Canny не соединяет пиксели в цепочки или сегменты".
Иллюстрация "растрового порядка", создаваемого Canny
.
Иллюстрация "порядка контура", ожидаемого approxPolyDP
Что необходимо
Вам нужен список "цепочек крайних пикселей". Каждая цепочка должна содержать граничные пиксели, которые смежны друг с другом, подобно тому, как кто-то отслеживает контур объекта карандашом, без кончика карандаша, оставляющего бумагу.
Это не то, что возвращается из методов обнаружения границ, например Canny
. Дальнейшая обработка необходима для преобразования карты ребер в цепи соседних (непрерывных) пикселов края.
Предлагаемые решения
(1) Использовать двоичный threshold
вместо обнаружения края в качестве входа в findContours
Это применимо, если существует пороговое значение, которое отделяет руку от фона и что это значение работает для всей руки (а не только для части руки).
(2) Сканируйте карту края и создайте список соседних пикселей, исследуя соседние пиксели каждого края.
Это похоже на алгоритм связанных компонентов, за исключением того, что вместо того, чтобы находить blob (где вам нужно знать только каждое членство в пикселях), вы пытаетесь найти цепочки пикселей, чтобы вы могли указать предыдущий и следующий пиксели края вдоль цепи.
(3) Используйте альтернативный алгоритм обнаружения края, например, "Граничный чертеж".
Подробности можно найти на http://ceng.anadolu.edu.tr/cv/EdgeDrawing/
К сожалению, это не предоставляется из коробки OpenCV, поэтому вам может потребоваться найти реализацию в другом месте.
Пример кода для опции № 1.
#include <stdint.h>
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat matInput = imread("~/Data/mA9EE.png", false);
// ---- Preprocessing of depth map. (Optional.) ----
GaussianBlur(matInput, matInput, cv::Size(9, 9), 4.0);
// ---- Here, we use cv::threshold instead of cv::Canny as explained above ----
Mat matEdge;
//Canny(matInput, matEdge, 0.1, 1.0);
threshold(matInput, matEdge, 192.0, 255.0, THRESH_BINARY_INV);
// ---- Use findContours to find chains of consecutive edge pixels ----
vector<vector<Point> > contours;
findContours(matEdge, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
// ---- Code below is only used for visualizing the result. ----
Mat matContour(matEdge.size(), CV_8UC1);
for (size_t k = 0; k < contours.size(); ++k)
{
const vector<Point>& contour = contours[k];
for (size_t k2 = 0; k2 < contour.size(); ++k2)
{
const Point& p = contour[k2];
matContour.at<uint8_t>(p) = 255;
}
}
imwrite("~/Data/output.png", matContour);
cout << "Done!" << endl;
return 0;
}