using Autofac; using Bro.Common.Helper; using Bro.Common.Interface; using Bro.Common.Model; using HalconDotNet; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Design; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using static Bro.Common.Helper.EnumHelper; namespace Bro.Common.Base { public abstract class CameraBase : DeviceBase { private const int IMAGESETLIFETIME = 10; #region DeviceBase protected override void Start() { if (clearImageTimer == null) { clearImageTimer = new System.Threading.Timer((o) => { ClearImage(); }, null, 0, 1000 * 60 * 120); } //if (clearImageSetTimer == null) //{ // clearImageSetTimer = new System.Threading.Timer((o) => { ClearImageSetPeriodically(); }, null, 0, 1000 * IMAGESETLIFETIME); //} } protected override void Stop() { if (clearImageTimer != null) { clearImageTimer.Change(-1, -1); clearImageTimer.Dispose(); clearImageTimer = null; } //if (clearImageSetTimer != null) //{ // clearImageSetTimer.Change(-1, -1); // clearImageSetTimer.Dispose(); // clearImageSetTimer = null; //} } #endregion #region 配置相关 public virtual CameraInitialConfigBase IConfig { get { return InitialConfig as CameraInitialConfigBase; } } public abstract IOperationConfig GetOperationConfigFromDevice(); public abstract void UploadOperationConfig(IOperationConfig config); #endregion #region 图片相关 public Action OnImageUpdated; public Action OnHImageOutput { get; set; } public event Action OnCameraOpModeChanged; private EnumHelper.CameraOpMode opMode = CameraOpMode.SingleSnapShot; /// /// 运行模式 扫描or拍照 /// public EnumHelper.CameraOpMode OpMode { get => opMode; set { if (opMode != value) { opMode = value; OnCameraOpModeChanged?.BeginInvoke(null, null); } if (opMode == CameraOpMode.ContinuousMode) { Task.Run(() => { while (CurrentState == DeviceState.DSOpen && opMode == CameraOpMode.ContinuousMode) { Snapshot(); ImageShowedHandle.WaitOne(); } }); } } } public AutoResetEvent ImageShowedHandle = new AutoResetEvent(false); //private Bitmap showImage; /// /// 输出显示的图片 /// //public virtual Bitmap ShowImage //{ // get // { // return showImage; // } // set // { // try // { // showImage = value; // if (showImage != null) // { // OnImageUpdated?.Invoke(this, new Bitmap(showImage)); // } // } // catch (Exception ex) // { // LogAsync(DateTime.Now, $"图片显示异常信息", ex.GetExceptionMessage()); // } // } //} /// /// 一次性抓拍 /// /// public abstract void Snapshot(); public abstract IImageSet Snapshot(IOperationConfig config); public virtual void InitialImageSet(IImageSet set, ImageSaveOption saveOption) { OpMode = CameraOpMode.SingleSnapShot; //CameraOprerationConfigBase opConfig = config as CameraOprerationConfigBase; set.ImageSaveOption.DataFrom(saveOption); set.IsOriginSaved = !set.ImageSaveOption.IsSaveOriginImage; set.IsFitSaved = !set.ImageSaveOption.IsSaveFitImage; set.IsAddtionalSaved = string.IsNullOrWhiteSpace(set.ImageSaveOption.AddtionalSaveType); set.OnImageSetTimeout += ImageSet_OnImageSetTimeout; _imageSetList.Add(set); } /// /// 释放图片数据 /// protected virtual void FreeImage() { //if (ShowImage != null) //{ // ShowImage.Dispose(); //} } #endregion #region 基元处理 public Action, string> OnElementsUpdated; #endregion #region 图片缓存/保存 public readonly ConcurrentBag _imageSetList = new ConcurrentBag(); public void NewImageSet(IImageSet set) { set.IsOriginSaved = !set.ImageSaveOption.IsSaveOriginImage; set.IsFitSaved = !set.ImageSaveOption.IsSaveFitImage; set.IsAddtionalSaved = string.IsNullOrWhiteSpace(set.ImageSaveOption.AddtionalSaveType); set.OnImageSetTimeout += ImageSet_OnImageSetTimeout; _imageSetList.Add(set); } //public virtual async void UpdateFitElements(List eleList, string imgSetId) //{ // await Task.Run(() => // { // OnElementsUpdated?.BeginInvoke(this, eleList, imgSetId, null, null); // }); //} public virtual async void SaveOriginImage(Bitmap map, Bitmap showImage, string imgSetId) { await Task.Run(() => { OnImageUpdated?.Invoke(this, showImage, imgSetId); }); IImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId); if (set == null) return; set.Image = map; await Task.Run(() => { lock (set.SaveLock) { if (set.ImageSaveOption.IsSaveOriginImage && !set.IsOriginSaved) { string imgDir = CheckImageDirectory(set.ImageSaveOption.ImageSaveSubDirectory, "Origin"); try { SaveImageByNameAndType(map, set.Id, set.ImageSaveOption.ImageFormat, imgDir); } catch (Exception ex) { LogAsync(DateTime.Now, $"{set.Id}原图保存失败", ex.GetExceptionMessage()); } } set.IsOriginSaved = true; ClearImageSet(set); } }); } public virtual async void SaveFitImage(List eleList, string imgSetId) { if (eleList.Count > 0) OnElementsUpdated?.BeginInvoke(this, eleList, imgSetId, null, null); IImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId); if (set == null) return; if (eleList.Count <= 0) { set.IsFitSaved = true; return; } //set.IsInvokeSaveFit = true; await Task.Run(() => { lock (set.SaveLock) { if (set.ImageSaveOption.IsSaveFitImage && !set.IsFitSaved) { string imgDir = CheckImageDirectory(set.ImageSaveOption.ImageSaveSubDirectory, "FitResult"); while (set.Image == null) { Thread.Sleep(50); } try { Bitmap map = new Bitmap(set.Image.Width, set.Image.Height); using (Graphics g = Graphics.FromImage(map)) { g.DrawImage(set.Image, 0, 0); eleList.ForEach(e => { e.State = ElementState.Normal; e.Draw(g); }); } SaveImageByNameAndType(map, set.Id, set.ImageSaveOption.ImageFormat, imgDir); } catch (Exception ex) { LogAsync(DateTime.Now, $"{set.Id}拟合图片保存失败", ex.GetExceptionMessage()); } //finally //{ // set.IsFitSaved = true; //} } set.IsFitSaved = true; ClearImageSet(set); } }); } /// /// 保存特定类型图片,例如NG图片 /// /// 图片类型说明 例如“NG” /// 图片ID public virtual async void SaveSelectedImage(string prefix, string imgSetId) { await Task.Run(() => { IImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId); if (set == null) return; lock (set.SaveLock) { if (set.ImageSaveOption.AddtionalSaveType.Contains(prefix) && !set.IsAddtionalSaved) { string imgDir = CheckImageDirectory(set.ImageSaveOption.ImageSaveSubDirectory, prefix); while (set.Image == null) { Thread.Sleep(50); } try { SaveImageByNameAndType(set.Image, set.Id, set.ImageSaveOption.ImageFormat, imgDir); //set.IsNGSaved = true; } catch (Exception ex) { LogAsync(DateTime.Now, $"{set.Id}{prefix}图片保存失败", ex.GetExceptionMessage()); } //finally //{ // set.IsAddtionalSaved = true; //} } set.IsAddtionalSaved = true; ClearImageSet(set); } }); } protected string CheckImageDirectory(string subDir, string prefix) { if (string.IsNullOrWhiteSpace(subDir)) { subDir = DateTime.Now.ToString("yyyyMMdd"); } else { subDir = Path.Combine(DateTime.Now.ToString("yyyyMMdd"), subDir); } string imgDir = Path.Combine(IConfig.ImgDirectory, subDir, prefix); DirectoryInfo dir = new DirectoryInfo(imgDir); if (!dir.Exists) { dir.Create(); } return imgDir; } protected void SaveImageByNameAndType(Bitmap map, string imageName, ImageFormat imgFormat, string imageDir) { string filePath = Path.Combine(imageDir, $"{imageName}.{imgFormat.ToString().ToLower()}"); map.Save(filePath, imgFormat); } //private Timer clearImageSetTimer = null; public void ClearImageSet(IImageSet set) { try { //if ((!set.ImageSaveOption.IsSaveOriginImage || set.IsOriginSaved) // && (!set.ImageSaveOption.IsSaveFitImage || set.IsFitSaved) // && (!set.ImageSaveOption.IsSaveNGImage || set.IsNGSaved)) if (set.IsOriginSaved && set.IsFitSaved && set.IsAddtionalSaved) { _imageSetList.TryTake(out set); set.Dispose(); //LogAsync(DateTime.Now, $"移除图片信息,当前缓存数量:{_imageSetList.Count}", ""); } //bool flag = false; //do //{ // flag = _imageSetList.TryTake(out set); //} while (!flag); //set.Dispose(); //LogAsync(DateTime.Now, $"移除图片信息,当前缓存数量:{_imageSetList.Count}", ""); } catch (Exception ex) { LogAsync(DateTime.Now, $"清理图片缓存异常,当前缓存数量{_imageSetList.Count}", ex.GetExceptionMessage()); } } public void ClearImageSet(string imgSetId) { IImageSet set = _imageSetList.First(u => u.Id == imgSetId); if (set != null) ClearImageSet(set); } private void ImageSet_OnImageSetTimeout(ImageSet set) { ClearImageSet(set); } //private void ClearImageSetPeriodically() //{ // try // { // DateTime dt = DateTime.Now; // foreach (ImageSet set in _imageSetList) // { // if ((dt - set.InitialTime).TotalSeconds > IMAGESETLIFETIME) // { // ImageSet temp = set; // _imageSetList.TryTake(out temp); // temp.Dispose(); // } // } // } // catch (Exception ex) // { // LogAsync(DateTime.Now, $"定时清理图片缓存异常,当前缓存数量{_imageSetList.Count}", ex.GetExceptionMessage()); // } //} #endregion #region 定时清理图片 static readonly object _clearImageLock = new object(); protected void ClearImage() { if (IConfig.SaveImageDayLimit > 0) { lock (_clearImageLock) { LogAsync(DateTime.Now, $"ClearImage Start", ""); try { DirectoryInfo dirImage = new DirectoryInfo(IConfig.ImgDirectory); if (dirImage.Exists) { DateTime dtNow = DateTime.Now; var folderList = dirImage.GetDirectories().ToList(); folderList = folderList.Where(f => { if (f.Name.Length < 8) return false; DateTime dt = DateTime.Now; string name = f.Name.Substring(0, 4) + "/" + f.Name.Substring(4, 2) + "/" + f.Name.Substring(6); return DateTime.TryParse(name, out dt); }).OrderBy(u => u.Name).ToList(); if (folderList.Count > IConfig.SaveImageDayLimit) { folderList.Take(folderList.Count - IConfig.SaveImageDayLimit).ToList().ForEach(f => { f.Delete(true); }); } } } catch (Exception ex) { LogAsync(DateTime.Now, $"ClearImage异常", ex.GetExceptionMessage()); } LogAsync(DateTime.Now, $"ClearImage Done", ""); } } } private System.Threading.Timer clearImageTimer = null; #endregion #region 图片转换 [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] public static extern void CopyMemory(IntPtr dest, IntPtr src, long count); protected async void Generate8GrayImageByPointer(int width, int height, IntPtr dataPtr, string imgSetId) { await Task.Run(() => { //************************Mono8 转 Bitmap******************************* Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed); Bitmap showImage = new Bitmap(width, height, PixelFormat.Format8bppIndexed); ColorPalette cp = bmp.Palette; for (int i = 0; i < 256; i++) { cp.Entries[i] = Color.FromArgb(i, i, i); } bmp.Palette = cp; ColorPalette cp1 = showImage.Palette; for (int i = 0; i < 256; i++) { cp1.Entries[i] = Color.FromArgb(i, i, i); } showImage.Palette = cp1; long[] ptr = new long[3]; Rectangle rect = new Rectangle(0, 0, width, height); BitmapData bitmapData = bmp.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); BitmapData showImageData = showImage.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); int PixelSize = Bitmap.GetPixelFormatSize(bitmapData.PixelFormat) / 8; if (width % 4 == 0) { CopyMemory(bitmapData.Scan0, dataPtr, width * height * PixelSize); CopyMemory(showImageData.Scan0, dataPtr, width * height * PixelSize); } else { ptr[0] = bitmapData.Scan0.ToInt64(); ptr[1] = showImageData.Scan0.ToInt64(); ptr[2] = (long)dataPtr; for (int i = 0; i < height - 1; i++) { ptr[2] += width; CopyMemory((IntPtr)ptr[0], (IntPtr)ptr[2], width * PixelSize); CopyMemory((IntPtr)ptr[1], (IntPtr)ptr[2], width * PixelSize); ptr[0] += bitmapData.Stride; ptr[1] += showImageData.Stride; } } bmp.UnlockBits(bitmapData); showImage.UnlockBits(showImageData); SaveOriginImage(bmp, showImage, imgSetId); }); } protected async Task Generate16GrayImageByPointer(int width, int height, IntPtr dataPtr, string imgSetId) { await Task.Run(() => { Thread.Sleep(1000); int widthIn4 = (int)Math.Ceiling(width / 4.0) * 4; Bitmap bmp = new Bitmap(widthIn4, height, PixelFormat.Format48bppRgb); Bitmap showImage = new Bitmap(widthIn4, height, PixelFormat.Format48bppRgb); Rectangle rect = new Rectangle(0, 0, widthIn4, height); BitmapData bitmapData = bmp.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format48bppRgb); BitmapData showImageData = showImage.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format48bppRgb); unsafe { byte* data = (byte*)dataPtr; byte* bitmapBuffer = (byte*)bitmapData.Scan0; byte* showBitmapBuffer = (byte*)showImageData.Scan0; //Parallel.For(0, height, i => // { // Parallel.For(0, width, j => // { // showBitmapBuffer[(i * widthIn4 + j) * 6] = bitmapBuffer[(i * widthIn4 + j) * 6] = data[(i * width + j) * 2]; // showBitmapBuffer[(i * widthIn4 + j) * 6 + 1] = bitmapBuffer[(i * widthIn4 + j) * 6 + 1] = data[(i * width + j) * 2 + 1]; // }); // }); Parallel.For(0, width * height, i => { int index = (i + 1) % width + widthIn4 * ((i + 1) / width) - 1; showBitmapBuffer[index * 6] = bitmapBuffer[index * 6] = data[i * 2]; showBitmapBuffer[index * 6 + 1] = bitmapBuffer[index * 6 + 1] = data[i * 2 + 1]; }); } bmp.UnlockBits(bitmapData); showImage.UnlockBits(showImageData); SaveOriginImage(bmp, showImage, imgSetId); }); } #endregion } #region Config [Device("Camera", "相机取像配置", EnumHelper.DeviceAttributeType.OperationConfig)] public class CameraOprerationConfigBase : OperationConfigBase, INotifyPropertyChanged, IComplexDisplay, IHalconToolPath { private float exposure = 0; /// /// 曝光 /// [Category("取像配置")] [Description("曝光")] [DisplayName("曝光")] public virtual float Exposure { get => exposure; set { if (exposure != value) { exposure = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Exposure")); } } } /// /// 增益 /// [Category("取像配置")] [Description("增益")] [DisplayName("增益")] public virtual float Gain { get; set; } [Category("取像配置")] [Description("曝光修改后等待时间,单位ms。针对部分场景确保曝光修改及时生效。")] [DisplayName("曝光等待")] public virtual int ExposureWaitTime { get; set; } [Category("取像配置")] [Description("拍摄后曝光值。可在一次拍摄后将曝光修改为下次拍摄的曝光值,节约曝光生效时间。")] [DisplayName("拍摄后曝光")] public virtual float ExposureAfterSnap { get; set; } [Category("算法配置")] [Description("算法路径")] [DisplayName("算法路径")] [Editor(typeof(FileDialogEditor), typeof(UITypeEditor))] public string AlgorithemPath { get; set; } = ""; [Category("保存设置")] [Description("图片保存配置")] [DisplayName("图片保存配置")] [TypeConverter(typeof(ComplexObjectConvert))] [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))] public ImageSaveOption ImageSaveOption { get; set; } = new ImageSaveOption(); [Category("延时配置")] [Description("拍照前延时")] [DisplayName("前延时")] public int DelayBefore { get; set; } = 0; [Category("延时配置")] [Description("拍照后延时")] [DisplayName("后延时")] public int DelayAfter { get; set; } = 0; public event PropertyChangedEventHandler PropertyChanged; public string GetDisplayText() { return $"曝光:{Exposure.ToString("f2")} 算法:{AlgorithemPath}"; } public List GetHalconToolPathList() { return new List() { AlgorithemPath }; } } public class CameraInitialConfigBase : InitialConfigBase { [Category("驱动类型")] [Description("驱动类型")] [DisplayName("驱动类型")] [Browsable(true)] [TypeConverter(typeof(CameraTypeConverter))] public override string DriverType { get; set; } [Category("相机设置")] [Description("相机序列号")] [DisplayName("序列号")] [TypeConverter(typeof(HalconSerialNumConverter))] public virtual string SerialNum { get; set; } [Category("相机设置")] [Description("相机IP地址")] [DisplayName("相机IP地址")] public virtual string CameraIP { get; set; } [Category("相机设置")] [Description("上位机IP地址")] [DisplayName("上位机IP地址")] public virtual string ComputerIP { get; set; } [Category("保存设置")] [Description("图片保存目录")] [DisplayName("图片保存目录")] [Editor(typeof(FoldDialogEditor), typeof(UITypeEditor))] public virtual string ImgDirectory { get; set; } = @"../Images"; [Category("保存设置")] [Description("图片保存天数")] [DisplayName("图片保存天数")] public virtual int SaveImageDayLimit { get; set; } = 0; [Category("保存设置")] [Description("图片保存默认配置,主要用于硬触发等没有明确操作配置说明的图片保存")] [DisplayName("图片保存默认配置")] [TypeConverter(typeof(ComplexObjectConvert))] [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))] public virtual ImageSaveOption ImageSaveOption { get; set; } = new ImageSaveOption(); [Category("拍摄设置")] [Description("默认曝光值,相机开启后就设置该曝光值")] [DisplayName("默认曝光值")] public virtual float DefaultExposure { get; set; } } #endregion #region Converter & Editor public class HalconSerialNumConverter : ComboBoxItemTypeConvert { public override Hashtable GetConvertHash(ITypeDescriptorContext context) { Hashtable table = new Hashtable(); HTuple deviceList = null; AutoResetEvent handle = new AutoResetEvent(false); Task.Run(() => { HOperatorSet.InfoFramegrabber("GigEVision", "info_boards", out HTuple deviceInfo, out deviceList); handle.Set(); }); handle.WaitOne(5000); if (deviceList != null) { for (int i = 0; i < deviceList.Length; i++) { var list = deviceList[i].S.Split(new char[] { ':', '|' }, StringSplitOptions.RemoveEmptyEntries); table[list[1].Trim()] = list[3].Trim() + "--" + list[1].Trim(); } } return table; } } public class CameraTypeConverter : DeviceTypeConverter { } public class CameraInitialConfigEditor : DeviceInitialConfigEditor { } public class CameraDeviceConverter : ComboBoxItemTypeConvert { public override Hashtable GetConvertHash(ITypeDescriptorContext context) { Hashtable table = new Hashtable(); using (var scope = GlobalVar.Container.BeginLifetimeScope()) { var config = scope.Resolve(); config.CameraConfigCollection.ForEach(camera => { table[camera.Id] = camera.Name; }); } return table; } } #endregion #region 辅助类 public class ImageSaveOption : IComplexDisplay { [Category("图片保存")] [Description("图片保存文件夹子文件夹名称")] [DisplayName("子目录")] public string ImageSaveSubDirectory { get; set; } [Category("图片保存")] [Description("是否保存原始图片")] [DisplayName("保存原始图片")] public bool IsSaveOriginImage { get; set; } = true; [Category("图片保存")] [Description("图片保存格式")] [DisplayName("图片保存格式")] public ImageFormat ImageFormat { get; set; } = ImageFormat.Jpeg; [Category("图片保存")] [Description("是否保存拟合/结果图片")] [DisplayName("保存拟合图片")] public bool IsSaveFitImage { get; set; } = false; //[Category("图片保存")] //[Description("是否保存NG图片")] //[DisplayName("保存NG图片")] //public bool IsSaveNGImage { get; set; } = false; [Category("图片保存")] [Description("附加保存图片类型,多类型使用\",\"间隔。如要保存OK和NG图片,填写\"OK,NG\"")] [DisplayName("附加保存图片类型")] public string AddtionalSaveType { get; set; } = ""; public string GetDisplayText() { //return $"子目录:{ImageSaveSubDirectory}{(IsSaveOriginImage ? " 保存原图" : "")}{(string.IsNullOrWhiteSpace(AddtionalSaveType) ? "" : ("保存"{AddtionalSaveType}"图片"))} {(IsSaveFitImage ? " 保存拟合图片" : "")}"; string txt = ""; if (!string.IsNullOrWhiteSpace(ImageSaveSubDirectory)) { txt += $"子目录:{ImageSaveSubDirectory}"; } if (IsSaveOriginImage) { txt += " 保存原图"; } if (!string.IsNullOrWhiteSpace(AddtionalSaveType)) { txt += $" 保存{AddtionalSaveType}图片"; } if (IsSaveFitImage) { txt += " 保存拟合图片"; } return txt; } } public interface IImageSet : IDisposable { DateTime InitialTime { get; set; } string Id { get; } HImage HImage { get; set; } Bitmap Image { get; set; } object SaveLock { get; set; } ImageSaveOption ImageSaveOption { get; set; } bool IsOriginSaved { get; set; } bool IsFitSaved { get; set; } bool IsAddtionalSaved { get; set; } event Action OnImageSetTimeout; /// /// 序列化操作的图像信息 /// string ImageData { get; set; } } public class ImageSet : IImageSet { public DateTime InitialTime { get; set; } = DateTime.Now; private string id = ""; public string Id { get { if (string.IsNullOrWhiteSpace(id)) { id = InitialTime.ToString("HHmmssfff"); } return id; } } public HImage HImage { get; set; } = null; public Bitmap Image { get; set; } = null; //public List EleList { get; set; } = new List(); //public OutputResult Result { get; set; } = OutputResult.OK; #region Save Option & Result public ImageSaveOption ImageSaveOption { get; set; } = new ImageSaveOption(); public bool IsOriginSaved { get; set; } = false; //public bool IsInvokeSaveFit { get; set; } = false; public bool IsFitSaved { get; set; } = false; //public bool IsInvokeSaveNG { get; set; } = false; public bool IsAddtionalSaved { get; set; } = false; public object SaveLock { get; set; } = new object(); public virtual string ImageData { get; set; } public event Action OnImageSetTimeout; private Timer autoDisposeTimer = null; private void OnAutoDispose(object state) { OnImageSetTimeout?.Invoke(this); } public ImageSet() { autoDisposeTimer = new Timer(OnAutoDispose, null, 10 * 1000, -1); } public virtual void Dispose() { autoDisposeTimer.Change(-1, -1); autoDisposeTimer.Dispose(); Image?.Dispose(); Image = null; HImage?.Dispose(); HImage = null; } #endregion } #endregion }