現在、Kinect for Windows v2用のツールを開発しています(XBOX ONEのツールと同様)。私はいくつかの例に従おうとしましたが、カメラ画像、深度画像、およびopencvを使用して深度をrgbにマッピングする画像を示す実用的な例があります。しかし、マッピングを行うときに手が重複していることがわかります。これは、座標マッパー部分の問題が原因だと思います。
これがその例です:
そして、これがイメージを作成するコードスニペットです(例では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);
}
}
}
}
}
誰かがこれを解決する方法の手がかりを持っていますか?この重複を防ぐ方法は?
前もって感謝します
更新:
単純な深度画像のしきい値処理を行うと、次の画像が得られます。
これは多かれ少なかれ私が起こると思っていたものであり、バックグラウンドで重複した手を持っていません。バックグラウンドでこの重複した手を防ぐ方法はありますか?
最後に、待望の答えを書く時間があります。
実際に何が起こっているのかを理解するための理論から始めて、考えられる答えを見てみましょう。
座標系の原点として深度カメラを持つ3D点群からRGBカメラの画像平面内の画像に移行する方法を知ることから始める必要があります。これを行うには、カメラのピンホールモデルを使用するだけで十分です。
ここで、u
とv
は、RGBカメラの画像平面の座標です。方程式の右辺の最初の行列はカメラ行列、別名RGBカメラの固有行列です。次の行列は、外因性の回転と平行移動です。言い換えると、深度カメラ座標系からRGBカメラ座標系に変換するために必要な変換です。最後の部分は3Dポイントです。
基本的に、このようなものは、KinectSDKが行うことです。では、手を複製する原因となる問題は何でしょうか。ええと、実際には複数のポイントが同じピクセルに投影されます。
言い換えれば、問題の問題の文脈でそれを置くこと。
深度画像は、順序付けられた点群の表現であり、実際には3Dポイントに簡単に変換できる各ピクセルのu v
値をクエリしています。 SDKは投影を提供しますが、同じピクセルを指すことができます(通常、2つの隣接するポイント間のz軸の距離が長くなると、この問題が非常に簡単に発生する可能性があります。
さて、大きな質問ですが、どうすればこれを回避できますか....ええと、外因性が適用された後のポイントのZ値がわからないため、Kinect SDKを使用するかどうかはわかりません。したがって、使用することはできません。 Zバッファリング ....のような手法。ただし、Z値は非常に似ていると想定し、元のポイントクラウドの値を使用することができます(自己責任で)。
SDKを使用せずに手動で行っていた場合は、Extrinsicsをポイントに適用し、それらをイメージプレーンに投影して、どのポイントがどのピクセルにマップされ、存在するかどうかを別のマトリックスでマークすることができます。ポイントはすでにマッピングされています。z値を確認して比較し、常にカメラに最も近いポイントを残します。そうすれば、問題なく有効なマッピングが得られます。この方法は一種の素朴な方法です。問題が明らかになったので、おそらくより良い方法を得ることができます:)
私はそれが十分に明確であることを望みます。
追伸:現在、Kinect 2を持っていないので、この問題に関連する更新があるかどうか、または同じことがまだ発生しているかどうかを確認することはできません。 SDKの最初にリリースされたバージョン(プレリリースではない)を使用しました...したがって、多くの変更が行われた可能性があります...これが解決したかどうか誰かが知っている場合は、コメントを残してください:)
BodyIndexFrameを使用して、特定の値がプレーヤーに属しているかどうかを識別することをお勧めします。このようにして、プレーヤーに属していないRGBピクセルを拒否し、残りのピクセルを保持することができます。 CoordinateMapperが嘘をついているとは思いません。
いくつかの注意:
フレームが到着したときの私のアプローチは次のとおりです(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ソースを使用することです。