849 lines
32 KiB
C#
849 lines
32 KiB
C#
|
using AnQing.Commons;
|
|||
|
using AnQing.Models;
|
|||
|
using Modbus.Device;
|
|||
|
using MvCamCtrl.NET;
|
|||
|
using RGD.DBUtility;
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.ComponentModel;
|
|||
|
using System.Data;
|
|||
|
using System.Data.SqlClient;
|
|||
|
using System.Drawing;
|
|||
|
using System.Drawing.Imaging;
|
|||
|
using System.IO;
|
|||
|
using System.IO.Ports;
|
|||
|
using System.Linq;
|
|||
|
using System.Runtime.InteropServices;
|
|||
|
using System.Text;
|
|||
|
using System.Threading;
|
|||
|
using System.Threading.Tasks;
|
|||
|
using System.Windows;
|
|||
|
using System.Windows.Controls;
|
|||
|
using System.Windows.Controls.Primitives;
|
|||
|
using System.Windows.Input;
|
|||
|
using System.Windows.Media;
|
|||
|
using System.Windows.Media.Imaging;
|
|||
|
using System.Windows.Threading;
|
|||
|
using ZXing;
|
|||
|
using ZXing.Common;
|
|||
|
using static System.Net.Mime.MediaTypeNames;
|
|||
|
using Application = System.Windows.Application;
|
|||
|
using Color = System.Drawing.Color;
|
|||
|
using PixelFormat = System.Drawing.Imaging.PixelFormat;
|
|||
|
|
|||
|
namespace AnQing.ViewModel
|
|||
|
{
|
|||
|
public class MianWindowViewModel : INotifyPropertyChanged
|
|||
|
{
|
|||
|
#region 属性
|
|||
|
/// <summary>
|
|||
|
/// 图片识别区域
|
|||
|
/// </summary>
|
|||
|
public List<Rectangle> possibleRegions = new List<Rectangle>();
|
|||
|
/// <summary>
|
|||
|
/// 摄像头1连接状态
|
|||
|
/// </summary>
|
|||
|
private bool video1State;
|
|||
|
public bool Video1State
|
|||
|
{
|
|||
|
get { return video1State; }
|
|||
|
set
|
|||
|
{
|
|||
|
video1State = value;
|
|||
|
OnPropertyChanged("Video1State");
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 摄像头2连接状态
|
|||
|
/// </summary>
|
|||
|
private bool video2State;
|
|||
|
public bool Video2State
|
|||
|
{
|
|||
|
get { return video2State; }
|
|||
|
set
|
|||
|
{
|
|||
|
video2State = value;
|
|||
|
OnPropertyChanged("Video2State");
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 识别状态
|
|||
|
/// </summary>
|
|||
|
private bool readerState;
|
|||
|
public bool ReaderState
|
|||
|
{
|
|||
|
get { return readerState; }
|
|||
|
set
|
|||
|
{
|
|||
|
readerState = value;
|
|||
|
OnPropertyChanged("ReaderState");
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 识别到的数据信息
|
|||
|
/// </summary>
|
|||
|
private string readerText;
|
|||
|
public string ReaderText
|
|||
|
{
|
|||
|
get { return readerText; }
|
|||
|
set
|
|||
|
{
|
|||
|
readerText = value;
|
|||
|
OnPropertyChanged("ReaderText");
|
|||
|
}
|
|||
|
}
|
|||
|
private string readerText_temp;
|
|||
|
/// <summary>
|
|||
|
/// 识别时间
|
|||
|
/// </summary>
|
|||
|
private string readerTextTime;
|
|||
|
public string ReaderTextTime
|
|||
|
{
|
|||
|
get { return readerTextTime; }
|
|||
|
set
|
|||
|
{
|
|||
|
readerTextTime = value;
|
|||
|
OnPropertyChanged("ReaderTextTime");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 图像一
|
|||
|
/// </summary>
|
|||
|
private ImageSource _imageSource;
|
|||
|
public ImageSource ImageSource
|
|||
|
{
|
|||
|
get { return _imageSource; }
|
|||
|
set
|
|||
|
{
|
|||
|
if (_imageSource != value)
|
|||
|
{
|
|||
|
if (_imageSource is BitmapSource oldBitmapSource)
|
|||
|
{
|
|||
|
DisposeBitmapSource(oldBitmapSource);
|
|||
|
}
|
|||
|
_imageSource = value;
|
|||
|
OnPropertyChanged(nameof(ImageSource));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 释放 BitmapSource 资源,避免 GDI 资源泄漏
|
|||
|
/// </summary>
|
|||
|
private void DisposeBitmapSource(BitmapSource bitmapSource)
|
|||
|
{
|
|||
|
if (bitmapSource == null) return;
|
|||
|
|
|||
|
if (bitmapSource is IDisposable disposable)
|
|||
|
{
|
|||
|
disposable.Dispose();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 图像二
|
|||
|
/// </summary>
|
|||
|
private ImageSource _imageSource2;
|
|||
|
public ImageSource ImageSource2
|
|||
|
{
|
|||
|
get { return _imageSource2; }
|
|||
|
set
|
|||
|
{
|
|||
|
if (_imageSource2 != value)
|
|||
|
{
|
|||
|
_imageSource2 = value;
|
|||
|
OnPropertyChanged(nameof(ImageSource2));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region 事件
|
|||
|
/// <summary>
|
|||
|
/// 摄像触发采集
|
|||
|
/// </summary>
|
|||
|
private ICommand buttonGatherCommand;
|
|||
|
public ICommand ButtonGatherCommand => buttonGatherCommand ??= new RelayCommand(TriggerExec);
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 释放
|
|||
|
/// </summary>
|
|||
|
private ICommand buttonCloseCommand;
|
|||
|
public ICommand ButtonCloseCommand => buttonCloseCommand ??= new RelayCommand(CloseCamera);
|
|||
|
#endregion
|
|||
|
|
|||
|
#region 相机初始化
|
|||
|
//图像一
|
|||
|
MyCamera.MV_CC_DEVICE_INFO_LIST m_stDeviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
|
|||
|
public MyCamera m_MyCamera = new MyCamera();
|
|||
|
bool m_bGrabbing = false;
|
|||
|
//图像二
|
|||
|
public MyCamera m_MyCamera2 = new MyCamera();
|
|||
|
bool m_bGrabbing2 = false;
|
|||
|
/// <summary>
|
|||
|
/// 关闭程序需要关闭摄像头
|
|||
|
/// </summary>
|
|||
|
private void CloseCamera()
|
|||
|
{
|
|||
|
// ch:关闭设备 | en:Close Device
|
|||
|
m_MyCamera.MV_CC_CloseDevice_NET();
|
|||
|
m_MyCamera.MV_CC_DestroyDevice_NET();
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 初始化打开摄像头
|
|||
|
/// </summary>
|
|||
|
private void OpenCamera()
|
|||
|
{
|
|||
|
string targetIp = "192.168.2.11"; // 摄像头IP
|
|||
|
string targetIp2 = "192.168.3.13"; // 摄像头2 IP
|
|||
|
|
|||
|
bool ip1 = false, ip2 = false;
|
|||
|
System.GC.Collect();
|
|||
|
m_stDeviceList.nDeviceNum = 0;
|
|||
|
|
|||
|
int nRet1 = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref m_stDeviceList);
|
|||
|
if (nRet1 != 0)
|
|||
|
{
|
|||
|
MessageBox.Show("MV_CC_EnumDevices_NET 初始化失败: " + nRet1);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// ch:获取选择的设备信息 | en:Get selected device information
|
|||
|
MyCamera.MV_CC_DEVICE_INFO device = new MyCamera.MV_CC_DEVICE_INFO();//相机一
|
|||
|
MyCamera.MV_CC_DEVICE_INFO device2 = new MyCamera.MV_CC_DEVICE_INFO();//相机二
|
|||
|
for (int i = 0; i < m_stDeviceList.nDeviceNum; i++)
|
|||
|
{
|
|||
|
MyCamera.MV_CC_DEVICE_INFO device_temp = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
|
|||
|
|
|||
|
if (device_temp.nTLayerType == MyCamera.MV_GIGE_DEVICE)
|
|||
|
{
|
|||
|
MyCamera.MV_GIGE_DEVICE_INFO_EX gigeInfo = (MyCamera.MV_GIGE_DEVICE_INFO_EX)MyCamera.ByteToStruct(device_temp.SpecialInfo.stGigEInfo, typeof(MyCamera.MV_GIGE_DEVICE_INFO_EX));
|
|||
|
|
|||
|
string deviceIp = $"{(gigeInfo.nCurrentIp >> 24) & 0xFF}.{(gigeInfo.nCurrentIp >> 16) & 0xFF}.{(gigeInfo.nCurrentIp >> 8) & 0xFF}.{gigeInfo.nCurrentIp & 0xFF}";
|
|||
|
if (deviceIp == targetIp) // 相机一
|
|||
|
{
|
|||
|
device = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
|
|||
|
ip1 = true;
|
|||
|
}
|
|||
|
if (deviceIp == targetIp2) // 相机二
|
|||
|
{
|
|||
|
device2 = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
|
|||
|
ip2 = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ch:打开设备 | en:Open device
|
|||
|
if (null == m_MyCamera)
|
|||
|
{
|
|||
|
m_MyCamera = new MyCamera();
|
|||
|
if (null == m_MyCamera)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
if (null == m_MyCamera2)
|
|||
|
{
|
|||
|
m_MyCamera2 = new MyCamera();
|
|||
|
if (null == m_MyCamera2)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
int nRet = m_MyCamera.MV_CC_CreateDevice_NET(ref device);
|
|||
|
if (MyCamera.MV_OK != nRet)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
nRet = m_MyCamera.MV_CC_OpenDevice_NET();
|
|||
|
if (MyCamera.MV_OK != nRet)
|
|||
|
{
|
|||
|
m_MyCamera.MV_CC_CloseDevice_NET();
|
|||
|
m_MyCamera.MV_CC_DestroyDevice_NET();
|
|||
|
|
|||
|
//// ch:关闭设备 | en:Close Device
|
|||
|
m_MyCamera2.MV_CC_CloseDevice_NET();
|
|||
|
m_MyCamera2.MV_CC_DestroyDevice_NET();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
int nRet_2 = m_MyCamera2.MV_CC_CreateDevice_NET(ref device2);
|
|||
|
if (MyCamera.MV_OK != nRet_2)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
nRet_2 = m_MyCamera2.MV_CC_OpenDevice_NET();
|
|||
|
if (MyCamera.MV_OK != nRet_2)
|
|||
|
{
|
|||
|
m_MyCamera.MV_CC_CloseDevice_NET();
|
|||
|
m_MyCamera.MV_CC_DestroyDevice_NET();
|
|||
|
|
|||
|
//// ch:关闭设备 | en:Close Device
|
|||
|
m_MyCamera2.MV_CC_CloseDevice_NET();
|
|||
|
m_MyCamera2.MV_CC_DestroyDevice_NET();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
|
|||
|
if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
|
|||
|
{
|
|||
|
int nPacketSize = m_MyCamera.MV_CC_GetOptimalPacketSize_NET();
|
|||
|
if (nPacketSize > 0)
|
|||
|
{
|
|||
|
nRet = m_MyCamera.MV_CC_SetIntValueEx_NET("GevSCPSPacketSize", nPacketSize);
|
|||
|
if (nRet != MyCamera.MV_OK)
|
|||
|
{
|
|||
|
MessageBox.Show("MV_CC_SetIntValueEx_NET 初始化失败: " + nRet);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
MessageBox.Show("MV_CC_SetIntValueEx_NET 初始化失败: " + nPacketSize);
|
|||
|
}
|
|||
|
}
|
|||
|
if (device2.nTLayerType == MyCamera.MV_GIGE_DEVICE)
|
|||
|
{
|
|||
|
int nPacketSize = m_MyCamera2.MV_CC_GetOptimalPacketSize_NET();
|
|||
|
if (nPacketSize > 0)
|
|||
|
{
|
|||
|
nRet_2 = m_MyCamera2.MV_CC_SetIntValueEx_NET("GevSCPSPacketSize", nPacketSize);
|
|||
|
if (nRet_2 != MyCamera.MV_OK)
|
|||
|
{
|
|||
|
MessageBox.Show("MV_CC_SetIntValueEx_NET 初始化失败: " + nRet_2);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
MessageBox.Show("MV_CC_SetIntValueEx_NET 初始化失败: " + nPacketSize);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ch:设置采集连续模式 | en:Set Continues Aquisition Mode
|
|||
|
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_ON);
|
|||
|
m_MyCamera.MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_SOFTWARE);
|
|||
|
m_MyCamera2.MV_CC_SetEnumValue_NET("AcquisitionMode", (uint)MyCamera.MV_CAM_ACQUISITION_MODE.MV_ACQ_MODE_CONTINUOUS);
|
|||
|
m_MyCamera2.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_ON);
|
|||
|
m_MyCamera2.MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_SOFTWARE);
|
|||
|
|
|||
|
//bnGetParam_Click(null, null);//获取相机焦距等参数
|
|||
|
|
|||
|
// ch:标志位置位true | en:Set position bit true
|
|||
|
m_bGrabbing = true;
|
|||
|
m_bGrabbing2 = true;
|
|||
|
|
|||
|
// ch:开始采集 | en:Start Grabbing
|
|||
|
int nRet2 = m_MyCamera.MV_CC_StartGrabbing_NET();
|
|||
|
if (MyCamera.MV_OK != nRet2)
|
|||
|
{
|
|||
|
m_bGrabbing = false;
|
|||
|
MessageBox.Show("MV_CC_StartGrabbing_NET 初始化失败: " + nRet2);
|
|||
|
return;
|
|||
|
}
|
|||
|
int nRet2_2 = m_MyCamera2.MV_CC_StartGrabbing_NET();
|
|||
|
if (MyCamera.MV_OK != nRet2_2)
|
|||
|
{
|
|||
|
m_bGrabbing2 = false;
|
|||
|
MessageBox.Show("MV_CC_StartGrabbing_NET 初始化失败: " + nRet2_2);
|
|||
|
return;
|
|||
|
}
|
|||
|
if (ip1)
|
|||
|
Video1State = true;
|
|||
|
|
|||
|
if (ip2)
|
|||
|
Video2State = true;
|
|||
|
}
|
|||
|
|
|||
|
private List<string> ReadImageGuid_Temp = new List<string> ();
|
|||
|
public string ReaderImagePath_Temp;
|
|||
|
/// <summary>
|
|||
|
/// 抓拍图片
|
|||
|
/// </summary>
|
|||
|
public void ReceiveThreadProcess()
|
|||
|
{
|
|||
|
MyCamera.MV_FRAME_OUT stFrameInfo = new MyCamera.MV_FRAME_OUT();
|
|||
|
int nRet = m_MyCamera.MV_CC_GetImageBuffer_NET(ref stFrameInfo, 1000);
|
|||
|
if (nRet == MyCamera.MV_OK)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
int width = stFrameInfo.stFrameInfo.nWidth;
|
|||
|
int height = stFrameInfo.stFrameInfo.nHeight;
|
|||
|
int stride = width; // Mono8: 每个像素1字节,stride = width
|
|||
|
|
|||
|
// 直接利用内存地址创建 Bitmap 对象
|
|||
|
Bitmap bitmap = new Bitmap(width, height, stride, PixelFormat.Format8bppIndexed, stFrameInfo.pBufAddr);
|
|||
|
|
|||
|
// 设置灰度调色板
|
|||
|
ColorPalette palette = bitmap.Palette;
|
|||
|
for (int i = 0; i < 256; i++)
|
|||
|
{
|
|||
|
palette.Entries[i] = Color.FromArgb(i, i, i);
|
|||
|
}
|
|||
|
bitmap.Palette = palette;
|
|||
|
|
|||
|
DateTime start = DateTime.Now;
|
|||
|
var reader = new BarcodeReader
|
|||
|
{
|
|||
|
AutoRotate = true,
|
|||
|
Options = new DecodingOptions
|
|||
|
{
|
|||
|
TryHarder = true, // 深度解析模式
|
|||
|
PureBarcode = false, // 是否纯条码(无文字干扰)
|
|||
|
AssumeCode39CheckDigit = false, // 是否验证Code39校验位
|
|||
|
PossibleFormats = new List<BarcodeFormat>
|
|||
|
{
|
|||
|
BarcodeFormat.CODE_128
|
|||
|
//BarcodeFormat.EAN_13,
|
|||
|
//BarcodeFormat.EAN_8,
|
|||
|
//BarcodeFormat.UPC_A,
|
|||
|
//BarcodeFormat.UPC_E,
|
|||
|
//BarcodeFormat.CODE_39,
|
|||
|
//BarcodeFormat.ITF,
|
|||
|
//BarcodeFormat.CODABAR
|
|||
|
},
|
|||
|
CharacterSet = "UTF-8"
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
foreach (var region in possibleRegions)
|
|||
|
{
|
|||
|
Bitmap cropped = CropBarcodeRegion(bitmap, region);
|
|||
|
var result = reader.Decode(cropped);
|
|||
|
cropped.Dispose();
|
|||
|
cropped = null;
|
|||
|
if (result != null)
|
|||
|
{
|
|||
|
readerText_temp = result.Text;
|
|||
|
ReaderText = result.Text;
|
|||
|
ReaderState = false;
|
|||
|
break;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
readerText_temp = "";
|
|||
|
}
|
|||
|
}
|
|||
|
if (string.IsNullOrEmpty(readerText_temp))
|
|||
|
{
|
|||
|
ReaderText = "未检测到条码!";
|
|||
|
ReaderState = true;
|
|||
|
}
|
|||
|
DateTime end = DateTime.Now;
|
|||
|
TimeSpan duration = end - start;
|
|||
|
ReaderTextTime = duration.TotalSeconds.ToString() + " 秒";
|
|||
|
|
|||
|
Application.Current.Dispatcher.Invoke(() =>
|
|||
|
{
|
|||
|
// 转换为 WPF BitmapImage 用于 Image 控件显示
|
|||
|
ImageSource = Common.BitmapToBitmapSource2(bitmap);
|
|||
|
});
|
|||
|
string guid = Guid.NewGuid().ToString();
|
|||
|
ReaderImagePath_Temp = AppDomain.CurrentDomain.BaseDirectory + "ImageList\\" + DateTime.Now.ToString("yyyy-MM-dd") + "\\" + guid + ".jpeg";
|
|||
|
string imgpath_after = "";
|
|||
|
string sql = @"
|
|||
|
IF NOT EXISTS (SELECT 1 FROM SCAN_MIAN WHERE goodcode = @goodcode)
|
|||
|
BEGIN
|
|||
|
INSERT INTO SCAN_MIAN (time, goodcode, img, img_after, ex)
|
|||
|
VALUES (@time, @goodcode, @img, @img_after, @ex)
|
|||
|
END";
|
|||
|
|
|||
|
SqlParameter[] parameters = {
|
|||
|
new SqlParameter("@time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
|
|||
|
new SqlParameter("@goodcode", readerText_temp),
|
|||
|
new SqlParameter("@img", ReaderImagePath_Temp),
|
|||
|
new SqlParameter("@img_after", imgpath_after),
|
|||
|
new SqlParameter("@ex", guid)
|
|||
|
};
|
|||
|
|
|||
|
DbHelperSQL.ExecuteSql(sql, parameters);
|
|||
|
DbHelperSQL.ExecuteSql($"INSERT INTO SCAN_MIAN (time, goodcode, img, img_after,ex) VALUES ('{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}', '{readerText_temp}', '{ReaderImagePath_Temp}', '{imgpath_after}','{guid}');");
|
|||
|
ReadImageGuid_Temp.Add(guid);
|
|||
|
// 直接将图像数据保存到本地(例如保存为 JPEG 格式)
|
|||
|
SaveBitmapToFile(bitmap, ReaderImagePath_Temp);
|
|||
|
|
|||
|
bitmap.Dispose();
|
|||
|
bitmap = null;
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
MessageBox.Show("图像处理失败: " + ex.Message);
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
m_MyCamera.MV_CC_FreeImageBuffer_NET(ref stFrameInfo);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
MessageBox.Show("获取图像失败,错误码:" + nRet);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public string ReaderImagePath_Temp2;
|
|||
|
/// <summary>
|
|||
|
/// 抓拍图片 左相机
|
|||
|
/// </summary>
|
|||
|
public void ReceiveThreadProcess2()
|
|||
|
{
|
|||
|
MyCamera.MV_FRAME_OUT stFrameInfo = new MyCamera.MV_FRAME_OUT();
|
|||
|
int nRet = m_MyCamera2.MV_CC_GetImageBuffer_NET(ref stFrameInfo, 1000);
|
|||
|
if (nRet == MyCamera.MV_OK)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
int width = stFrameInfo.stFrameInfo.nWidth;
|
|||
|
int height = stFrameInfo.stFrameInfo.nHeight;
|
|||
|
int stride = width; // Mono8: 每个像素1字节,stride = width
|
|||
|
|
|||
|
// 直接利用内存地址创建 Bitmap 对象
|
|||
|
Bitmap bitmap = new Bitmap(width, height, stride, PixelFormat.Format8bppIndexed, stFrameInfo.pBufAddr);
|
|||
|
|
|||
|
// 设置灰度调色板
|
|||
|
ColorPalette palette = bitmap.Palette;
|
|||
|
for (int i = 0; i < 256; i++)
|
|||
|
{
|
|||
|
palette.Entries[i] = Color.FromArgb(i, i, i);
|
|||
|
}
|
|||
|
bitmap.Palette = palette;
|
|||
|
|
|||
|
Application.Current.Dispatcher.Invoke(() =>
|
|||
|
{
|
|||
|
// 转换为 WPF BitmapImage 用于 Image 控件显示
|
|||
|
ImageSource2 = Common.BitmapToBitmapSource2(bitmap);
|
|||
|
});
|
|||
|
ReaderImagePath_Temp2 = AppDomain.CurrentDomain.BaseDirectory + "ImageList\\" + DateTime.Now.ToString("yyyy-MM-dd") + "\\" + Guid.NewGuid() + ".jpeg";
|
|||
|
//DbHelperSQL.ExecuteSql($"update SCAN_MIAN set img_after='{ReaderImagePath_Temp2}' WHERE id = (SELECT id FROM ( SELECT id FROM SCAN_MIAN ORDER BY id DESC OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY ) AS subquery)");
|
|||
|
DbHelperSQL.ExecuteSql($"update SCAN_MIAN set img_after='{ReaderImagePath_Temp2}' WHERE ex = '{ReadImageGuid_Temp[0]}'");
|
|||
|
ReadImageGuid_Temp.Remove(ReadImageGuid_Temp[0]);
|
|||
|
// 直接将图像数据保存到本地(例如保存为 JPEG 格式)
|
|||
|
SaveBitmapToFile(bitmap, ReaderImagePath_Temp2);
|
|||
|
|
|||
|
bitmap.Dispose();
|
|||
|
bitmap = null;
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
MessageBox.Show("图像处理失败: " + ex.Message);
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
m_MyCamera.MV_CC_FreeImageBuffer_NET(ref stFrameInfo);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
MessageBox.Show("获取图像失败,错误码:" + nRet);
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 直接将图像保存为文件
|
|||
|
/// </summary>
|
|||
|
/// <param name="bitmap"></param>
|
|||
|
/// <param name="filePath"></param>
|
|||
|
private void SaveBitmapToFile(Bitmap bitmap, string filePath)
|
|||
|
{
|
|||
|
// 使用 MemoryStream 保存图像,减少内存占用
|
|||
|
using (MemoryStream ms = new MemoryStream())
|
|||
|
{
|
|||
|
bitmap.Save(ms, ImageFormat.Jpeg); // 保存为 JPEG 格式
|
|||
|
File.WriteAllBytes(filePath, ms.ToArray());
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 触发拍照指令 右相机
|
|||
|
/// </summary>
|
|||
|
public void TriggerExec()
|
|||
|
{
|
|||
|
string sql = @"
|
|||
|
IF NOT EXISTS (SELECT 1 FROM SCAN_MIAN WHERE goodcode = @goodcode)
|
|||
|
BEGIN
|
|||
|
INSERT INTO SCAN_MIAN (time, goodcode, img, img_after, ex)
|
|||
|
VALUES (@time, @goodcode, @img, @img_after, @ex)
|
|||
|
END";
|
|||
|
|
|||
|
SqlParameter[] parameters = {
|
|||
|
new SqlParameter("@time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
|
|||
|
new SqlParameter("@goodcode", "123"),
|
|||
|
new SqlParameter("@img", "321"),
|
|||
|
new SqlParameter("@img_after", "imgpath_after"),
|
|||
|
new SqlParameter("@ex", "guid")
|
|||
|
};
|
|||
|
DbHelperSQL.ExecuteSql(sql, parameters);
|
|||
|
//// ch:触发命令 | en:Trigger command
|
|||
|
//int nRet = m_MyCamera.MV_CC_SetCommandValue_NET("TriggerSoftware");
|
|||
|
//if (MyCamera.MV_OK != nRet)
|
|||
|
//{
|
|||
|
// //ShowErrorMsg("Trigger Software Fail!", nRet);
|
|||
|
//}
|
|||
|
//if (m_bGrabbing)
|
|||
|
//{
|
|||
|
// Task.Run(() => {
|
|||
|
// ReceiveThreadProcess();
|
|||
|
// });
|
|||
|
//}
|
|||
|
//TriggerExec_Left();
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 触发拍照指令 左相机
|
|||
|
/// </summary>
|
|||
|
public void TriggerExec_Left()
|
|||
|
{
|
|||
|
// ch:触发命令 | en:Trigger command
|
|||
|
int nRet = m_MyCamera2.MV_CC_SetCommandValue_NET("TriggerSoftware");
|
|||
|
if (MyCamera.MV_OK != nRet)
|
|||
|
{
|
|||
|
//ShowErrorMsg("Trigger Software Fail!", nRet);
|
|||
|
}
|
|||
|
if (m_bGrabbing2)
|
|||
|
{
|
|||
|
Task.Run(() => {
|
|||
|
ReceiveThreadProcess2();
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
public static Bitmap CropBarcodeRegion(Bitmap original, Rectangle roi)
|
|||
|
{
|
|||
|
// 确保裁剪区域在图片范围内
|
|||
|
roi.Intersect(new Rectangle(0, 0, original.Width, original.Height));
|
|||
|
|
|||
|
// 进行裁剪
|
|||
|
return original.Clone(roi, original.PixelFormat);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
|
|||
|
#region 通讯数据
|
|||
|
private SerialPort _serialPort;
|
|||
|
private IModbusSerialMaster _modbusMaster;
|
|||
|
private DispatcherTimer _timer;
|
|||
|
|
|||
|
private bool _leftCameraTriggered;
|
|||
|
public bool LeftCameraTriggered
|
|||
|
{
|
|||
|
get => _leftCameraTriggered;
|
|||
|
set { _leftCameraTriggered = value; OnPropertyChanged("LeftCameraTriggered"); }
|
|||
|
}
|
|||
|
|
|||
|
private bool _rightCameraTriggered;
|
|||
|
public bool RightCameraTriggered
|
|||
|
{
|
|||
|
get => _rightCameraTriggered;
|
|||
|
set { _rightCameraTriggered = value; OnPropertyChanged("RightCameraTriggered"); }
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 初始化连接PLC
|
|||
|
/// </summary>
|
|||
|
private void InitializeModbus()
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
// 创建并打开串口
|
|||
|
_serialPort = new SerialPort("COM10", 115200, Parity.None, 8, StopBits.One);
|
|||
|
_serialPort.ReadTimeout = 1000; // 设置串口读取超时为1秒
|
|||
|
_serialPort.WriteTimeout = 1000; // 设置串口写入超时为1秒
|
|||
|
_serialPort.Open();
|
|||
|
|
|||
|
// 创建 Modbus 主站
|
|||
|
_modbusMaster = ModbusSerialMaster.CreateRtu(_serialPort);
|
|||
|
_modbusMaster.Transport.Retries = 3;
|
|||
|
_modbusMaster.Transport.WaitToRetryMilliseconds = 100;
|
|||
|
_modbusMaster.Transport.SlaveBusyUsesRetryCount = true;
|
|||
|
Console.WriteLine("MODBUS 连接成功!");
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Console.WriteLine("MODBUS 连接失败: " + ex.Message);
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 线程 监听接收信息
|
|||
|
/// </summary>
|
|||
|
private void StartPolling()
|
|||
|
{
|
|||
|
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
|
|||
|
_timer.Tick += (s, e) => ReadPlcData();
|
|||
|
_timer.Start();
|
|||
|
}
|
|||
|
private async Task<ushort[]> ReadHoldingRegistersWithRetry(byte slaveId, ushort startAddress, ushort numRegisters)
|
|||
|
{
|
|||
|
int retryCount = 3;
|
|||
|
int waitTimeMs = 100;
|
|||
|
ushort[] result = null;
|
|||
|
|
|||
|
for (int i = 0; i < retryCount; i++)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
result = _modbusMaster.ReadHoldingRegisters(slaveId, startAddress, numRegisters);
|
|||
|
return result; // 成功读取则返回结果
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Console.WriteLine($"Modbus 读取失败: {ex.Message}");
|
|||
|
if (i < retryCount - 1) // 如果不是最后一次重试
|
|||
|
{
|
|||
|
await Task.Delay(waitTimeMs); // 等待一段时间后重试
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return result; // 如果超过重试次数还没成功,可以返回 null 或处理失败逻辑
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 信息接收方法
|
|||
|
/// </summary>
|
|||
|
private void ReadPlcData()
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
//return;
|
|||
|
byte slaveId = 3; // 站号3
|
|||
|
|
|||
|
//// 读取左相机寄存器VW2000(Modbus地址0)
|
|||
|
//ushort[] leftCameraRegisters = _modbusMaster.ReadHoldingRegisters(slaveId, 0, 1);
|
|||
|
|
|||
|
//// 读取右相机寄存器VW2002(Modbus地址1)
|
|||
|
//ushort[] rightCameraRegisters = _modbusMaster.ReadHoldingRegisters(slaveId, 1, 1);
|
|||
|
|
|||
|
// 一次性读取左相机和右相机的寄存器
|
|||
|
ushort[] registers = _modbusMaster.ReadHoldingRegisters(slaveId, 0, 2);
|
|||
|
|
|||
|
// 解析左相机信号(VW2000的第0位)
|
|||
|
LeftCameraTriggered = (registers[0] & 0x0001) != 0;
|
|||
|
|
|||
|
// 解析右相机信号(VW2002的第1位)
|
|||
|
RightCameraTriggered = (registers[1] & 0x0002) != 0;
|
|||
|
|
|||
|
// 触发拍照
|
|||
|
if (RightCameraTriggered) TakePhoto("右相机");
|
|||
|
if (LeftCameraTriggered) TakePhoto("左相机");
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Console.WriteLine("PLC 读取失败: " + ex.Message);
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 向上位机Y5/7发送右相机OK信号至PLC
|
|||
|
/// </summary>
|
|||
|
/// <param name="isActive">true=激活信号,false=关闭信号</param>
|
|||
|
public void SendRightCameraOkSignal()
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
byte slaveId = 3; // 站号3
|
|||
|
ushort coilAddress = 7; // Y5/7的Modbus VW 2014
|
|||
|
short value = 1;
|
|||
|
_modbusMaster.WriteSingleRegister(slaveId, coilAddress,(ushort)value);
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Console.WriteLine("PLC信号发送失败: " + ex.Message);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private DateTime lastLeftTriggerTime = DateTime.MinValue; // 左相机上次触发时间
|
|||
|
private DateTime lastRightTriggerTime = DateTime.MinValue; // 右相机上次触发时间
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 获取哪个相机要触发
|
|||
|
/// </summary>
|
|||
|
/// <param name="camera"></param>
|
|||
|
|
|||
|
private void TakePhoto(string camera)
|
|||
|
{
|
|||
|
DateTime currentTime = DateTime.Now;
|
|||
|
|
|||
|
if (camera == "左相机")
|
|||
|
{
|
|||
|
// 判断左相机触发的间隔时间
|
|||
|
if ((currentTime - lastLeftTriggerTime).TotalSeconds < 1)
|
|||
|
{
|
|||
|
Console.WriteLine("左相机触发太快,请稍等...");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 更新左相机的触发时间
|
|||
|
lastLeftTriggerTime = currentTime;
|
|||
|
|
|||
|
Console.WriteLine("左相机触发" + currentTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
|||
|
TriggerExec_Left();
|
|||
|
}
|
|||
|
else if (camera == "右相机")
|
|||
|
{
|
|||
|
// 判断右相机触发的间隔时间
|
|||
|
if ((currentTime - lastRightTriggerTime).TotalSeconds < 1)
|
|||
|
{
|
|||
|
Console.WriteLine("右相机触发太快,请稍等...");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 更新右相机的触发时间
|
|||
|
lastRightTriggerTime = currentTime;
|
|||
|
|
|||
|
Console.WriteLine("右相机触发" + currentTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
|||
|
TriggerExec();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
public void Dispose()
|
|||
|
{
|
|||
|
// 停止定时器
|
|||
|
_timer?.Stop();
|
|||
|
|
|||
|
// 释放 ModbusMaster 和串口资源
|
|||
|
_modbusMaster?.Dispose();
|
|||
|
if (_serialPort?.IsOpen == true) _serialPort.Close();
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
public MianWindowViewModel()
|
|||
|
{
|
|||
|
GetPossibleRegions();
|
|||
|
|
|||
|
////ch: 初始化SDK | en: Initialize SDK
|
|||
|
//MyCamera.MV_CC_Initialize_NET();
|
|||
|
|
|||
|
////相机初始化
|
|||
|
//OpenCamera();
|
|||
|
|
|||
|
//plc 通讯初始化
|
|||
|
//InitializeModbus();
|
|||
|
//StartPolling();
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 获取裁切范围
|
|||
|
/// </summary>
|
|||
|
public void GetPossibleRegions()
|
|||
|
{
|
|||
|
possibleRegions.Clear();
|
|||
|
DataView dv = DbHelperSQL.Query("select * from IMAGEINFO").Tables[0].DefaultView;
|
|||
|
foreach (DataRowView rowView in dv)
|
|||
|
{
|
|||
|
possibleRegions.Add(new Rectangle(int.Parse(rowView["x"].ToString()), int.Parse(rowView["y"].ToString()), int.Parse(rowView["width"].ToString()), int.Parse(rowView["height"].ToString())));
|
|||
|
}
|
|||
|
}
|
|||
|
public event PropertyChangedEventHandler PropertyChanged;
|
|||
|
protected void OnPropertyChanged(string propertyName)
|
|||
|
{
|
|||
|
if (PropertyChanged != null)
|
|||
|
{
|
|||
|
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|