WPF之海康面阵相机SDK的使用-实现开始采集
<Button x:Name="btn_StartGrab" Content="开始采集" Margin="10" Click="btn_StartGrab_Click"></Button>
1.是否是彩色相机
// 判断是否为彩色图像
private Boolean IsColorData(MyCamera.MvGvspPixelType enGvspPixelType)
{
switch (enGvspPixelType)
{
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_YUV422_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_YUV422_YUYV_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_YCBCR411_8_CBYYCRYY:
return true;
default:
return false;
}
}
2.转彩色图像
/// <summary>
/// 转换为RGB格式
/// </summary>
/// <param name="obj"></param>
/// <param name="pSrc"></param>
/// <param name="nHeight"></param>
/// <param name="nWidth"></param>
/// <param name="nPixelType"></param>
/// <param name="pDst"></param>
/// <returns></returns>
private Int32 ConvertToRGB(object obj, IntPtr pSrc, ushort nHeight, ushort nWidth, MyCamera.MvGvspPixelType nPixelType, IntPtr pDst)
{
if (IntPtr.Zero == pSrc || IntPtr.Zero == pDst)
{
return MyCamera.MV_E_PARAMETER;
}
int nRet = MyCamera.MV_OK;
MyCamera device = obj as MyCamera;
MyCamera.MV_PIXEL_CONVERT_PARAM stPixelConvertParam = new MyCamera.MV_PIXEL_CONVERT_PARAM();
stPixelConvertParam.pSrcData = pSrc;//源数据
if (IntPtr.Zero == stPixelConvertParam.pSrcData)
{
return -1;
}
stPixelConvertParam.nWidth = nWidth;//图像宽度
stPixelConvertParam.nHeight = nHeight;//图像高度
stPixelConvertParam.enSrcPixelType = nPixelType;//源数据的格式
stPixelConvertParam.nSrcDataLen = (uint)(nWidth * nHeight * ((((uint)nPixelType) >> 16) & 0x00ff) >> 3);
stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * ((((uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed) >> 16) & 0x00ff) >> 3);
stPixelConvertParam.pDstBuffer = pDst;//转换后的数据
stPixelConvertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed;
stPixelConvertParam.nDstBufferSize = (uint)nWidth * nHeight * 3;
nRet = device.MV_CC_ConvertPixelType_NET(ref stPixelConvertParam);//格式转换
if (MyCamera.MV_OK != nRet)
{
return -1;
}
return MyCamera.MV_OK;
}
3.是否是黑白相机
// 判断是否为黑白图像
private Boolean IsMonoData(MyCamera.MvGvspPixelType enGvspPixelType)
{
switch (enGvspPixelType)
{
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12_Packed:
return true;
default:
return false;
}
}
4.转灰度图像
/// <summary>
/// 转换为Mono8
/// </summary>
/// <param name="obj"></param>
/// <param name="pInData"></param>
/// <param name="pOutData"></param>
/// <param name="nHeight"></param>
/// <param name="nWidth"></param>
/// <param name="nPixelType"></param>
/// <returns></returns>
private Int32 ConvertToMono8(object obj, IntPtr pInData, IntPtr pOutData, ushort nHeight, ushort nWidth, MyCamera.MvGvspPixelType nPixelType)
{
if (IntPtr.Zero == pInData || IntPtr.Zero == pOutData)
{
return MyCamera.MV_E_PARAMETER;
}
int nRet = MyCamera.MV_OK;
MyCamera device = obj as MyCamera;
MyCamera.MV_PIXEL_CONVERT_PARAM stPixelConvertParam = new MyCamera.MV_PIXEL_CONVERT_PARAM();
stPixelConvertParam.pSrcData = pInData;//源数据
if (IntPtr.Zero == stPixelConvertParam.pSrcData)
{
return -1;
}
stPixelConvertParam.nWidth = nWidth;//图像宽度
stPixelConvertParam.nHeight = nHeight;//图像高度
stPixelConvertParam.enSrcPixelType = nPixelType;//源数据的格式
stPixelConvertParam.nSrcDataLen = (uint)(nWidth * nHeight * ((((uint)nPixelType) >> 16) & 0x00ff) >> 3);
stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * ((((uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed) >> 16) & 0x00ff) >> 3);
stPixelConvertParam.pDstBuffer = pOutData;//转换后的数据
stPixelConvertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8;
stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * 3);
nRet = device.MV_CC_ConvertPixelType_NET(ref stPixelConvertParam);//格式转换
if (MyCamera.MV_OK != nRet)
{
return -1;
}
return nRet;
}
5.取像线程(核心)
/// <summary>
/// 取像线程(核心)
/// </summary>
private void ReceiveThreadProcess()
{
// 取图线程流程
// 1.获取单帧图像数据的有效负载大小(单位为字节),即从相机传输到客户端的每一帧图像数据(不包含协议头、尾等额外开销)的实际大小
MyCamera.MVCC_INTVALUE stParam = new MyCamera.MVCC_INTVALUE();
int nRet = m_MyCamera.MV_CC_GetIntValue_NET("PayloadSize", ref stParam);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"读取PayloadSize失败,失败代码:{nRet}");
return;
}
UInt32 nPayloadSize = stParam.nCurValue;
// 2.获取图像高
nRet = m_MyCamera.MV_CC_GetIntValue_NET("Height", ref stParam);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"获取图像高失败,失败代码:{nRet}");
return;
}
uint nHeight = stParam.nCurValue;
// 3.获取图像宽
nRet = m_MyCamera.MV_CC_GetIntValue_NET("Width", ref stParam);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"获取图像宽失败,失败代码:{nRet}");
return;
}
uint nWidth = stParam.nCurValue;
// 4.根据图像大小设置图像缓存
m_pDataForRed = new byte[nWidth * nHeight];
m_pDataForGreen = new byte[nWidth * nHeight];
m_pDataForBlue = new byte[nWidth * nHeight];
if (3 * nPayloadSize > m_nBufSizeForDriver)
{
if (m_BufForDriver != IntPtr.Zero)
{
Marshal.Release(m_BufForDriver);
}
m_nBufSizeForDriver = 3 * nPayloadSize;
m_BufForDriver = Marshal.AllocHGlobal((Int32)m_nBufSizeForDriver);
}
if (m_BufForDriver == IntPtr.Zero)
{
return;
}
IntPtr pImageBuffer = Marshal.AllocHGlobal((int)nPayloadSize * 3);
if (pImageBuffer == IntPtr.Zero)
{
MessageBox.Show($"申请图像缓存区失败!");
return;
}
MyCamera.MV_FRAME_OUT_INFO_EX stFrameInfo = new MyCamera.MV_FRAME_OUT_INFO_EX();
IntPtr RedPtr = IntPtr.Zero;
IntPtr GreenPtr = IntPtr.Zero;
IntPtr BluePtr = IntPtr.Zero;
IntPtr pTemp = IntPtr.Zero;
DateTime ProStartTime = DateTime.MinValue;
// 5.循环监听,触发相机的图像采集信号
while (isGrabbing)
{
// 6.获取一帧图像数据(核心)等待,软触发或者硬触发的信号
// 锁,实际相机只有一个,但是这个取图的线程,我们可以启动多个,不能一个线程在读取数据时候,另一个线程也对这个数据进行操作
lock (BufForDriverLock)
{
// 6.获取一帧图像数据(核心)等待,软触发或者硬触发的信号
nRet = m_MyCamera.MV_CC_GetOneFrameTimeout_NET(m_BufForDriver, m_nBufSizeForDriver, ref stFrameInfo, 1000);
// 如果采集成功,则进行记录提示
if (nRet == MyCamera.MV_OK)
{
ProStartTime = DateTime.Now;
// MessageBox.Show("相机取图完成,开始处理...");
m_stFrameInfo = stFrameInfo;
}
// 判别是否采集图像成功
if (nRet == MyCamera.MV_OK)
{
// 如果是彩色相机
if (IsColorData(stFrameInfo.enPixelType))
{
// 7.如果是彩色图像格式,则直接使用数据,给到pTemp
if (stFrameInfo.enPixelType == MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed)
{
pTemp = m_BufForDriver;
}
// 否则转换成RGB三通道数据,再给到pTemp
else
{
nRet = ConvertToRGB(m_MyCamera, m_BufForDriver, stFrameInfo.nHeight, stFrameInfo.nWidth, stFrameInfo.enPixelType, pImageBuffer);
if (MyCamera.MV_OK != nRet)
{
return;
}
pTemp = pImageBuffer;
}
// 8.获取rgb三个通道的数据
unsafe
{
byte* pBufForSaveImage = (byte*)pTemp;
UInt32 nSupWidth = (stFrameInfo.nWidth + (UInt32)3) & 0xfffffffc;//5120
for (int nRow = 0; nRow < stFrameInfo.nHeight; nRow++)
{
for (int col = 0; col < stFrameInfo.nWidth; col++)
{
m_pDataForRed[nRow * nSupWidth + col] = pBufForSaveImage[nRow * stFrameInfo.nWidth * 3 + (3 * col)];
m_pDataForGreen[nRow * nSupWidth + col] = pBufForSaveImage[nRow * stFrameInfo.nWidth * 3 + (3 * col + 1)];
m_pDataForBlue[nRow * nSupWidth + col] = pBufForSaveImage[nRow * stFrameInfo.nWidth * 3 + (3 * col + 2)];
}
}
}
RedPtr = Marshal.UnsafeAddrOfPinnedArrayElement(m_pDataForRed, 0);
GreenPtr = Marshal.UnsafeAddrOfPinnedArrayElement(m_pDataForGreen, 0);
BluePtr = Marshal.UnsafeAddrOfPinnedArrayElement(m_pDataForBlue, 0);
// 9.rgb三通道的指针数据转bitmap
//显示采集图像
lock (BufForImageLock)
{
// 进行图像显示
Dispatcher.Invoke(
() => {
int width = stFrameInfo.nWidth;
int height = stFrameInfo.nHeight;
m_writeableBitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Rgb24, null);
m_writeableBitmap.Lock();
unsafe
{
byte[] temp = new byte[width * height * 3];
byte[] tempR = new byte[width * height];
byte[] tempG = new byte[width * height];
byte[] tempB = new byte[width * height];
Marshal.Copy(RedPtr, tempR, 0, width * height);
Marshal.Copy(GreenPtr, tempG, 0, width * height);
Marshal.Copy(BluePtr, tempB, 0, width * height);
for (int i = 0; i < width * height; i++)
{
temp[i * 3] = tempR[i];
temp[i * 3 + 1] = tempG[i];
temp[i * 3 + 2] = tempB[i];
}
Marshal.Copy(temp, 0, m_writeableBitmap.BackBuffer, width * height * 3);
}
m_writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
m_writeableBitmap.Unlock();
myImageCtr.Source = m_writeableBitmap;
}
);
}
}
// 11.如果是黑白相机图像
else if (IsMonoData(stFrameInfo.enPixelType))
{
if (stFrameInfo.enPixelType == MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8)
{
pTemp = m_BufForDriver;
}
else
{
nRet = ConvertToMono8(m_MyCamera, m_BufForDriver, pImageBuffer, stFrameInfo.nHeight, stFrameInfo.nWidth, stFrameInfo.enPixelType);
if (MyCamera.MV_OK != nRet)
{
return;
}
pTemp = pImageBuffer;
}
// 显示采集图像
lock (BufForImageLock)
{
// 13.进行图像显示
Dispatcher.Invoke(
() => {
int width = stFrameInfo.nWidth;
int height = stFrameInfo.nHeight;
m_writeableBitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Gray8, null);
// 12.灰度指针数据转writeablebitmap
m_writeableBitmap.Lock();
unsafe
{
byte[] tempR = new byte[width * height];
Marshal.Copy(pTemp, tempR, 0, width * height);
Marshal.Copy(tempR, 0, m_writeableBitmap.BackBuffer, width * height);
}
m_writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
m_writeableBitmap.Unlock();
myImageCtr.Source = m_writeableBitmap;
}
);
}
}
else
{
continue;
}
}
else
{
// 如果是触发模式,取图失败,则睡眠5ms
if (isTriggerMode)
{
Thread.Sleep(5);
}
}
}
}
}
6.取图
/// <summary>
/// 开始采集
/// </summary>
/// <returns></returns>
public bool StartGrab()
{
// 开始采集流程
// 1.采集标志位置位,设为true,表示开始采集
isGrabbing = true;
// 2.启动取像线程(核心)
m_hReceiveThread = new Thread(ReceiveThreadProcess);
m_hReceiveThread.Start();
// 3.取流之前先清除帧长度
m_stFrameInfo.nFrameLen = 0;
m_stFrameInfo.enPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG8;
// 4.开始采集
int nRet = m_MyCamera.MV_CC_StartGrabbing_NET();
// 5.如果采集失败,进行提示
if (MyCamera.MV_OK != nRet)
{
isGrabbing = false;
m_hReceiveThread.Join();
MessageBox.Show($"连续采集失败,失败代码:{nRet}");
return false;
}
return true;
}
7.开始取图
/// <summary>
/// 开始采集
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_StartGrab_Click(object sender, RoutedEventArgs e)
{ // 开始采集
if (StartGrab())
{
Console.WriteLine("成功开始采集");
}
}
完整代码
UI
<Window x:Class="WPF之海康面阵相机.MainWindow"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPF之海康面阵相机"
mc:Ignorable="d"
Closing="Window_Closing"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="600" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="6*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Label Content="相机序列号:" Margin="10"></Label>
<TextBox x:Name="tbx_CameraSerialNum" Text="02C93521633" Margin="10"></TextBox>
<GroupBox Header="采集模式">
<StackPanel>
<RadioButton Content="连续模式" Margin="10"></RadioButton>
<RadioButton Content="软触发" Margin="10"></RadioButton>
<RadioButton Content="硬触发" Margin="10"></RadioButton>
</StackPanel>
</GroupBox>
<GroupBox Header="参数显示">
<StackPanel>
<Label Content="曝光:"></Label>
<TextBox x:Name="tbx_Exposure" Text="0"></TextBox>
<Label Content="增益:"></Label>
<TextBox x:Name="tbx_Gain" Text="0"></TextBox>
<Label Content="帧率:" Cursor=""></Label>
<TextBox x:Name="tbx_FrameRate" Text="0"></TextBox>
</StackPanel>
</GroupBox>
</StackPanel>
<StackPanel Grid.Column="1" Cursor="">
<Image x:Name="myImageCtr" Height="400" Stretch="UniformToFill" ></Image>
</StackPanel>
<StackPanel Grid.Column="2">
<Button x:Name="btn_Open" Click="btn_Open_Click" Content="打开设备" Margin="10"></Button>
<Button x:Name="btn_Close" Content="关闭设备" Margin="10" Click="btn_Close_Click"></Button>
<Button x:Name="btn_StartGrab" Content="开始采集" Margin="10" Click="btn_StartGrab_Click"></Button>
<Button Content="停止采集" Margin="10"></Button>
<Button Content="软触发一次" Margin="10"></Button>
<Button Content="获取参数" Margin="10"></Button>
<Button Content="设置参数" Margin="10"></Button>
</StackPanel>
</Grid>
</Window>
后端
using MvCamCtrl.NET;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WPF之海康面阵相机
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// 是否为触发模式
/// </summary>
public bool isTriggerMode { get; private set; }
/// <summary>
/// 相机图像(bipmap格式)
/// </summary>
WriteableBitmap m_writeableBitmap;
// 读写图像时锁定
private Object BufForDriverLock = new Object();
private Object BufForImageLock = new Object();
UInt32 m_nBufSizeForDriver = 0;
// R通道数据
byte[] m_pDataForRed = null;
// G通道数据
byte[] m_pDataForGreen = null;
// B通道数据
byte[] m_pDataForBlue = null;
// 用于从驱动获取图像的缓存
IntPtr m_BufForDriver;
// 取像线程
Thread m_hReceiveThread = null;
// 设备列表
private MyCamera.MV_CC_DEVICE_INFO_LIST m_stDeviceList;
// 相机对象
private MyCamera m_MyCamera = null;
// 帧信息
MyCamera.MV_FRAME_OUT_INFO_EX m_stFrameInfo = new MyCamera.MV_FRAME_OUT_INFO_EX();
/// <summary>
/// 相机是否已连接
/// </summary>
public bool IsConnect { get; private set; }
/// <summary>
/// 是否开始采集
/// </summary>
public bool isGrabbing { get; private set; }
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// 打开相机
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Open_Click(object sender, RoutedEventArgs e)
{
if (OpenDevice())
{
MessageBox.Show("打开相机成功");
}
}
/// <summary>
/// 获取相机对应的枚举索引
/// </summary>
/// <param name="CameraID"></param>
/// <returns></returns>
private int GetDeviceIndex(string CameraID)
{
for (int i = 0; i < m_stDeviceList.nDeviceNum; i++)
{
MyCamera.MV_CC_DEVICE_INFO device = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
{
MyCamera.MV_GIGE_DEVICE_INFO gigeInfo = (MyCamera.MV_GIGE_DEVICE_INFO)MyCamera.ByteToStruct(device.SpecialInfo.stGigEInfo, typeof(MyCamera.MV_GIGE_DEVICE_INFO));
if (gigeInfo.chSerialNumber == CameraID)
return i;
}
else if (device.nTLayerType == MyCamera.MV_USB_DEVICE)
{
MyCamera.MV_USB3_DEVICE_INFO usb3Info = (MyCamera.MV_USB3_DEVICE_INFO)MyCamera.ByteToStruct(device.SpecialInfo.stUsb3VInfo, typeof(MyCamera.MV_USB3_DEVICE_INFO));
if (usb3Info.chSerialNumber == CameraID)
return i;
}
}
return -1;
}
/// <summary>
/// 枚举海康相机(GIGE,USB3)
/// </summary>
public void EnumDevices()
{
// 枚举设备列表
m_stDeviceList.nDeviceNum = 0;
int nRet = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref m_stDeviceList);
if (0 != nRet)
{
MessageBox.Show("枚举HIK相机设备失败!");
return;
}
}
/// <summary>
/// 异常,则关闭相机
/// </summary>
/// <param name="nMsgType"></param>
/// <param name="pUser"></param>
private void cbException(uint nMsgType, IntPtr pUser)
{
IsConnect = false;
if (nMsgType == MyCamera.MV_EXCEPTION_DEV_DISCONNECT)
{
// 先关闭设备
CloseDevice();
// 在尝试重新打开设备
if (OpenDevice())
{
MessageBox.Show("尝试重新连接设备失败!");
}
}
}
/// <summary>
/// 关闭设备
/// </summary>
public void CloseDevice()
{
// 取流标志位清零
if (isGrabbing == true)
{
isGrabbing = false;
m_hReceiveThread.Join();
}
if (m_BufForDriver != IntPtr.Zero)
{
Marshal.Release(m_BufForDriver);
}
// 关闭设备
m_MyCamera.MV_CC_CloseDevice_NET();
m_MyCamera.MV_CC_DestroyDevice_NET();
IsConnect = false;
}
/// <summary>
/// 打开相机
/// </summary>
/// <returns></returns>
public bool OpenDevice()
{
// 打开相机流程:
// 1.获取所有相机
EnumDevices();
// 2.根据相机序列号,看该序列号对应的相机ID是否存在,确定该相机是否存在
// 获取相机索引
int camIdx = GetDeviceIndex(tbx_CameraSerialNum.Text.Trim());
if (camIdx == -1)
{
MessageBox.Show("找不到该ID的相机!");
return false;
}
// 3.获取相机信息
// 获取相机设备信息
MyCamera.MV_CC_DEVICE_INFO device = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[camIdx], typeof(MyCamera.MV_CC_DEVICE_INFO));
// 4.建立设备对象
// 建立设备对象
if (null == m_MyCamera)
{
// 创建相机实例
m_MyCamera = new MyCamera();
if (null == m_MyCamera)
{
MessageBox.Show("初始化相机对象失败");
return false;
}
}
// 5.根据相机信息创建相机
int nRet = m_MyCamera.MV_CC_CreateDevice_NET(ref device);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"创建设备失败,失败代码:{nRet}");
return false;
}
// 6.打开设备
nRet = m_MyCamera.MV_CC_OpenDevice_NET();
// 如果打开失败,则销毁相机设备
if (MyCamera.MV_OK != nRet)
{
m_MyCamera.MV_CC_DestroyDevice_NET();
MessageBox.Show($"设备打开失败,失败代码:{nRet}");
return false;
}
// 7.探测网络最佳包大小,并进行设置(只对GigE相机有效)
if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
{
// 获取最优网络包大小
int nPacketSize = m_MyCamera.MV_CC_GetOptimalPacketSize_NET();
if (nPacketSize > 0)
{
nRet = m_MyCamera.MV_CC_SetIntValue_NET("GevSCPSPacketSize", (uint)nPacketSize);
if (nRet != MyCamera.MV_OK)
{
MessageBox.Show($"设置包大小失败,失败代码:{nRet}");
}
}
else
{
MessageBox.Show($"获取包大小失败,返回的包大小为:{nPacketSize}");
}
}
// 8.设置为连续采集模式
m_MyCamera.MV_CC_SetEnumValue_NET("AcquisitionMode", (uint)MyCamera.MV_CAM_ACQUISITION_MODE.MV_ACQ_MODE_CONTINUOUS);
m_MyCamera.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_OFF);
// 9.注册异常回调函数
m_MyCamera.MV_CC_RegisterExceptionCallBack_NET(cbException, IntPtr.Zero);
return true;
}
/// <summary>
/// 关闭设备
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Close_Click(object sender, RoutedEventArgs e)
{
// 关闭相机
CloseDevice();
}
/// <summary>
/// 开始采集
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_StartGrab_Click(object sender, RoutedEventArgs e)
{ // 开始采集
if (StartGrab())
{
Console.WriteLine("成功开始采集");
}
}
/// <summary>
/// 开始采集
/// </summary>
/// <returns></returns>
public bool StartGrab()
{
// 开始采集流程
// 1.采集标志位置位,设为true,表示开始采集
isGrabbing = true;
// 2.启动取像线程(核心)
m_hReceiveThread = new Thread(ReceiveThreadProcess);
m_hReceiveThread.Start();
// 3.取流之前先清除帧长度
m_stFrameInfo.nFrameLen = 0;
m_stFrameInfo.enPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG8;
// 4.开始采集
int nRet = m_MyCamera.MV_CC_StartGrabbing_NET();
// 5.如果采集失败,进行提示
if (MyCamera.MV_OK != nRet)
{
isGrabbing = false;
m_hReceiveThread.Join();
MessageBox.Show($"连续采集失败,失败代码:{nRet}");
return false;
}
return true;
}
/// <summary>
/// 取像线程(核心)
/// </summary>
private void ReceiveThreadProcess()
{
// 取图线程流程
// 1.获取单帧图像数据的有效负载大小(单位为字节),即从相机传输到客户端的每一帧图像数据(不包含协议头、尾等额外开销)的实际大小
MyCamera.MVCC_INTVALUE stParam = new MyCamera.MVCC_INTVALUE();
int nRet = m_MyCamera.MV_CC_GetIntValue_NET("PayloadSize", ref stParam);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"读取PayloadSize失败,失败代码:{nRet}");
return;
}
UInt32 nPayloadSize = stParam.nCurValue;
// 2.获取图像高
nRet = m_MyCamera.MV_CC_GetIntValue_NET("Height", ref stParam);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"获取图像高失败,失败代码:{nRet}");
return;
}
uint nHeight = stParam.nCurValue;
// 3.获取图像宽
nRet = m_MyCamera.MV_CC_GetIntValue_NET("Width", ref stParam);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"获取图像宽失败,失败代码:{nRet}");
return;
}
uint nWidth = stParam.nCurValue;
// 4.根据图像大小设置图像缓存
m_pDataForRed = new byte[nWidth * nHeight];
m_pDataForGreen = new byte[nWidth * nHeight];
m_pDataForBlue = new byte[nWidth * nHeight];
if (3 * nPayloadSize > m_nBufSizeForDriver)
{
if (m_BufForDriver != IntPtr.Zero)
{
Marshal.Release(m_BufForDriver);
}
m_nBufSizeForDriver = 3 * nPayloadSize;
m_BufForDriver = Marshal.AllocHGlobal((Int32)m_nBufSizeForDriver);
}
if (m_BufForDriver == IntPtr.Zero)
{
return;
}
IntPtr pImageBuffer = Marshal.AllocHGlobal((int)nPayloadSize * 3);
if (pImageBuffer == IntPtr.Zero)
{
MessageBox.Show($"申请图像缓存区失败!");
return;
}
MyCamera.MV_FRAME_OUT_INFO_EX stFrameInfo = new MyCamera.MV_FRAME_OUT_INFO_EX();
IntPtr RedPtr = IntPtr.Zero;
IntPtr GreenPtr = IntPtr.Zero;
IntPtr BluePtr = IntPtr.Zero;
IntPtr pTemp = IntPtr.Zero;
DateTime ProStartTime = DateTime.MinValue;
// 5.循环监听,触发相机的图像采集信号
while (isGrabbing)
{
// 6.获取一帧图像数据(核心)等待,软触发或者硬触发的信号
// 锁,实际相机只有一个,但是这个取图的线程,我们可以启动多个,不能一个线程在读取数据时候,另一个线程也对这个数据进行操作
lock (BufForDriverLock)
{
// 6.获取一帧图像数据(核心)等待,软触发或者硬触发的信号
nRet = m_MyCamera.MV_CC_GetOneFrameTimeout_NET(m_BufForDriver, m_nBufSizeForDriver, ref stFrameInfo, 1000);
// 如果采集成功,则进行记录提示
if (nRet == MyCamera.MV_OK)
{
ProStartTime = DateTime.Now;
// MessageBox.Show("相机取图完成,开始处理...");
m_stFrameInfo = stFrameInfo;
}
// 判别是否采集图像成功
if (nRet == MyCamera.MV_OK)
{
// 如果是彩色相机
if (IsColorData(stFrameInfo.enPixelType))
{
// 7.如果是彩色图像格式,则直接使用数据,给到pTemp
if (stFrameInfo.enPixelType == MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed)
{
pTemp = m_BufForDriver;
}
// 否则转换成RGB三通道数据,再给到pTemp
else
{
nRet = ConvertToRGB(m_MyCamera, m_BufForDriver, stFrameInfo.nHeight, stFrameInfo.nWidth, stFrameInfo.enPixelType, pImageBuffer);
if (MyCamera.MV_OK != nRet)
{
return;
}
pTemp = pImageBuffer;
}
// 8.获取rgb三个通道的数据
unsafe
{
byte* pBufForSaveImage = (byte*)pTemp;
UInt32 nSupWidth = (stFrameInfo.nWidth + (UInt32)3) & 0xfffffffc;//5120
for (int nRow = 0; nRow < stFrameInfo.nHeight; nRow++)
{
for (int col = 0; col < stFrameInfo.nWidth; col++)
{
m_pDataForRed[nRow * nSupWidth + col] = pBufForSaveImage[nRow * stFrameInfo.nWidth * 3 + (3 * col)];
m_pDataForGreen[nRow * nSupWidth + col] = pBufForSaveImage[nRow * stFrameInfo.nWidth * 3 + (3 * col + 1)];
m_pDataForBlue[nRow * nSupWidth + col] = pBufForSaveImage[nRow * stFrameInfo.nWidth * 3 + (3 * col + 2)];
}
}
}
RedPtr = Marshal.UnsafeAddrOfPinnedArrayElement(m_pDataForRed, 0);
GreenPtr = Marshal.UnsafeAddrOfPinnedArrayElement(m_pDataForGreen, 0);
BluePtr = Marshal.UnsafeAddrOfPinnedArrayElement(m_pDataForBlue, 0);
// 9.rgb三通道的指针数据转bitmap
//显示采集图像
lock (BufForImageLock)
{
// 进行图像显示
Dispatcher.Invoke(
() => {
int width = stFrameInfo.nWidth;
int height = stFrameInfo.nHeight;
m_writeableBitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Rgb24, null);
m_writeableBitmap.Lock();
unsafe
{
byte[] temp = new byte[width * height * 3];
byte[] tempR = new byte[width * height];
byte[] tempG = new byte[width * height];
byte[] tempB = new byte[width * height];
Marshal.Copy(RedPtr, tempR, 0, width * height);
Marshal.Copy(GreenPtr, tempG, 0, width * height);
Marshal.Copy(BluePtr, tempB, 0, width * height);
for (int i = 0; i < width * height; i++)
{
temp[i * 3] = tempR[i];
temp[i * 3 + 1] = tempG[i];
temp[i * 3 + 2] = tempB[i];
}
Marshal.Copy(temp, 0, m_writeableBitmap.BackBuffer, width * height * 3);
}
m_writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
m_writeableBitmap.Unlock();
myImageCtr.Source = m_writeableBitmap;
}
);
}
}
// 11.如果是黑白相机图像
else if (IsMonoData(stFrameInfo.enPixelType))
{
if (stFrameInfo.enPixelType == MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8)
{
pTemp = m_BufForDriver;
}
else
{
nRet = ConvertToMono8(m_MyCamera, m_BufForDriver, pImageBuffer, stFrameInfo.nHeight, stFrameInfo.nWidth, stFrameInfo.enPixelType);
if (MyCamera.MV_OK != nRet)
{
return;
}
pTemp = pImageBuffer;
}
// 显示采集图像
lock (BufForImageLock)
{
// 13.进行图像显示
Dispatcher.Invoke(
() => {
int width = stFrameInfo.nWidth;
int height = stFrameInfo.nHeight;
m_writeableBitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Gray8, null);
// 12.灰度指针数据转writeablebitmap
m_writeableBitmap.Lock();
unsafe
{
byte[] tempR = new byte[width * height];
Marshal.Copy(pTemp, tempR, 0, width * height);
Marshal.Copy(tempR, 0, m_writeableBitmap.BackBuffer, width * height);
}
m_writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
m_writeableBitmap.Unlock();
myImageCtr.Source = m_writeableBitmap;
}
);
}
}
else
{
continue;
}
}
else
{
// 如果是触发模式,取图失败,则睡眠5ms
if (isTriggerMode)
{
Thread.Sleep(5);
}
}
}
}
}
/// <summary>
/// 判断是否为黑白图像
/// </summary>
/// <param name="enGvspPixelType"></param>
/// <returns></returns>
private Boolean IsMonoData(MyCamera.MvGvspPixelType enGvspPixelType)
{
switch (enGvspPixelType)
{
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12_Packed:
return true;
default:
return false;
}
}
/// <summary>
/// 判断是否为彩色图像
/// </summary>
/// <param name="enGvspPixelType"></param>
/// <returns></returns>
private Boolean IsColorData(MyCamera.MvGvspPixelType enGvspPixelType)
{
switch (enGvspPixelType)
{
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_YUV422_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_YUV422_YUYV_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_YCBCR411_8_CBYYCRYY:
return true;
default:
return false;
}
}
/// <summary>
/// 转换为RGB格式
/// </summary>
/// <param name="obj"></param>
/// <param name="pSrc"></param>
/// <param name="nHeight"></param>
/// <param name="nWidth"></param>
/// <param name="nPixelType"></param>
/// <param name="pDst"></param>
/// <returns></returns>
private Int32 ConvertToRGB(object obj, IntPtr pSrc, ushort nHeight, ushort nWidth, MyCamera.MvGvspPixelType nPixelType, IntPtr pDst)
{
if (IntPtr.Zero == pSrc || IntPtr.Zero == pDst)
{
return MyCamera.MV_E_PARAMETER;
}
int nRet = MyCamera.MV_OK;
MyCamera device = obj as MyCamera;
MyCamera.MV_PIXEL_CONVERT_PARAM stPixelConvertParam = new MyCamera.MV_PIXEL_CONVERT_PARAM();
stPixelConvertParam.pSrcData = pSrc;//源数据
if (IntPtr.Zero == stPixelConvertParam.pSrcData)
{
return -1;
}
stPixelConvertParam.nWidth = nWidth;//图像宽度
stPixelConvertParam.nHeight = nHeight;//图像高度
stPixelConvertParam.enSrcPixelType = nPixelType;//源数据的格式
stPixelConvertParam.nSrcDataLen = (uint)(nWidth * nHeight * ((((uint)nPixelType) >> 16) & 0x00ff) >> 3);
stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * ((((uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed) >> 16) & 0x00ff) >> 3);
stPixelConvertParam.pDstBuffer = pDst;//转换后的数据
stPixelConvertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed;
stPixelConvertParam.nDstBufferSize = (uint)nWidth * nHeight * 3;
nRet = device.MV_CC_ConvertPixelType_NET(ref stPixelConvertParam);//格式转换
if (MyCamera.MV_OK != nRet)
{
return -1;
}
return MyCamera.MV_OK;
}
/// <summary>
/// 转换为Mono8
/// </summary>
/// <param name="obj"></param>
/// <param name="pInData"></param>
/// <param name="pOutData"></param>
/// <param name="nHeight"></param>
/// <param name="nWidth"></param>
/// <param name="nPixelType"></param>
/// <returns></returns>
private Int32 ConvertToMono8(object obj, IntPtr pInData, IntPtr pOutData, ushort nHeight, ushort nWidth, MyCamera.MvGvspPixelType nPixelType)
{
if (IntPtr.Zero == pInData || IntPtr.Zero == pOutData)
{
return MyCamera.MV_E_PARAMETER;
}
int nRet = MyCamera.MV_OK;
MyCamera device = obj as MyCamera;
MyCamera.MV_PIXEL_CONVERT_PARAM stPixelConvertParam = new MyCamera.MV_PIXEL_CONVERT_PARAM();
stPixelConvertParam.pSrcData = pInData;//源数据
if (IntPtr.Zero == stPixelConvertParam.pSrcData)
{
return -1;
}
stPixelConvertParam.nWidth = nWidth;//图像宽度
stPixelConvertParam.nHeight = nHeight;//图像高度
stPixelConvertParam.enSrcPixelType = nPixelType;//源数据的格式
stPixelConvertParam.nSrcDataLen = (uint)(nWidth * nHeight * ((((uint)nPixelType) >> 16) & 0x00ff) >> 3);
stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * ((((uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed) >> 16) & 0x00ff) >> 3);
stPixelConvertParam.pDstBuffer = pOutData;//转换后的数据
stPixelConvertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8;
stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * 3);
nRet = device.MV_CC_ConvertPixelType_NET(ref stPixelConvertParam);//格式转换
if (MyCamera.MV_OK != nRet)
{
return -1;
}
return nRet;
}
/// <summary>
/// 关闭窗口
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
CloseDevice();
}
}
}
停止采集
/// <summary>
/// 停止采集
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_StopGrab_Click(object sender, RoutedEventArgs e)
{
StopGrab();
}
/// <summary>
/// 停止采集
/// </summary>
/// <returns></returns>
public bool StopGrab()
{
// 标志位设为false
isGrabbing = false;
// 取像线程,停止
m_hReceiveThread.Join();
// 停止采集
int nRet = m_MyCamera.MV_CC_StopGrabbing_NET();
if (nRet != MyCamera.MV_OK)
{
MessageBox.Show($"停止采集失败,失败代码:{nRet}");
return false;
}
return true;
}
补充:
在WPF中,Image
控件的显示模式主要通过 Stretch
和 StretchDirection
属性控制,用于定义图像如何适应分配的空间。以下是详细说明和示例:
1. Stretch 属性
决定图像在目标空间中的缩放方式,可选值:
- None:不缩放,保持原始尺寸。若图像大于控件,会显示部分内容。
- Fill:拉伸图像以完全填充控件,不保持宽高比(可能变形)。
- Uniform(默认):保持宽高比缩放,图像完全显示(可能留白)。
- UniformToFill:保持宽高比缩放,完全填充控件(可能裁剪部分图像)。
示例代码:
<Image Source="image.jpg" Stretch="UniformToFill" />
2. StretchDirection 属性
控制缩放的方向,可选值:
- UpOnly:仅当图像小于控件时放大。
- DownOnly:仅当图像大于控件时缩小。
- Both(默认):双向缩放。
示例代码:
<Image Source="image.jpg" Stretch="Uniform" StretchDirection="DownOnly" />
3. 其他相关属性
- Width/Height:固定控件尺寸,影响图像显示区域。
- MaxWidth/MaxHeight:限制最大尺寸,结合
Stretch
使用。 - HorizontalAlignment/VerticalAlignment:控制图像在控件内的对齐方式(如
Center
、Stretch
)。
示例:保持比例并居中
<Image
Source="image.jpg"
Stretch="Uniform"
Width="200" Height="200"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
4. 动态设置(代码后台)
// C# 代码设置
myImage.Stretch = Stretch.Uniform;
myImage.StretchDirection = StretchDirection.Both;
5. 高级场景
- 视图框(Viewbox):用
Viewbox
包裹Image
实现动态缩放。<Viewbox Stretch="Uniform"> <Image Source="image.jpg" /> </Viewbox>
- 裁剪(Clip):通过
Clip
属性自定义显示区域。
总结
场景 | 推荐设置 |
---|---|
保持比例,完整显示 | Stretch="Uniform" |
填充控件,可能裁剪 | Stretch="UniformToFill" |
禁止变形,原始尺寸 | Stretch="None" |
仅缩小大图 | StretchDirection="DownOnly" |
根据需求调整这些属性,即可灵活控制图像的显示模式。