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

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

вот пример: error

И вот фрагмент кода, который создает изображение (образ 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);
                }
            }

        }
    }
}

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

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

UPDATE:

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

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

Ответы

Ответ 1

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

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

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

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

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

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

Иными словами, в контексте проблемы в вопросе.

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

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

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

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

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

Ответ 2

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

Несколько примечаний:

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

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

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.