接到新任务,用来监控3D打印机的摄像头,需要对其进行控制。之前我没有做过任何这方面的工作,很是挠头。不过有脚本和开源两个强大的工具,我也不是很害怕。
首先有人指明需要用到的库是OpenCV。CV是指Computer vision, 从名字就能判断这是专注于视频处理的库。很快我就找到了python的调用demo.并且成功的带动了手边的摄像头。http://opencv.org/安装后,就能在sources/samples/python/demo文件夹下面找到完整的demo程序。
不过这不能解决项目问题。我们的项目要求在.net环境下开发。所以很快,我就找到了成熟的.net平台下OpenCV解决方案,名叫EMGU;
同样的,EMGU也完整的demo例程。在EMGU3.1版本的安装路径下面\Solution\VS2013-2015文件夹中,就有摄像头的驱动例子:实现摄像头实时采样,只需要几行代码:
... private static Capture _cameraCapture; ... void run(){ try { _cameraCapture = new Capture(); } catch (Exception e) { MessageBox.Show(e.Message); return; } Application.Idle += ProcessFrame; ... void ProcessFrame(object sender, EventArgs e) { Mat frame = _cameraCapture.QueryFrame(); }
通过Applicaiton.Idle+= ProcessFrame
的加载事件,非常优雅的实现了摄像头的驱动。
进一步,EMGU中有Websevice的例子。但是例子中传输的是一串字符串,采用的是net.tcp协议。我尝试传输image,结果在旁边电脑成功访问了我的摄像头,但是卡顿非常严重。(暂时分析不出来卡顿的原因)
host端代码:
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.ServiceModel; using System.ServiceModel.Description; using System.Text; using Emgu.CV; namespace Webservice_Host { class Program { static void Main(string[] args) { Uri uri = new Uri("net.tcp://localhost:8082/ImageService"); NetTcpBinding binding = new NetTcpBinding(); binding.Security.Mode = SecurityMode.None; //WSHttpBinding binding = new WSHttpBinding(); binding.MaxReceivedMessageSize = 2147483647; ServiceHost host = new ServiceHost(typeof(ImageService)); ServiceBehaviorAttribute serviceBehavior = host.Description.Behaviors.Find<ServiceBehaviorAttribute>(); serviceBehavior.IncludeExceptionDetailInFaults = true; // Create endpoint host.AddServiceEndpoint(typeof(IImageService), binding, uri); host.Open(); Console.WriteLine("Service is ready, press any key to terminate."); Console.ReadKey(); host.Close(); } } }
ImageService.cs using System; using System.Collections.Generic; using System.Text; using System.ServiceModel; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; namespace Webservice_Host { public class ImageService : IImageService { public Image<Bgr, Byte> GrabFrame() { Capture _capture = new Capture(); Mat frame=new Mat(); //_capture.Retrieve(frame,0); //Image<Bgr, Byte> img = frame.ToImage<Bgr, Byte>(); Image<Bgr, Byte> img = _capture.QueryFrame().ToImage<Bgr, Byte>(); if (_capture != null) _capture.Dispose(); return img; } } }
IImageService.cs using System; using System.Collections.Generic; using System.Text; using Emgu.CV; using Emgu.CV.Structure; using System.ServiceModel; namespace Webservice_Host { [ServiceContract] [XmlSerializerFormat] public interface IImageService { [OperationContract(IsOneWay = false)] Image<Bgr, Byte> GrabFrame(); } }
client端代码:
//---------------------------------------------------------------------------- // Copyright (C) 2004-2016 by EMGU Corporation. All rights reserved. //---------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.ServiceModel; using System.Threading; using Emgu.CV; namespace Webservice_Client { public partial class Form1 : Form { private bool _started; private Webservice_Host.IImageService _imageGrabber; private NetTcpBinding _binding; private static Queue<IImage> imageQ; public Form1() { InitializeComponent(); _binding = new NetTcpBinding(); _binding.Security.Mode = SecurityMode.None; _binding.MaxReceivedMessageSize = 2147483647; Queue<IImage> imageQ = new Queue<IImage>(); //System.Collections.Queue imageQ= new System.Collections.Queue(); _started = false; } public bool Started { get { return _started; } set { _started = value; serviceUrlBox.Enabled = !_started; if (!_started) { //stop grabing frames //Application.Idle -= ProcessImage; // System.Timers.Timer t =new System.Timers.Timer(1); // t.Start(); // t.Elapsed+= ProcessImage; } else { //start to grab frames _imageGrabber = new ChannelFactory<Webservice_Host.IImageService>( _binding, new EndpointAddress(serviceUrlBox.Text) ).CreateChannel(); //System.Timers.Timer t =new System.Timers.Timer(50); //t.Start(); ////button2.Click += ProcessImage; //t.Elapsed+= ProcessImage; //// Application.Idle Thread rx_image_thread = new Thread(new ThreadStart(get_image)); Thread show_image_thread = new Thread(new ThreadStart(show_image)); rx_image_thread.Start(); show_image_thread.Start(); } } } private void get_image() { Queue<IImage> imageQ = new Queue<IImage>(); while (true) { imageQ.Enqueue(_imageGrabber.GrabFrame()); } } private void show_image() { while (true) { // if (imageQ.Count>0&&imageQ!=null) // imageBox1.Image=imageQ.Dequeue(); } } private void ProcessImage(object sender, EventArgs e) { imageBox1.Image = _imageGrabber.GrabFrame(); } private void button1_Click(object sender, EventArgs e) { Started = !Started; if (Started == true) button1.Text = "Stop"; else button1.Text = "Start"; } private void ReleaseResource() { Started = false; } private void button2_Click(object sender, EventArgs e) { } } }
IImageService
using System; using System.Collections.Generic; using System.Text; using Emgu.CV; using Emgu.CV.Structure; using System.ServiceModel; namespace Webservice_Host { [ServiceContract] [XmlSerializerFormat] public interface IImageService { [OperationContract(IsOneWay = false)] Image<Bgr, Byte> GrabFrame(); } }
到此为止,我对EMGU带摄像头的尝试就结束了。后面我采用EMGU做了两件事情,都是和网络摄像头有关:一是用ASP.NET用网页控制远程web摄像头拍照并传过来实时图像。二是将EMGU截图封装成为dll的方法,交给其他工程师使用。
总体而言,也可能是受制于我的编程功力,无法用EMGU来实现更复杂的摄像头访问应用。但是EMGU作为OpenCV在.net平台的封装,其项目重心用在了图像识别算法的封装而不是多媒体应用上:我们可以看到大量的例程进行图像识别等算法,调用起来非常方便。
而对于将摄像头转成流媒体源的工作,我找到了更加有效的框架,即微软自带的Media Foudation框架。