如何用Kinect每10分钟捕获骨骼图像并保存?(C#)
Kinect定时捕获图像保存的代码优化与问题排查
嘿,你已经基于Kinect SDK的示例搭好了定时截图的基础框架,不过咱们可以从几个方面优化代码,同时排查可能踩坑的地方:
一、当前代码的潜在问题
- 线程安全隐患:你用的
System.Timers.Timer的Elapsed事件是在非UI线程触发的,而你直接操作了WPF的CompositeImage元素——WPF的UI控件只能在创建它的Dispatcher线程里操作,这大概率会导致线程异常。 - 定时器生命周期问题:
saveTimer是方法内的局部变量,方法执行完后可能被GC回收,导致定时器莫名停止工作,得把它改成类的成员变量。 - 截图效率问题:通过VisualBrush渲染UI元素来截图的方式,如果只是需要Kinect的原始图像,有点绕远路——直接从Kinect的帧数据保存会更高效,还能避免UI渲染带来的问题。
二、优化后的完整代码示例
首先,在你的类里声明定时器的成员变量:
// 声明为类级别的成员变量,避免被GC回收 private Timer _saveTimer;
然后修改启动定时器的方法:
private void startSaveTimer() { // 10分钟 = 600000毫秒 _saveTimer = new Timer(600000); _saveTimer.Elapsed += saveTimer_Elapsed; _saveTimer.Start(); }
接下来修正定时触发的事件处理逻辑,加入UI线程调度:
private void saveTimer_Elapsed(object sender, ElapsedEventArgs e) { // 切换到UI线程执行所有和UI相关的操作 Application.Current.Dispatcher.Invoke(() => { try { RenderTargetBitmap renderBitmap = new RenderTargetBitmap( (int)CompositeImage.ActualWidth, (int)CompositeImage.ActualHeight, 96.0, 96.0, PixelFormats.Pbgra32); DrawingVisual dv = new DrawingVisual(); using (DrawingContext dc = dv.RenderOpen()) { VisualBrush brush = new VisualBrush(CompositeImage); dc.DrawRectangle(brush, null, new Rect(new Point(), new Size(CompositeImage.ActualWidth, CompositeImage.ActualHeight))); } renderBitmap.Render(dv); BitmapEncoder encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(renderBitmap)); // 优化时间戳格式,加上日期避免同一天内文件名重复 string timeStamp = DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss", CultureInfo.CurrentUICulture.DateTimeFormat); string saveFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures); string filePath = Path.Combine(saveFolder, $"KinectScreenshot-bodybasic-{timeStamp}.png"); using (FileStream fs = new FileStream(filePath, FileMode.Create)) { encoder.Save(fs); } // 更新状态文本 StatusText = string.Format(Properties.Resources.SavedScreenshotStatusTextFormat, filePath); } catch (IOException ex) { StatusText = string.Format(Properties.Resources.FailedScreenshotStatusTextFormat, filePath); // 可以加个日志输出方便调试 // Debug.WriteLine($"截图保存失败:{ex.Message}"); } catch (InvalidOperationException ex) { StatusText = "线程错误:无法在非UI线程操作UI元素"; // Debug.WriteLine($"线程异常详情:{ex.Message}"); } }); }
三、额外的优化方向
- 直接从Kinect帧数据保存:如果你不需要UI上的合成效果,建议直接缓存Kinect的ColorFrame/DepthFrame数据,定时从缓存中取数据生成图片,跳过UI渲染步骤。示例逻辑如下:
// 类成员变量缓存最新的彩色帧数据 private byte[] _latestColorFrameData; private object _frameLock = new object(); // 在ColorFrameArrived事件中更新缓存 private void ColorFrameReader_FrameArrived(object sender, ColorFrameArrivedEventArgs e) { using (ColorFrame colorFrame = e.FrameReference.AcquireFrame()) { if (colorFrame != null) { var frameDesc = colorFrame.FrameDescription; lock (_frameLock) { _latestColorFrameData = new byte[frameDesc.BytesPerPixel * frameDesc.LengthInPixels]; colorFrame.CopyConvertedFrameDataToArray(_latestColorFrameData, ColorImageFormat.Bgra); } } } } // 定时任务中直接用缓存数据生成图片 private void SaveLatestFrame() { lock (_frameLock) { if (_latestColorFrameData == null) return; var frameDesc = _kinectSensor.ColorFrameSource.FrameDescription; BitmapSource bitmapSource = BitmapSource.Create( frameDesc.Width, frameDesc.Height, 96, 96, PixelFormats.Bgra32, null, _latestColorFrameData, frameDesc.Width * frameDesc.BytesPerPixel); // 后续保存逻辑和之前一致 } } - 改用DispatcherTimer:如果是WPF场景,用
DispatcherTimer更省心,它本身就在UI线程运行,不需要手动切换线程:private DispatcherTimer _saveTimer; private void startSaveTimer() { _saveTimer = new DispatcherTimer(); _saveTimer.Interval = TimeSpan.FromMinutes(10); _saveTimer.Tick += SaveTimer_Tick; _saveTimer.Start(); } private void SaveTimer_Tick(object sender, EventArgs e) { // 这里直接在UI线程执行,不需要Dispatcher.Invoke // 截图保存逻辑写在这里即可 }
内容的提问来源于stack exchange,提问作者capture
相关产品推荐
相关产品推荐

