C#读取摄像头并对图像做简单处理--AVICap32篇

硕士研三找完工作后就开始投入紧张忙碌的论文生涯,现在我在忙大论文的工程。前期好多的实验和理论,头都大了,好在通过搞这些东西,收获了不少心得。

写一下在用C#读取摄像头并做处理的Demo心得吧。

如果我们需要读取摄像头,并且还要对其中的某帧图像进行处理,比如说取出其中的某个像素点坐标的对应的像素色彩值,RGB或者灰度值。有以下几种方法:

(1)我们第一个想到的就是使用大名鼎鼎的Opencv。确实Opencv非常好用,但是对于一些对于C++一知半解的人来说,要有界面程序的话还得学Qt,哇,Qt配置Opencv可麻烦了,以前做的时候用过,也配置过,可以说非常麻烦,网上众说纷纭,各种版本的配置,基本上没有一个版本能够不出问题的配置好,还是我参考了各个版本后,自己想出的配置方法,也写在我以前的博客里了,如果需要的话,可以上下捣一捣翻一翻,准能找到。我这一次做为啥不用以前的程序了呢?就是因为我刚把环境卸掉了,不想再装了,所以问题来了,想偷懒,就得有其他办法做。如下的方法(2)

(2)好多人说,opencv也可以配置在C#上啊,对,没错,大牛应该能用的了,搞得动。为什么这么说?我问了一下度娘,她说,你必须学习EmguCV才行啊,说这是microsoft吸取了opencv的精华,而后又加入了很多高级功能之后的版本。高大上啊,然后我又开始搜EmguCV,大概的资料在下面两个链接上:

http://www.opencv.org.cn/forum.php?mod=viewthread&tid=32710
http://wenku.baidu.com/link?url=DIvhesSEe7CjxWPtX2YH-HOVciLereVfNVfd-OSZRlcpCGENlYyRO7wdSlbcqkueLRGlqT-UkswmKOKeMbAN6SrJJJb1sE2ic4ukwrcWvoK

资料很少,用的人也不多,所以,给那些刚涉入程序员行列不久的人一条靠谱的建议:我们在做一个工程的时候,网上一搜,会有很多种办法解决,但是,我们不要随便找一个就开始干,这是蛮干,开始的时候可能还没啥难度,网上的资料也够用,但是越往后做,难度越大,网上的资料可以用的资料就越少了,那就要看你的功底了;所以对于内功修为不够的人,千万不要随便学一门武功,容易走火入魔 !这样做是非常浪费时间的,如果你在公司这么干的话,是会有致命后果的。所以公司的人在做一个工程前,首先都会论证网上的各种技术,做法,选一个比较成熟的技术来做,这样的话,节省时间,而且也不容易出现大的错误。我算是记住了!所以这个方案对于我的工程来说也就pass掉了。
换下一话题, ,方法(3)
(3)起初为了完成某个短期的目标就慌不择路的选择了该方法,结果越陷越深啊,幸好,我还有点内功,能够控制住自己,没有走火入魔。很心酸。该方法就是本文主要讲的方法
AVICap;avicap32.dll;
百度一搜avicap的话会有不少的资料,不过有很多资料都是重复的,你copy我的,我copy你的,真正的有几篇写的还可以的,链接如下:

http://blog.chinaunix.net/uid-20594503-id-1619859.html
http://blog.csdn.net/laolei1986/article/details/5730245
还有一些百度文档和博客上的程序,基本上都是一个样的,他们的程序我就不贴了,网上可以搜到的,基本功能就是打开视频,保存bmp格式的图片,录制avi格式的视频并保存;
而我的功能需求是这样的,(1)可以打开视频;(2)能够在视频现实中抓取一帧图片并且把图片转换成bmp格式;(3)分析这帧图片,找出某像素点的RGB值;(4)最后用Graphics绘图把RGB值根据大小画到picbox上,二维图像这样的,横轴是像素数(图片中某一行的第多少个像素点),纵轴是R值;
现把各个功能模块的程序帖上来:
先附上一张图吧:
C#读取摄像头并对图像做简单处理--AVICap32篇_第1张图片
做完后基本是这个效果,左侧的图片是640*480的,右侧是点击图片中的某一列之后的480个像素点对应的R值,0-255;
程序如下:包括功能(1)和功能(2)的程序:
using System;
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;
                   /// 
                /// 初始化显示图像
                /// 

                ///  控件的句柄 
                ///  开始显示的左边距 
                ///  开始显示的上边距 
                ///  要显示的宽度 
                ///  要显示的长度 
                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 ")]
private static extern IntPtr capCreateCaptureWindowA(byte[] lpszWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, int nID);

                [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);

                /// 
                /// 开始显示图像
                /// 

                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;

                }

                /// 
                /// 停止显示
                /// 

                public void Stop()
                {
                    SendMessage(hWndC, WM_CAP_DRIVER_DISCONNECT, 0, 0);
                    bWorkStart = false;
                }

                /// 
                /// 抓图
                /// 

                ///  要保存bmp文件的路径 
                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格式的图片,总之绕了一大圈,很费劲,不过总算功能实现了,但是,说实话,我不满意!以上功能再绘图类中,如下:
class Draw
    {
        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
            }
        }
    }
貌似都完成了,但是还有一个令我百思不得其解的问题,以前graphics绘图,刷新的时候就直接在定时器中用invalidate就可以搞定了,一般在定时器中首先调用抓图函数并保存到本地,然后另外一个picturebox在读取本地的图片就OK了,但是该实验这么做就就不行,在获取了图片之后,就直接刷新是不行的,在绘图中会报错,解决方法是在用一个定时器,在另外一个定时器中定时刷新就可以了,如下程序:
    private void timer1_Tick(object sender, EventArgs e)
                {
                    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的图像直接处理。有知道的大神,可以写评论啊,谢谢!

你可能感兴趣的:(C#,avicap32,rgb,avicap,graphics,摄像头,灰度值)