还有一些百度文档和博客上的程序,基本上都是一个样的,他们的程序我就不贴了,网上可以搜到的,基本功能就是打开视频,保存bmp格式的图片,录制avi格式的视频并保存;
而我的功能需求是这样的,(1)可以打开视频;(2)能够在视频现实中抓取一帧图片并且把图片转换成bmp格式;(3)分析这帧图片,找出某像素点的RGB值;(4)最后用Graphics绘图把RGB值根据大小画到picbox上,二维图像这样的,横轴是像素数(图片中某一行的第多少个像素点),纵轴是R值;
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的图像直接处理。有知道的大神,可以写评论啊,谢谢!