前几天别的学院有人找我做一个人机交互的程序,具体的需求是这样的:通过网络摄像头来识别图形,图形中包含某些信息,然后读取图形中的信息来完成相应的音乐输出。其实也就是更具图形播放相应的音乐。主要只给了5天的时间。
听了他们的需求其实心里还是没有底的,毕竟从来没有做过图像处理的程序,最多也就是图形学课做了点基础的图像绘制,对于这些知识还是远不能处理他们的需求的。后来想到可以根据识别二维码来获取图像信息,这样更加灵活,也更加增加互动效果。
一开始打算用Java做的,这需要用到JMF这个JAR包。但是无论试了很多次都没有成功打开摄像头,一直没有办法注册我的网络摄像头,百度了下有人说win7下不兼容的,有说将摄像头的驱动降版本的,总之试了一圈都没效果,好了不折腾了,Java有时候真的很让人恼火,只能说甲骨文公司不知道想干嘛,JMF的win7版也迟迟不发布。最后无奈选择C#语言,理由就是做界面的时候可以省心一点。
由于时间紧迫我也来不及去研究二维码(QR)的实现算法,这是我比较耿耿于怀的,毕竟程序的核心不是自己的。不过在这次开发过程中也解决了相应没有遇到过的问题,这个还是很高兴的。二维码的识别软件有很多,但是很多都是基于移动设备的,对于个人电脑版本的不是很多,结果挑选我选择了ZBar这个软件,对于效果还是要赞一个的,唯一不足的就是对于中文的不支持。ZBar会将扫描结果通过一个DOS窗口输出出来,其初衷就是能够更好的基于二次开发,这也是我选择这个的关键所在。我打算自己写一个winform程序来调用其ZBar程序,通过管道的重定向获取ZBar在DOS下识别的二维码输出。程序如下:
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "ZBar.exe";
p.Start();
// Do not wait for the child process to exit before
// reading to the end of its redirected stream.
// p.WaitForExit();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
执行了下发现有几个明显的问题,第一在执行的时候整个UI呈现假死的状态,也就是程序未响应,如果把ZBar软件关闭,winform程序便能接收到ZBar程序通过管道传来的数据。问题也就出来了,子程序并没有实时得像调用程序传回数据,也就是说ZBar并没有调用及时的刷新缓冲区(fflush),导致了主程序的假死。
这点在帮助文档中也有提到。
同步读取操作在读取 StandardOutput 流的调用方及写入该流中的子进程之间引入一个依赖项。这些依赖项可能导致产生死锁情况。调用方读取子进程的重定向流时依赖于该子进程。调用方将等待读取操作,直到子进程写入流或关闭流为止。子进程写入足够多的数据以填充重定向流的时间依赖于父进程。子进程将等待下一次写操作,直到父进程读取了全部流或关闭该流为止。当调用方和子进程相互等待对方完成操作时,就会产生死锁情况,使双方都无法继续执行操作。您可以通过计算调用方和子进程之间的依赖项从而避免出现死锁情况。
可见在主程序和子程序中存在了死锁现象,如果拥有子程序的源码只要在每次输出的时候强制刷新缓冲流就可避免,但是很遗憾不适用于本例,但是有没有办法能解决呢?还是可以的,在阅读帮助文档的时候看到了一个提供异步读取操作。MSDN如下写到:
BeginOutputReadLine 在 StandardOutput 流上开始异步读取操作。此方法为流输出启用一个指定的事件处理程序并立即返回到调用方,这样当流输出被定向到该事件处理程序时,调用方可以执行其他操作。
接下来就看看如何修改代码:(主程序)
Process proc = new Process();
string str = Directory.GetCurrentDirectory();
proc.StartInfo.FileName = str+"\\ZBar\\bin\\zbarcam.exe";
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardInput = true;
proc.OutputDataReceived += new DataReceivedEventHandler(pro_OutputDataReceived);//
proc.Start();
proc.BeginOutputReadLine();
proc.Close();
void pro_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
//可通过调用 CancelOutputRead 取消异步读取操作。可通过调用方或事件处理程序取消读取操作。取消之后,可以再次调用 BeginOutputReadLine 继续进行异步读取操作。
if (!string.IsNullOrEmpty(e.Data))
{ line = e.Data;
//对异步的数据流进行处理
//例如输出line的信息
}
}
步骤如下:
按照这些步骤对 Process 的 StandardOutput 执行异步读取操作:
将 UseShellExecute 设置为 false。
将 RedirectStandardOutput 设置为 true。
向 OutputDataReceived 事件添加事件处理程序。事件处理程序必须与 System.Diagnostics..::.DataReceivedEventHandler 委托签名相匹配。
启动 Process。
调用 Process 的 BeginOutputReadLine。此调用将启动 StandardOutput 上的异步读取操作。
启动异步读取操作时,关联的 Process 每向其 StandardOutput 流写入一行文本时,都将调用该事件处理程序。
可通过调用 CancelOutputRead 取消异步读取操作。可通过调用方或事件处理程序取消读取操作。取消之后,可以再次调用 BeginOutputReadLine 继续进行异步读取操作。
这样就完成了实时的通过管道重定向获取子程序的输出数据,用于主程序处理。