硕士研三找完工作后就开始投入紧张忙碌的论文生涯,现在我在忙大论文的工程。前期好多的实验和理论,头都大了,好在通过搞这些东西,收获了不少心得。
写一下在用C#读取摄像头并做处理的Demo心得吧。
如果我们需要读取摄像头,并且还要对其中的某帧图像进行处理,比如说取出其中的某个像素点坐标的对应的像素色彩值,RGB或者灰度值。有以下几种方法:
(1)我们第一个想到的就是使用大名鼎鼎的Opencv。确实Opencv非常好用,但是对于一些对于C++一知半解的人来说,要有界面程序的话还得学Qt,哇,Qt配置Opencv可麻烦了,以前做的时候用过,也配置过,可以说非常麻烦,网上众说纷纭,各种版本的配置,基本上没有一个版本能够不出问题的配置好,还是我参考了各个版本后,自己想出的配置方法,也写在我以前的博客里了,如果需要的话,可以上下捣一捣翻一翻,准能找到。我这一次做为啥不用以前的程序了呢?就是因为我刚把环境卸掉了,不想再装了,所以问题来了,想偷懒,就得有其他办法做。如下的方法(2)
(2)好多人说,opencv也可以配置在C#上啊,对,没错,大牛应该能用的了,搞得动。为什么这么说?我问了一下度娘,她说,你必须学习EmguCV才行啊,说这是microsoft吸取了opencv的精华,而后又加入了很多高级功能之后的版本。高大上啊,然后我又开始搜EmguCV,大概的资料在下面两个链接上:
资料很少,用的人也不多,所以,给那些刚涉入程序员行列不久的人一条靠谱的建议:我们在做一个工程的时候,网上一搜,会有很多种办法解决,但是,我们不要随便找一个就开始干,这是蛮干,开始的时候可能还没啥难度,网上的资料也够用,但是越往后做,难度越大,网上的资料可以用的资料就越少了,那就要看你的功底了;所以对于内功修为不够的人,千万不要随便学一门武功,容易走火入魔

换下一话题,

(3)起初为了完成某个短期的目标就慌不择路的选择了该方法,结果越陷越深啊,幸好,我还有点内功,能够控制住自己,没有走火入魔。很心酸。该方法就是本文主要讲的方法
AVICap;avicap32.dll;
还有一些百度文档和博客上的程序,基本上都是一个样的,他们的程序我就不贴了,网上可以搜到的,基本功能就是打开视频,保存bmp格式的图片,录制avi格式的视频并保存;
而我的功能需求是这样的,(1)可以打开视频;(2)能够在视频现实中抓取一帧图片并且把图片转换成bmp格式;(3)分析这帧图片,找出某像素点的RGB值;(4)最后用Graphics绘图把RGB值根据大小画到picbox上,二维图像这样的,横轴是像素数(图片中某一行的第多少个像素点),纵轴是R值;
现把各个功能模块的程序帖上来:
先附上一张图吧:
做完后基本是这个效果,左侧的图片是640*480的,右侧是点击图片中的某一列之后的480个像素点对应的R值,0-255;
程序如下:包括功能(1)和功能(2)的程序:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using System.Drawing;
namespace Test
{
class VideoWork
{
private const int WM_USER = 0x400;
private const int WS_CHILD = 0x40000000;
private const int WS_VISIBLE = 0x10000000;
private const int WM_CAP_START = WM_USER;
private const int WM_CAP_STOP = WM_CAP_START + 68;
private const int WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
private const int WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
private const int WM_CAP_SAVEDIB = WM_CAP_START + 25;//保存文件
private const int WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
private const int WM_CAP_SEQUENCE = WM_CAP_START + 62;
private const int WM_CAP_FILE_SET_CAPTURE_FILEA = WM_CAP_START + 20;//设置捕获文件
public const int WM_CAP_FILE_GET_CAPTURE_FILE = WM_CAP_START + 21;//获得捕获文件
private const int WM_CAP_SEQUENCE_NOFILE = WM_CAP_START + 63;
private const int WM_CAP_SET_OVERLAY = WM_CAP_START + 51;
private const int WM_CAP_SET_PREVIEW = WM_CAP_START + 50;//50
private const int WM_CAP_SET_CALLBACK_VIDEOSTREAM = WM_CAP_START + 6;
private const int WM_CAP_SET_CALLBACK_ERROR = WM_CAP_START + 2;
private const int WM_CAP_SET_CALLBACK_STATUSA = WM_CAP_START + 3;
private const int WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5;
private const int WM_CAP_SET_SCALE = WM_CAP_START + 53;
private const int WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52;
//private IntPtr hWndC;
public static IntPtr hWndC;
private bool bWorkStart = false;
private IntPtr mControlPtr;
private int mWidth;
private int mHeight;
private int mLeft;
private int mTop;
/// <summary>
/// 初始化显示图像
/// </summary>
/// <param name= “handle “> 控件的句柄 </param>
/// <param name= “left “> 开始显示的左边距 </param>
/// <param name= “top “> 开始显示的上边距 </param>
/// <param name= “width “> 要显示的宽度 </param>
/// <param name= “height “> 要显示的长度 </param>
public VideoWork(IntPtr handle, int left, int top, int width, int height)
{
mControlPtr = handle;
mWidth = width;
mHeight = height;
mLeft = left;
mTop = top;
}
[DllImport("avicap32.dll ")]
[DllImport("avicap32.dll ")]
private static extern int capGetVideoFormat(IntPtr hWnd, IntPtr psVideoFormat, int wSize);
//
//这里特别注意,因为WinAPI中的long为32位,而C#中的long为64wei,所以需要将lParam该为int
//
[DllImport("User32.dll ")]
private static extern bool SendMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);
/// <summary>
/// 开始显示图像
/// </summary>
public void Start()
{
if (bWorkStart)
return;
bWorkStart = true;
byte[] lpszName = new byte[100];
hWndC = capCreateCaptureWindowA(lpszName, WS_CHILD | WS_VISIBLE, mLeft, mTop, mWidth, mHeight, mControlPtr, 0);
if (hWndC.ToInt32() != 0)
{
SendMessage(hWndC, WM_CAP_SET_CALLBACK_VIDEOSTREAM, 0, 0);
SendMessage(hWndC, WM_CAP_SET_CALLBACK_ERROR, 0, 0);
SendMessage(hWndC, WM_CAP_SET_CALLBACK_STATUSA, 0, 0);
SendMessage(hWndC, WM_CAP_DRIVER_CONNECT, 0, 0);
SendMessage(hWndC, WM_CAP_SET_SCALE, 1, 0);//打开预览视频的缩放比例
SendMessage(hWndC, WM_CAP_SET_PREVIEWRATE, 66, 0);//66ms,视频刷新频率
SendMessage(hWndC, WM_CAP_SET_OVERLAY, 1, 0);//启用叠加 注:据说启用此项可以加快渲染速度
SendMessage(hWndC, WM_CAP_SET_PREVIEW, 1, 0);//设置显示图像启动预览模式 PREVIEW
}
return;
}
/// <summary>
/// 停止显示
/// </summary>
public void Stop()
{
SendMessage(hWndC, WM_CAP_DRIVER_DISCONNECT, 0, 0);
bWorkStart = false;
}
/// <summary>
/// 抓图
/// </summary>
/// <param name= “path “> 要保存bmp文件的路径 </param>
public void GrabImage()//string path
{
IntPtr hBmp = Marshal.StringToHGlobalAnsi(@"G:\c.bmp");//正确@"G:\c.bmp" 正确Application.StartupPath+".bmp"
SendMessage(hWndC, WM_CAP_SAVEDIB, 0, hBmp.ToInt32());
}
}
}
比较简单,因为网上都有,但关键点比较难,就是我没有办法从正在播放的视频中抓取一帧直接保存成bmp格式的图片直接分析,取而代之的方法是,先把其中抓获的一帧图片保存到本地磁盘,然后,另外一个picbox在一个定时器中不停的读取本地磁盘保存的bmp格式的图片,然后再绘图类中,我们在从该picbox中读取bitmap格式的图片,总之绕了一大圈,很费劲,不过总算功能实现了,但是,说实话,我不满意!以上功能再绘图类中,如下:
{
public static double startPixel = 0;
public static double endPixel = 480;
public static double startGray = 0;
public static double endGray = 255;
private Graphics glight;//灰度值
public void draw(PaintEventArgs e, PictureBox pictureBox3)
{
glight = e.Graphics;
Pen p = new Pen(Color.Black, 2);
//画标题
Font fii = new Font("Tahoma",15, FontStyle.Regular);
//以下两个for循环是画网格
for (int i = 0; i < 6; i++)
{
glight.DrawLine(p, 70, i * 102, 550, i * 102);//横轴
}
for (int i = 0; i < 6; i++)
{
glight.DrawLine(p, 70 + i * 96, 0, 70 + i * 96, 512);
}
//画纵坐标
for (int i = 0; i < 6; i++)
{
glight.DrawString((startGray + i * ((endGray - startGray) / 5)).ToString(), fii, new SolidBrush(Color.Red), 20, 505 - i * 102);
}
//画横坐标
for (int i = 0; i < 6; i++)
{
glight.DrawString((startPixel + i * ((endPixel - startPixel) / 5)).ToString(), fii, new SolidBrush(Color.Red), 50+ i * 96, 520);
}
//画波形图
double perPixel = 480 / (endPixel - startPixel);//每个点占多少像素
double perGray = 512 / (endGray - startGray);//每个灰度值占多少像素
//画点
Pen point = new Pen(Color.Red, 3);
Bitmap bmp;
bmp = (Bitmap)pictureBox3.Image;
if (bmp == null)
bmp = new Bitmap(@"G:\c.bmp");
int begin=0;
for (double i = 0; i < endPixel - startPixel; i=i+1)
{
//bmp.GetPixel(Form1.clickpixel, (int)i);
begin=Convert.ToInt32(bmp.GetPixel(Form1.clickpixel,(int)(startPixel + i)).R);
if (begin>= startGray &&begin<= endGray)
glight.DrawEllipse(point, 70 + (int)(perPixel * (i + 1)), 512 - (int)((begin - startGray) * perGray), 1, 1);//画椭圆 420 - x * 100, 610 - y * 100, 1, 1
}
}
}
{
wv.GrabImage();
this.pictureBox3.ImageLocation = @"G:\c.bmp";
}
我 2015/12/8 20:27:13
private void timer2_Tick(object sender, EventArgs e)
{
this.pictureBox2.Invalidate();//代表每隔多长时间就重绘一次
}
至此,该工程基本完成了,但是效率很低,因为抓帧后保存本地---picbox从本地读取----bitmap从picbox读取,所以绕了很多弯子,动态显示的时候,鼠标都在抖动,说明很差啊,不过也算是用该方法完成了这个功能了吧,因为我实在不知道该如何从视频中直接抓取一帧bitmap的图像直接处理。有知道的大神,可以写评论啊,谢谢!