Kinect学习笔记第六篇Coordinate Mapping(坐标映射) 上篇
简述:
本次学习的是如何利用kinect的人体识别然后分离彩色图像中的人体,将至移植到其他图片上,效果类似于AE键控中的绿幕效果。
用到的流有ColorFrame/DepthFrame/BodyIndexFrame.
因此需要打开“MultiSourceFrame”多种数据流源与多种流阅读器“ this.multiFrameSourceReader = this.kinectSensor.OpenMultiSourceFrameReader(FrameSourceTypes.Depth | FrameSourceTypes.Color | FrameSourceTypes.BodyIndex); ”
相应的事件是多种流到达事件“multiFrameSourceReader.MultiSourceFrameArrived”。
之所以用到深度流,之一是因为索引是对应深度帧的512*424,进而用深度帧对应彩色,最终实现对应1920*1080,在中间转换时明显看见它采用了“周围统一”,即:
阴影中心点(深度点)“统治”阴影范围内的点(多个像素点)。
2073600个彩色映射深度点,对应彩色像素2073600(1920*1080)个,大约10(2073600/(424*512))个左右像素点对应一个深度点。
知识储备:
(这里可深度学习!)
Coordinate Mapper(坐标映射器):可实现以下
(2法一般指访问底层缓存与使用数组)
1所有相机点映射到彩色空间*2法
2个别相机点映射到彩色空间(第二实例采用-下篇)
3所有相机点映射到深度空间*2法
4个别相机点映射到深度空间
5彩色帧映射到相机空间*2法
6彩色帧映射到深度空间*2法(第一个实例采用)
7深度帧映射到相机空间*2法
8深度帧映射到彩色空间*2法
9所有深度点映射到相机空间*2法
10所有深度点映射到彩色空间*2法
11个别深度点映射到相机空间
12个别深度点映射到彩色空间
Bitmap中每个像素点的值通常是这样表示的:0x80FFFFFF
0x表示是十六进制表示法,80在这是50%透明度(80在16进制中是1/2位置)FFFFFF六位表彩色。排列格式ARGB。A通道(掌管透明度)。
Bitmap.Config ARGB_4444 16 每个像素 占四位
Bitmap.Config ARGB_8888 32 每个像素 占八位
Bitmap.Config RGB_565 16 R占5位 G占6位 B占5位 没有透明度(A)
骨骼点数据和深度数据或者彩色影像数据的测量方法不同。每一种类的数据(深度数据,影像数据,骨骼数据)都是在特定的集合坐标或空间内定义的。深度数据或者影像数据用像素来表示,X,Y位置从左上角以0开始。深度数据的Z方位数据以毫米为单位。与这些不同的是,骨骼空间是以米为单位来描述的,以深度传感器为中心,其X,Y值为0。骨骼坐空间坐标系是右手坐标系,X正方向朝右,Y周正方向朝上,X轴数据范围为-2.2~2.2,总共范围为4.2米,Y周范围为-1.6~1.6米,Z轴范围为0~4米。
代码后有流程图可看。
CoordinateMapping第一程序代码实例:
其中包含了附加的便于理解的输出,需要手都设为True与取消注释才可。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using Microsoft.Kinect;
namespace myCoordinateMappingViewer
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private readonly int bytesPerPixel = (PixelFormats.Bgr32.BitsPerPixel + 7) / 8;
private KinectSensor kinectSensor = null;
private CoordinateMapper coordinateMapper = null;//坐标映射器
private MultiSourceFrameReader multiFrameSourceReader = null;//多源阅读器
private WriteableBitmap bitmap = null;
private uint bitmapBackBufferSize = 0;//Bitmap缓冲区大小
private DepthSpacePoint[] colorMappedToDepthPoints = null;
bool savejudge1 = false;//设为true可保存一帧的this.colorMappedTodepthPoints查看
bool savejudge2 =false;//设为true可保存一帧的this.bodyIndexDataPointer查看
public MainWindow()
{
this.kinectSensor = KinectSensor.GetDefault();
this.multiFrameSourceReader = this.kinectSensor.OpenMultiSourceFrameReader(FrameSourceTypes.Depth | FrameSourceTypes.Color | FrameSourceTypes.BodyIndex);
this.multiFrameSourceReader.MultiSourceFrameArrived += this.Reader_MultiSourceFrameArrived;
this.coordinateMapper = this.kinectSensor.CoordinateMapper;
//DepthFrame
FrameDescription depthFrameDescription = this.kinectSensor.DepthFrameSource.FrameDescription;
int depthWidth = depthFrameDescription.Width;
int depthHeight = depthFrameDescription.Height;
//ColorFrame
FrameDescription colorFrameDescription = this.kinectSensor.ColorFrameSource.FrameDescription;
int colorWidth = colorFrameDescription.Width;
int colorHeight = colorFrameDescription.Height;
this.colorMappedToDepthPoints = new DepthSpacePoint[colorWidth * colorHeight];//彩色对应深度点数组大小(1920*1080)
this.bitmap = new WriteableBitmap(colorWidth, colorHeight, 96.0, 96.0, PixelFormats.Bgra32, null);
// Calculate the WriteableBitmap back buffer size 1920*1080*4(以下这么写是让你了解底层数据大小)
this.bitmapBackBufferSize = (uint)((this.bitmap.BackBufferStride * (this.bitmap.PixelHeight - 1)) + (this.bitmap.PixelWidth * this.bytesPerPixel));
this.kinectSensor.Open();
this.DataContext = this;
InitializeComponent();
}
private void Reader_MultiSourceFrameArrived(object sender, MultiSourceFrameArrivedEventArgs e)
{
int depthWidth = 0;
int depthHeight = 0;
DepthFrame depthFrame = null;
ColorFrame colorFrame = null;
BodyIndexFrame bodyIndexFrame = null;
bool isBitmapLocked = false;
MultiSourceFrame multiSourceFrame = e.FrameReference.AcquireFrame();
// If the Frame has expired by the time we process this event, return.
if (multiSourceFrame == null)
{
return;
}
//用try/finnally来处理数据,保证用完后释放清空并解锁缓存
try
{
depthFrame = multiSourceFrame.DepthFrameReference.AcquireFrame();
colorFrame = multiSourceFrame.ColorFrameReference.AcquireFrame();
bodyIndexFrame = multiSourceFrame.BodyIndexFrameReference.AcquireFrame();
//确保接受到数据,否则返回,以防止finally释放为空
if ((depthFrame == null) || (colorFrame == null) || (bodyIndexFrame == null))
{
return;
}
// Process Depth
FrameDescription depthFrameDescription = depthFrame.FrameDescription;
depthWidth = depthFrameDescription.Width;
depthHeight = depthFrameDescription.Height;
// Access the depth frame data directly via LockImageBuffer to avoid making a copy
//深度数据映射到一个与彩色图大小相同的DepthSpacePoints处
using (KinectBuffer depthFrameData = depthFrame.LockImageBuffer())
{
//1数组,低效
//unsafe
//{
// //ushort*pointer=(ushort*)depthFrameData.UnderlyingBuffer;
// ushort[] mydepthData = null;
// mydepthData = new ushort[depthFrameData.Size / 2];
// for (int i = 0; i < depthFrameData.Size / 2; i++)
// {
// //mydepthData[i] = pointer[i];
// mydepthData[i] = ((ushort*)depthFrameData.UnderlyingBuffer)[i];
// }
// this.coordinateMapper.MapColorFrameToDepthSpace(mydepthData, this.colorMappedToDepthPoints);
//}
//2底层缓存,高效
this.coordinateMapper.MapColorFrameToDepthSpaceUsingIntPtr(
depthFrameData.UnderlyingBuffer,
depthFrameData.Size,
this.colorMappedToDepthPoints);
}
//这里配合保存一帧的this.colorMappedToDepthPoints
/*if(true)
{
FileStream fs = new FileStream("D:\\ColorMappedToDepthPoints.txt", FileMode.OpenOrCreate);
StreamWriter sw = new StreamWriter(fs);
int numx = 0,num=0;;
foreach (var x in this.colorMappedToDepthPoints)
{
sw.Write("[{0},{1}] ", x.X,x.Y);
numx++;
num++;
if (numx == 10)
{
sw.Write("\r\n");
numx = 0;
}
}
sw.Write("总计{0}",num);
sw.Flush();
sw.Close();
fs.Close();
this.savejudge1 = false;
}*/
// We're done with the DepthFrame
depthFrame.Dispose();
depthFrame = null;
// Process Color
// Lock the bitmap for writing
this.bitmap.Lock();
isBitmapLocked = true;
//彩色数据写入this.bitmap.BackBuffer
colorFrame.CopyConvertedFrameDataToIntPtr(this.bitmap.BackBuffer, this.bitmapBackBufferSize, ColorImageFormat.Bgra);
// We're done with the ColorFrame
colorFrame.Dispose();
colorFrame = null;
// We'll access the body index data directly to avoid a copy
using (KinectBuffer bodyIndexData = bodyIndexFrame.LockImageBuffer())
{
// 使用整一帧存在的身体索引
unsafe
{
byte* bodyIndexDataPointer = (byte*)bodyIndexData.UnderlyingBuffer;
int colorMappedToDepthPointCount = this.colorMappedToDepthPoints.Length;
fixed (DepthSpacePoint* colorMappedToDepthPointsPointer = this.colorMappedToDepthPoints)
{
// Treat the color data as 4-byte pixels
uint* bitmapPixelsPointer = (uint*)this.bitmap.BackBuffer;
// Loop over each row and column of the color image
// Zero out any pixels that don't correspond to a body index
for (int colorIndex = 0; colorIndex < colorMappedToDepthPointCount; ++colorIndex)
{
float colorMappedToDepthX = colorMappedToDepthPointsPointer[colorIndex].X;
float colorMappedToDepthY = colorMappedToDepthPointsPointer[colorIndex].Y;
// The sentinel value is -inf, -inf, meaning that no depth pixel corresponds to this color pixel.
if (!float.IsNegativeInfinity(colorMappedToDepthX) &&
!float.IsNegativeInfinity(colorMappedToDepthY))
{
// Make sure the depth pixel maps to a valid(有效) point in color space
int depthX = (int)(colorMappedToDepthX + 0.5f);//+0.5f For进位
int depthY = (int)(colorMappedToDepthY + 0.5f);
// If the point is not valid, there is no body index there.
if ((depthX >= 0) && (depthX < depthWidth) && (depthY >= 0) && (depthY < depthHeight))
{
int depthIndex = (depthY * depthWidth) + depthX;
/*if (this.savejudge2)
{
FileStream fs = new FileStream("D:\\bodyIndexDataPointer.txt", FileMode.OpenOrCreate);
StreamWriter sw = new StreamWriter(fs);
int num = 0;
for (int i = 0; i < depthWidth; i++)
{
for (int j = 0; j < depthHeight; ++j)
{
sw.Write("{0} ", bodyIndexDataPointer[i + j * depthHeight]);
num++;
}
sw.Write("\r\n");
}
sw.Write("共计{0}", num);
sw.Flush();
sw.Close();
fs.Close();
this.savejudge2 = false;
}*/
// If we are tracking a body for the current pixel, do not zero out the pixel
if (bodyIndexDataPointer[depthIndex] != 0xff)
{
continue;
}
}
}
bitmapPixelsPointer[colorIndex] = 0;
//bitmapPixelsPointer[colorIndex] = 0xFF0000FF;//Ox表十六进制,然后透明度(2位)+彩色值(6位)0透明度蓝色
}
}
//更新位图(覆盖)
this.bitmap.AddDirtyRect(new Int32Rect(0, 0, this.bitmap.PixelWidth, this.bitmap.PixelHeight));
}
}
}
finally
{
if (isBitmapLocked) this.bitmap.Unlock();
if (depthFrame != null) depthFrame.Dispose();
if (colorFrame != null) colorFrame.Dispose();
if (bodyIndexFrame != null) bodyIndexFrame.Dispose();
}
}
public ImageSource ImageSource
{
get
{
return this.bitmap;
}
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (this.multiFrameSourceReader != null)
{
// MultiSourceFrameReder is IDisposable
this.multiFrameSourceReader.Dispose();
this.multiFrameSourceReader = null;
}
if (this.kinectSensor != null)
{
this.kinectSensor.Close();
this.kinectSensor = null;
}
}
}
}
渣手写流程图一张:
好长时间不写字,都.......ORZ 虽然本来字也差。~(@^_^@)~
其中的“无人清零”就是将该点无人体索引像素透明设为100%。
一些小东西:
关于映射后的点为什么有负值?
因为不同的视场和纵横比,两个摄像头没有看到同样的事情。具体地说,在彩色图像的左和右边界,有一个区域的深度相机看不到两边(大约6%)。
同样,在顶部和底部的深度图像彩色摄像头看不到的区域。
这就是负值来自:前几排的深度图像映射到值外(上图)彩色图像,导致负面的Y值。同样的,最后几行映射到Y值大于1080,也在彩色图像。
顺便说一句。当使用逆方法(MapColorFrameToDepthSpace()),最左边的和右列映射到无效值,因为它们是在深度图像。
附:
第一程序xaml添加代码:
<Image HorizontalAlignment="Left" VerticalAlignment="Top" Source="Picture\10921221_003010567114_2.jpg" Stretch="UniformToFill" />
<Image HorizontalAlignment="Left" VerticalAlignment="Top" Source="{Binding ImageSource}" Stretch="UniformToFill"/>
fixed 语句设置指向托管变量的指针,并在执行该语句期间“固定”此变量。 如果没有 fixed 语句,则指向可移动托管变量的指针的作用很小,因为垃圾回收可能不可预知地重定位变量。 C# 编译器只允许在 fixed 语句中分配指向托管变量的指针。