Глубина Kinect для Windows v2 для смещения цвета изображения

В настоящее время я разрабатываю инструмент для Kinect для Windows v2 (аналогичный тому, что есть в XBOX ONE). Я попытался следовать некоторым примерам, и у меня есть рабочий пример, который показывает изображение с камеры, изображение глубины и изображение, которое отображает глубину на rgb, используя opencv. Но я вижу, что это дублирует мою руку при выполнении отображения, и я думаю, что это связано с чем-то неправильным в части отображения координат.

вот пример этого:ошибка

А вот фрагмент кода, который создает изображение (изображение rgbd в примере)

void KinectViewer::create_rgbd(cv::Mat& depth_im, cv::Mat& rgb_im, cv::Mat& rgbd_im){
    HRESULT hr = m_pCoordinateMapper->MapDepthFrameToColorSpace(cDepthWidth * cDepthHeight, (UINT16*)depth_im.data, cDepthWidth * cDepthHeight, m_pColorCoordinates);
    rgbd_im = cv::Mat::zeros(depth_im.rows, depth_im.cols, CV_8UC3);
    double minVal, maxVal;
    cv::minMaxLoc(depth_im, &minVal, &maxVal);
    for (int i=0; i < cDepthHeight; i++){
        for (int j=0; j < cDepthWidth; j++){
            if (depth_im.at<UINT16>(i, j) > 0 && depth_im.at<UINT16>(i, j) < maxVal * (max_z / 100) && depth_im.at<UINT16>(i, j) > maxVal * min_z /100){
                double a = i * cDepthWidth + j;
                ColorSpacePoint colorPoint = m_pColorCoordinates[i*cDepthWidth+j];
                int colorX = (int)(floor(colorPoint.X + 0.5));
                int colorY = (int)(floor(colorPoint.Y + 0.5));
                if ((colorX >= 0) && (colorX < cColorWidth) && (colorY >= 0) && (colorY < cColorHeight))
                {
                    rgbd_im.at<cv::Vec3b>(i, j) = rgb_im.at<cv::Vec3b>(colorY, colorX);
                }
            }

        }
    }
}

У кого-нибудь есть подсказка, как это решить? Как предотвратить это дублирование?

заранее спасибо

ОБНОВИТЬ:

Если я делаю простую настройку глубины изображения, я получаю следующее изображение:пороговая

Это то, что более или менее я ожидал, и не иметь дублирующую руку на заднем плане. Есть ли способ предотвратить эту двойную руку на заднем плане?

2 ответа

Решение

Наконец-то у меня есть время написать долгожданный ответ.

Давайте начнем с некоторой теории, чтобы понять, что на самом деле происходит, а затем возможный ответ.

Мы должны начать с знания пути перехода от трехмерного облака точек, в котором в качестве источника системы координат используется камера глубины, к изображению в плоскости изображения камеры RGB. Для этого достаточно использовать модель камеры-обскуры:

введите описание изображения здесь

Здесь, u а также v являются координатами в плоскости изображения камеры RGB. первая матрица в правой части уравнения - матрица камеры, встроенная в камеру RGB. Следующая матрица - это вращение и перемещение внешних элементов, или, точнее, преобразование, необходимое для перехода из системы координат камеры глубины в систему координат камеры RGB. Последняя часть - это 3D-точка.

По сути, это то, что делает Kinect SDK. Итак, что может пойти не так, что рука будет дублирована? ну, на самом деле более одного пункта проецирует на один и тот же пиксель....

Чтобы выразить это другими словами и в контексте проблемы в вопросе.

Изображение глубины - это представление упорядоченного облака точек, и я запрашиваю u v значения каждого из его пикселей, которые в действительности могут быть легко преобразованы в трехмерные точки. SDK дает вам проекцию, но он может указывать на один и тот же пиксель (обычно, большее расстояние по оси z между двумя соседними точками может решить эту проблему довольно легко.

Теперь большой вопрос, как вы можете избежать этого... ну, я не уверен, что использую Kinect SDK, так как вы не знаете значение Z точек ПОСЛЕ примененного внешнего вида, поэтому использовать его невозможно такая техника, как буферизация Z. Тем не менее, вы можете предположить, что значение Z будет очень похожим, и использовать значения из оригинального pointcloud (на свой страх и риск).

Если вы делали это вручную, а не с помощью SDK, вы можете применить внешние элементы к точкам и использовать их для проецирования на плоскость изображения, помечая в другой матрице, какая точка отображается на какой пиксель и если он существует. Точка уже нанесена на карту, проверьте значения z и сравните их и всегда оставляйте самую близкую точку к камере. Тогда у вас будет действительное сопоставление без проблем. Этот путь довольно наивный, возможно, вы можете получить лучшие, так как проблема теперь ясна:)

Надеюсь, это достаточно ясно.

PS: на данный момент у меня нет Kinect 2, поэтому я не могу попытаться выяснить, есть ли обновление относительно этой проблемы или все еще происходит то же самое. Я использовал первую выпущенную версию (не предварительную) SDK... Так что, возможно, произошло много изменений... Если кто-то знает, решено ли это, просто оставьте комментарий:)

Я предлагаю вам использовать BodyIndexFrame, чтобы определить, принадлежит ли конкретное значение игроку или нет. Таким образом, вы можете отклонить любой пиксель RGB, который не принадлежит плееру, и оставить остальные. Я не думаю, что CoordinateMapper врет.

Несколько заметок:

  • Включите источник BodyIndexFrame в ваш фрейдер
  • Используйте MapColorFrameToDepthSpace вместо MapDepthFrameToColorSpace; таким образом, вы получите изображение HD на переднем плане
  • Найдите соответствующие DepthSpacePoint и глубина X, глубина Y вместо ColorSpacePoint и colorX, colorY

Вот мой подход, когда приходит кадр (это в C#):

depthFrame.CopyFrameDataToArray(_depthData);
colorFrame.CopyConvertedFrameDataToArray(_colorData, ColorImageFormat.Bgra);
bodyIndexFrame.CopyFrameDataToArray(_bodyData);

_coordinateMapper.MapColorFrameToDepthSpace(_depthData, _depthPoints);

Array.Clear(_displayPixels, 0, _displayPixels.Length);

for (int colorIndex = 0; colorIndex < _depthPoints.Length; ++colorIndex)
{
    DepthSpacePoint depthPoint = _depthPoints[colorIndex];

    if (!float.IsNegativeInfinity(depthPoint.X) && !float.IsNegativeInfinity(depthPoint.Y))
    {
        int depthX = (int)(depthPoint.X + 0.5f);
        int depthY = (int)(depthPoint.Y + 0.5f);

        if ((depthX >= 0) && (depthX < _depthWidth) && (depthY >= 0) && (depthY < _depthHeight))
        {
            int depthIndex = (depthY * _depthWidth) + depthX;
            byte player = _bodyData[depthIndex];

            // Identify whether the point belongs to a player
            if (player != 0xff)
            {
                int sourceIndex = colorIndex * BYTES_PER_PIXEL;

                _displayPixels[sourceIndex] = _colorData[sourceIndex++];    // B
                _displayPixels[sourceIndex] = _colorData[sourceIndex++];    // G
                _displayPixels[sourceIndex] = _colorData[sourceIndex++];    // R
                _displayPixels[sourceIndex] = 0xff;                         // A
            }
        }
    }
}

Вот инициализация массивов:

BYTES_PER_PIXEL = (PixelFormats.Bgr32.BitsPerPixel + 7) / 8;

_colorWidth = colorFrame.FrameDescription.Width;
_colorHeight = colorFrame.FrameDescription.Height;
_depthWidth = depthFrame.FrameDescription.Width;
_depthHeight = depthFrame.FrameDescription.Height;
_bodyIndexWidth = bodyIndexFrame.FrameDescription.Width;
_bodyIndexHeight = bodyIndexFrame.FrameDescription.Height;
_depthData = new ushort[_depthWidth * _depthHeight];
_bodyData = new byte[_depthWidth * _depthHeight];
_colorData = new byte[_colorWidth * _colorHeight * BYTES_PER_PIXEL];
_displayPixels = new byte[_colorWidth * _colorHeight * BYTES_PER_PIXEL];
_depthPoints = new DepthSpacePoint[_colorWidth * _colorHeight];

Обратите внимание, что массив _depthPoints имеет размер 1920x1080.

Еще раз, самое важное, это использовать источник BodyIndexFrame.

Другие вопросы по тегам