C#和Halcon混编的多种方法
Halcon的学习过程中,关于Halcon的混合编程是无法避免的,Halcon可以和很多种语言进行混编,这里仅赘述与C#语言进行混编的一些简单方式。
C#与Halcon进行混编的方式大体可以分为:使用Halcon导出功能、面向对象的方式、Halcon引擎。
当然,除了以上方法,还有导出库工程这样的方式可以选择,这里就不再阐述。
一、Halcon导出功能
下图为使用Halcon编写的简单程序
然后点击文件-》导出,或者直接点击如下按钮
点击导出之后,选择语言为C#-Halcon/.NET,选择好导出文件的路径,则会得到一个和Halcon程序同名的.cs文件
打开.cs文件,我们看到导出的代码中有一个action的方法,这个方法是即是我们需要重点关注的部分,我们在Halcon中写的代码都在这个方法中有所实现。以下为action的代码:
1 private void action() 2 { 3 4 5 // Local iconic variables 6 7 HObject ho_Image, ho_Region, ho_ConnectedRegions; 8 HObject ho_RegionDilation; 9 10 // Local control variables 11 12 HTuple hv_Width = null, hv_Height = null; 13 // Initialize local and output iconic variables 14 HOperatorSet.GenEmptyObj(out ho_Image); 15 HOperatorSet.GenEmptyObj(out ho_Region); 16 HOperatorSet.GenEmptyObj(out ho_ConnectedRegions); 17 HOperatorSet.GenEmptyObj(out ho_RegionDilation); 18 ho_Image.Dispose(); 19 HOperatorSet.ReadImage(out ho_Image, "printer_chip/printer_chip_01"); 20 HOperatorSet.GetImageSize(ho_Image, out hv_Width, out hv_Height); 21 if (HDevWindowStack.IsOpen()) 22 { 23 HOperatorSet.SetPart(HDevWindowStack.GetActive(), 0, 0, hv_Height, hv_Width); 24 } 25 ho_Region.Dispose(); 26 HOperatorSet.Threshold(ho_Image, out ho_Region, 128, 255); 27 ho_ConnectedRegions.Dispose(); 28 HOperatorSet.Connection(ho_Region, out ho_ConnectedRegions); 29 ho_RegionDilation.Dispose(); 30 HOperatorSet.DilationCircle(ho_ConnectedRegions, out ho_RegionDilation, 3.5); 31 if (HDevWindowStack.IsOpen()) 32 { 33 HOperatorSet.DispObj(ho_Image, HDevWindowStack.GetActive()); 34 } 35 if (HDevWindowStack.IsOpen()) 36 { 37 HOperatorSet.DispObj(ho_ConnectedRegions, HDevWindowStack.GetActive()); 38 } 39 40 41 ho_Image.Dispose(); 42 ho_Region.Dispose(); 43 ho_ConnectedRegions.Dispose(); 44 ho_RegionDilation.Dispose(); 45 46 }
现在,需要我们对这个代码进行简单的更改,就可以在C#的Halcon窗体控件中将其显示出来,首先,新建一个C#窗体程序,添加halcondotnet的引用,并且添加其命名空间,在主窗体上添加一个Halcon的窗体控件和一个Button控件,并在Button的Click事件对应的方法中添加如下代码即可:
代码如下:
1 namespace Halconprogram 2 { 3 public partial class Form1 : Form 4 { 5 public Form1() 6 { 7 InitializeComponent(); 8 } 9 10 private void button1_Click(object sender, EventArgs e) 11 { 12 13 // Local iconic variables 14 15 HObject ho_Image, ho_Region, ho_ConnectedRegions; 16 HObject ho_RegionDilation; 17 18 // Local control variables 19 20 HTuple hv_Width = null, hv_Height = null; 21 // Initialize local and output iconic variables 22 HOperatorSet.GenEmptyObj(out ho_Image); 23 HOperatorSet.GenEmptyObj(out ho_Region); 24 HOperatorSet.GenEmptyObj(out ho_ConnectedRegions); 25 HOperatorSet.GenEmptyObj(out ho_RegionDilation); 26 ho_Image.Dispose(); 27 HOperatorSet.ReadImage(out ho_Image, "printer_chip/printer_chip_01"); 28 HOperatorSet.GetImageSize(ho_Image, out hv_Width, out hv_Height); 29 30 HOperatorSet.SetPart(hWindowControl1.HalconID, 0, 0, hv_Height, hv_Width); 31 32 ho_Region.Dispose(); 33 HOperatorSet.Threshold(ho_Image, out ho_Region, 128, 255); 34 ho_ConnectedRegions.Dispose(); 35 HOperatorSet.Connection(ho_Region, out ho_ConnectedRegions); 36 ho_RegionDilation.Dispose(); 37 HOperatorSet.DilationCircle(ho_ConnectedRegions, out ho_RegionDilation, 3.5); 38 39 HOperatorSet.DispObj(ho_Image, hWindowControl1.HalconID); 40 41 42 HOperatorSet.DispObj(ho_ConnectedRegions, hWindowControl1.HalconID); 43 44 45 ho_Image.Dispose(); 46 ho_Region.Dispose(); 47 ho_ConnectedRegions.Dispose(); 48 ho_RegionDilation.Dispose(); 49 } 50 } 51 }
比较前两段代码可以发现,只是对Halcon中导出的.cs文件进行了简单的窗体句柄的更改,其他地方没有任何改变,即可实现想要的效果。
需要注意的是,如果Halcon代码中存在外部函数的话,在导出时,除了action方法外,还需要将导出的cs文件中对应外部函数的整个方法实现也要拷贝到自己的工程中。
二、面向对象的方式
使用Halcon程序导出为C#程序之后,可以发现它的可读性并不太好,而且更改起来不太方便,所有的对象都是HObject类型。
以下程序不使用Halcon的IDE,直接添加引用和命名空间后,在C#环境下编写:
1 namespace Halconprogram 2 { 3 public partial class Form1 : Form 4 { 5 public Form1() 6 { 7 InitializeComponent(); 8 } 9 10 private void button1_Click(object sender, EventArgs e) 11 { 12 HImage img = new HImage("printer_chip/printer_chip_01"); 13 img.GetImageSize(out HTuple width, out HTuple height); 14 hWindowControl1.HalconWindow.SetPart(0D, 0, height, width); 15 HRegion region = img.Threshold(128D, 255); 16 HRegion conRegion = region.Connection(); 17 HRegion dilationRegion = conRegion.DilationCircle(3.5); 18 hWindowControl1.HalconWindow.SetColored(12); 19 hWindowControl1.HalconWindow.DispObj(img); 20 hWindowControl1.HalconWindow.DispObj(dilationRegion); 21 22 img.Dispose(); 23 region.Dispose(); 24 conRegion.Dispose(); 25 dilationRegion.Dispose(); 26 } 27 } 28 }
可以看出,使用这种方式同样能达到相同的效果,并且对于图像、区域、XLD都有相关的HImage、HRegion、HXLD类与其对应,看起来更加直观。其实在使用导出功能时,调用Halcon的方法基本使用的都是HOperatorSet这个类,这个类里面包含了Halcon的几乎所有的算子的静态方法,那么为什么Halcon还要提供如HImage、HRegion、HXLD等许多的其他的类呢,我觉得是因为在一些情况下,使用面向对象的方式更容易进行代码封装,而且更容易去修改代码。不过,存在即合理,我觉得在一些代码量较小的Halcon算法中,使用这种方法的确挺好的,可以很好的锻炼自己脱离Halcon的IDE去写Halcon算法的能力,但是面对代码量很大的Halcon算法,个人觉得这不是一个很好好的方式,写起来会比较累,不那么容易调试,前提还是在你对这种方式足够熟悉的基础上。
三、Halcon引擎
在了解Halcon引擎之前,我们最好先了解HDevelop函数文件,即.hdvp后缀的Halcon文件。使用Halcon引擎在C#中进行编程的方式我觉得主要是面向三种Halcon程序:
1.不含自己创建的外部函数的Halcon程序,
这样的程序只需要new一个HDevEngine实例,一个HDevProgram实例,以及一个HDevProgramCall实例(其实可以不用这个实例也可以,为了方便可以new一个),在编写时主要需要注意的是HDev文件的路径,我这里将HDev文件放在了Debug目录下的HDEV文件夹内。
最终效果:
代码:
1 namespace MyProgramCall 2 { 3 public partial class Form1 : Form 4 { 5 6 public Form1() 7 { 8 InitializeComponent(); 9 } 10 HDevEngine hDevEngine = new HDevEngine();//引擎 11 HDevProgram HDevProgram;//Halcon程序 12 HDevProgramCall HDevProgramCall;//Halcon程序执行实例 13 HWindow window;//窗体 14 HDevOpMultiWindowImpl HDevOpMultiWindowImpl;//方便Halcon程序操作显示的实例 15 string exePath = ""; 16 private void btn_Init_Click(object sender, EventArgs e) 17 { 18 window = hWindowControl1.HalconWindow; 19 //通过以下两句,表示整个要执行的Halcon的HDev程序的父窗口(即显示窗口)为当前的窗口控件,如果不添加以下两句,则无法将Halcon程序的显示内容显示在窗体控件上,不过写程序时不建议通过将Halcon程序内的内容通过这种方式显示在控件上,可以直接拿到想要的对象然后再显示比较好 20 //HDevOpMultiWindowImpl = new HDevOpMultiWindowImpl(window); 21 //hDevEngine.SetHDevOperators(HDevOpMultiWindowImpl); 22 HDevProgram = new HDevProgram(exePath + "HDEV\\test.hdev");//实例化一个HDevProgram的实例 23 HDevProgramCall = new HDevProgramCall(HDevProgram);//实例化一个执行Halcon程序的实例 24 MessageBox.Show("初始化成功!!!"); 25 } 26 27 private void Form1_Load(object sender, EventArgs e) 28 { 29 exePath = AppDomain.CurrentDomain.BaseDirectory; 30 } 31 32 private void btn_Execute_Click(object sender, EventArgs e) 33 { 34 try 35 { 36 HDevProgramCall.Execute(); 37 } 38 catch (HDevEngineException Ex) 39 { 40 MessageBox.Show(Ex.Message, "HDevEngine Exception"); 41 return; 42 } 43 44 //建议不使用以上显示的两句,手动显示想要显示的部分 45 HImage img = HDevProgramCall.GetIconicVarImage("Image"); 46 HRegion region = HDevProgramCall.GetIconicVarRegion("RegionDilation"); 47 if(img.IsInitialized()) 48 { 49 img.GetImageSize(out HTuple width, out HTuple height); 50 window.SetPart(0D, 0, height, width); 51 //window.SetColor("green"); 52 window.SetColored(12); 53 window.SetDraw("fill"); 54 window.DispObj(img); 55 window.DispObj(region); 56 } 57 img.Dispose(); 58 } 59 } 60 }
2.一个单一的HDevlop函数文件
如果只是封装好的一个.hdvp后缀的外部函数的文件,同样可以利用Halcon引擎来调用。这里我封装了一个十分简单的加法的Halcon函数文件,在调用时,只需要传入适当的参数,即可调用函数
这个函数有两个输入的控制参数分别为num1和num2,有一个输出的控制参数为a,在C#中建立一个如下的窗口界面:
其中三个textbox分别代表两个输入和一个输出,ADD按钮表示执行这个函数,label2表示通过引擎调用这个函数的消耗时间
效果如下:
代码:
1 namespace hdvpFUc 2 { 3 public partial class Form1 : Form 4 { 5 6 public Form1() 7 { 8 InitializeComponent(); 9 } 10 11 HDevEngine hDevEngine = new HDevEngine();//引擎 12 HDevProcedure HDevProcedure;//Halcon函数 13 Stopwatch sw = new Stopwatch(); 14 string exePath = ""; 15 public void LoadAlgorithm() 16 { 17 string procedurePath = exePath + "HDEV"; 18 hDevEngine.SetProcedurePath(procedurePath);//使用引擎配置函数的路径,参数为这个函数的目录 19 HDevProcedure = new HDevProcedure("add_Num");//new一个Halcon外部函数的实例,参数为函数名 20 HDevProcedureCall hDevProcedureCall = new HDevProcedureCall(HDevProcedure);//new一个函数执行的实例 21 HTuple num1 = new HTuple(int.Parse(textBox1.Text)); 22 HTuple num2 = new HTuple(int.Parse(textBox2.Text)); 23 try 24 { 25 //设置函数参数,这里的参数名要和函数内的参数名一一对应 26 hDevProcedureCall.SetInputCtrlParamTuple("num1", num1); 27 hDevProcedureCall.SetInputCtrlParamTuple("num2", num2); 28 sw.Start(); 29 hDevProcedureCall.Execute();//执行函数 30 sw.Stop(); 31 } 32 catch (HDevEngineException EX) 33 { 34 MessageBox.Show(EX.ToString()); 35 return; 36 } 37 string time = sw.ElapsedMilliseconds.ToString(); 38 label2.Text = time; 39 HTuple a = hDevProcedureCall.GetOutputCtrlParamTuple("a");//获得函数的输出的控制参数”a" 40 textBox3.Text = a.ToString(); 41 } 42 private void button1_Click(object sender, EventArgs e) 43 { 44 LoadAlgorithm(); 45 } 46 47 private void Form1_Load(object sender, EventArgs e) 48 { 49 exePath = AppDomain.CurrentDomain.BaseDirectory; 50 } 51 } 52 }
3.包含外部函数的Halcon程序
个人觉得这个是最常用的情况,使用Halcon引擎的情况下,在Halcon程序写好后,我们往往会对其中的核心代码封装成一个外部函数文件,并设置好输入输出的参数。
其实就是将1、2两步结合在一起即可,想执行整个程序就通过HDevProgramCall的实例调用其Execute方法,并通过这个实例的Getxxxxxx开头的方法获取想得到的控制参数或者图标参数,如果想执行Halcon程序中的某个外部函数,那么就通过HDevProcedureCall的实例,在执行其Execute方法前通过这个实例的Setxxxxx开头的方法传入相关参数,然后执行即可,获取参数的方法和前面一样。这里只通过一个简单的示例说明
路径:
.hdev文件
.hdvp文件
效果
代码:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.IO; 7 using System.Linq; 8 using System.Text; 9 using System.Threading; 10 using System.Threading.Tasks; 11 using System.Windows.Forms; 12 using HalconDotNet; 13 namespace MultiDisplayEngeeni 14 { 15 public partial class Form1 : Form 16 { 17 HDevEngine hDevEngine; 18 HDevProgram hDevProgram; 19 HDevProgramCall hDevProgramCall; 20 HDevProcedure hDevProcedure; 21 HDevProcedureCall hDevProcedureCall; 22 String[] _fileNames; 23 HImage img = new HImage(); 24 HXLDCont contour = new HXLDCont(); 25 HXLD affContour = new HXLD(); 26 HShapeModel HShapeModel = new HShapeModel("board.shm"); 27 int _Index = 0; 28 ManualResetEvent ManualResetEvent = new ManualResetEvent(false); 29 string exePath = ""; 30 public Form1() 31 { 32 InitializeComponent(); 33 } 34 35 private void btn_Init_Click(object sender, EventArgs e) 36 { 37 InitHdevlop(); 38 Action(); 39 btn_Init.Enabled = false; 40 btn_Start.Enabled = true; 41 } 42 43 public void InitHdevlop() 44 { 45 hDevEngine = new HDevEngine(); 46 hDevEngine.SetProcedurePath(@"./HDVP"); 47 hDevProgram = new HDevProgram(@"./HDEV\affine_trans_model.hdev"); 48 hDevProgramCall = new HDevProgramCall(hDevProgram); 49 hDevProcedure = new HDevProcedure(hDevProgram, "affine_trans_find_model"); 50 hDevProcedureCall = new HDevProcedureCall(hDevProcedure); 51 } 52 53 public void Action() 54 { 55 Action ac = new Action(() => { 56 hWindowControl1.HalconWindow.DispImage(img); 57 hWindowControl1.HalconWindow.DispObj(affContour); 58 }); 59 Task.Run(() => 60 { 61 while (true) 62 { 63 try 64 { 65 ManualResetEvent.WaitOne(); 66 img.Dispose(); 67 affContour.Dispose(); 68 string imgPath = exePath + _fileNames[_Index]; 69 img = new HImage(imgPath); 70 img.GetImageSize(out HTuple width, out HTuple height); 71 hWindowControl1.HalconWindow.SetPart(0D, 0, height, width); 72 hDevProcedureCall.SetInputCtrlParamTuple("ModelID", HShapeModel); 73 hDevProcedureCall.SetInputIconicParamObject("Image", img); 74 hDevProcedureCall.SetInputIconicParamObject("ModelContours", contour); 75 hDevProcedureCall.Execute(); 76 affContour = hDevProcedureCall.GetOutputIconicParamXld("ContoursAffineTrans"); 77 Invoke(ac); 78 _Index++; 79 if (_Index == _fileNames.Length - 1) 80 { 81 _Index = 0; 82 } 83 Thread.Sleep(10); 84 } 85 catch (Exception) 86 { 87 return; 88 } 89 } 90 }); 91 } 92 93 private void Form1_Load(object sender, EventArgs e) 94 { 95 exePath = AppDomain.CurrentDomain.BaseDirectory; 96 _fileNames = Directory.GetFiles("Images"); 97 hWindowControl1.HalconWindow.SetColor("blue"); 98 contour.ReadObject("contours.hobj"); 99 btn_Init.Enabled = true; 100 btn_Start.Enabled = false; 101 btn_Stop.Enabled = false; 102 } 103 104 private void btn_Start_Click(object sender, EventArgs e) 105 { 106 btn_Start.Enabled = false; 107 btn_Stop.Enabled = true; 108 ManualResetEvent.Set(); 109 } 110 111 private void btn_Stop_Click(object sender, EventArgs e) 112 { 113 btn_Start.Enabled = true; 114 btn_Stop.Enabled = false; 115 ManualResetEvent.Reset(); 116 } 117 } 118 }
在使用Halcon引擎时,个人觉得需要注意的是路径的配置(最好使用相对路径,方便程序移植),以及传参和获取参数时要保持和Halcon程序内部的参数名保持一致。
最后,做个小总结,关于具体使用哪种方法较好,我觉得因人而异,因为每种方法都可以实现相同的效果,并且各有优缺点
1.导出比较方便快捷,但是其不易封装和修改,可读性差。
2.面向对象的方式易封装和修改,并且完全可以脱离Halcon的IDE,但是它要求比较高的熟练度,而且面对较为复杂的程序和较多的代码量,这样的效率比较低。
3.使用Halcon引擎在代码修改方面十分方便,只需要在Halcon的IDE中修改好保存即可,并且也不存在可读性差的问题,无论是较为复杂的程序还是很简单的程序都是适用的,但是它比较依赖Halcon的软件,在移植的时候,Halcon的程序和外部函数文件也要一同进行移植。
以上所有内容仅为个人看法,如有不当,欢迎指正!
原文地址:https://www.cnblogs.com/Nq1996/p/13410567.html
推荐阅读
-
贪婪算法在 Python、JavaScript、Java、C++ 和 C# 中的多种实现及其在硬币变化、分数骑士、活动选择和使用哈夫曼编码的最小生成树问题中的应用实例
-
c# ListView 控件的常用属性、方法和事件
-
气泡排序(超级详细)--升序",从小到大;另一种是 "降序",从大到小。该主题可抽象为 "按升序对 n 个数字排序 "的一般形式。 排序是一种重要的基本算法。排序的方法有很多种,但在本题中我们将使用冒泡排序法。 冒泡法的基本思想 冒泡法的基本思想是,每次比较相邻的两个数字时,较小的那个会被移到前面。如果有 5 个数字9,8,5,2,0,第一次将前两个数字 8 和 9 互换。第二次将第二个和第三个数字(9 和 5)对调......这样一共对调 4 次,得到 8-5-2-0-9 的顺序,可以看到:最大的数字 9 一直在 "下沉",成为最下面的一个数字,而小的数字 "上升" 最小的数字 "上升"。最小的数字 0 已经向上 "浮 "了一个位置。经过第一次比较(共 4 次比较和交换),得到了最大的数字 9。 然后进行第二趟比较,对剩下的前 4 个数字(8、5、2、0)进行新一轮比较,这样第二个最大的数字就 "沉到了底部"。同样,按照上述方法进行第二轮比较。经过 3 次比较和交换,我们得到了第二大数 8。 按照这个规律,我们可以推断出,比较 5 个数字需要 4 次旅行,才能将 5 个数字从小到大排列起来。在第一次旅行中,两个数字之间进行了 4 次比较,在第二次旅行中,进行了 3 次比较......在第四次旅行中,只进行了一次比较。 思路总结 总结:如果有 n 个数字,那么要进行 n-1 次比较。在第一次行程中进行 n-1 次比较,在第 i 次行程中进行 n-i 次比较。
-
C# 等待的常用方法和扩展方法
-
用 ggplot2 拟合回归模型和绘制回归曲线的多种方法
-
紧急模式问题处理 - 图 1 紧急模式 根本原因分析 应急模式提供了尽可能小的环境,即使无法进入应急模式,也可以在其中修复系统。在应急模式下,系统只安装根文件系统供读取,不尝试安装任何其他本地文件系统,不激活网络接口,只启动一些基本服务。 进入应急模式的原因通常是 /etc/fstab 文件中存在错误,导致文件系统挂载失败。 文件系统中存在错误,导致。 约束和限制 本节适用于 Linux 操作系统紧急模式。程序涉及修复文件系统。修复文件系统有丢失数据的风险,因此请先备份数据,然后再执行修复操作。 处理方法 输入根密码,然后进入修复模式。 在应急模式下,根分区以只读模式挂载。要修改根目录中的文件,需要执行以下命令以读写模式重新挂载根分区。# mount -o rw,remount / 请执行以下命令首先检查 fstab 文件是否有误,然后尝试挂载所有未挂载的文件系统。# mount -a 如果挂载点不存在,请创建一个挂载点。 如果不存在此类设备,请注释或删除挂载行。 如果指定了不正确的挂载选项,请将挂载参数更改为正确的参数。 如果没有发生错误,但出现 UNEXPECTED INCONSISTENCY;RUN fsck MANUALLY 消息(通常是由文件系统错误引起的),请跳至第 7 步。 执行以下命令打开 /etc/fstab 以修改相应的错误。# vi /etc/fstab /etc/fstab 文件包含以下字段,以空格分隔:[文件系统] [dir] [type] [options] [dump] [fsck] 表 1 /etc/fstab 参数 说明 参数 说明 [文件系统] 要挂载的分区或存储设备。 文件系统]列建议以 UUID 的形式写入。执行 blkid 命令可查询设备文件系统 UUID。 参考格式如下: # <device> <dir> <type> <options> <dump> <fsck>; UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 使用 UUID 的好处是,它们与磁盘顺序无关。如果你在 BIOS 中更改了存储设备的顺序,或重新插入了存储设备,或者因为某些 BIOS 可能会随机更改存储设备的顺序,那么使用 UUID 会更有效率。 [文件系统] 文件系统]的挂载位置。 类型 挂载设备或分区的文件系统类型,支持多种不同的文件系统:ext2、ext3、ext4、reiserfs、xfs、jfs、smbfs、iso9660、vfat、ntfs、swap 和 auto。 设置为自动类型后,挂载命令会猜测所使用的文件系统类型,这对 CDROM 和 DVD 等移动设备非常有用。 选项 挂载时要使用的参数,有些参数是特定文件系统特有的。例如,默认值参数使用文件系统的默认挂载参数,ext4 的默认参数为:rw、suid、dev、exec、auto、nouser、async。 有关更多参数,请执行以下命令查看 man 手册:# man mount
-
旷视天元开源图像比对工具 MegSpot,助力图像算法研发 - 1.多样化图像比对:可提供叠加比对、拖拽比对等多种比对方式,支持缩放、移动等同步操作,并可生成 GIF 保存比对结果。2. 2.专业呈现:支持像素级图像查看、图像直方图、RGB 查看;支持预览亮度、对比度、饱和度、灰度等指标。3. 视频对比:Cognizant Megapixel 可提供多种图像对比方法,如拖放对比等。 3.视频对比:除了支持视屏的所有图像对比功能外,CCTV MegSpot 还支持同步回放、回放暂停和快进、回放速度设置等功能。 4.跨平台支持:CCTV MegSpot 提供对 Mac、Linux 和 Windows 系统的跨平台支持,借助 Electron 框架,可以低成本完成跨平台应用的开发,并保证各平台体验的一致性。 此外,央视网MegSpot支持跨平台自动更新和数据持久化,确保用户体验的连续性,并支持中、英、日三种语言:MegSpot为大尺寸图像文件的对比提供了本地解决方案。 MegSpot 是一种用于比较大型图像文件的本地解决方案。
-
Python 2D 矩阵 行和列转置的多种实现方法
-
@Validated和@Valid区别-1.分组 @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。没有添加分组属性时,默认验证没有分组的验证属性。 伪代码如下: public interface First{ } public interface Second{ } public class UserModel { @NotNull(message = "{id.empty}", groups = { First.class }) private int id; @NotNull(message = "{username.empty}", groups = { First.class, Second.class }) private String username; @NotNull(message = "{content.empty}", groups = { First.class, Second.class }) private String content; } public String save(@Validated( { Second.class }) UserModel userModel, BindingResult result) { if (result.hasErrors) { return "validate/error"; } return "redirect:/success"; } 对一个参数需要多种验证方式时,也可通过分配不同的组达到目的。例: @NotEmpty(groups = { First.class }) @Size(min = 3, max = 8, groups = { Second.class }) private String name; 分组还支持组序列 默认情况下,不同组别的约束验证是无序的,然而在某些情况下,约束验证的顺序却很重要,如下面两个例子:(1)第二个组中的约束验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。(2)某个组的验证比较耗时,CPU 和内存的使用率相对比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念。 一个组可以定义为其他组的序列,使用它进行验证的时候必须符合该序列规定的顺序。在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。 public interface GroupA { } public interface GroupB { } @GroupSequence( { GroupA.class, GroupB.class }) public interface Group { } public @ResponseBody String addPeople(@Validated({Group.class}) People p,BindingResult result) { if(result.hasErrors) { return "0"; } return "1"; } @Valid:作为标准JSR-303规范,还没有吸收分组的功能。 2.注解地方 @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上 @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上 两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。 3.嵌套验证 在比较两者嵌套验证时,先说明下什么叫做嵌套验证。 比如我们现在有个实体叫做Item: public class Item { @NotNull(message = "id不能为空") @Min(value = 1, message = "id必须为正整数") private Long id; @NotNull(message = "props不能为空") @Size(min = 1, message = "至少要有一个属性") private List<Prop> props; } Item带有很多属性,属性里面有:pid、vid、pidName和vidName,如下所示: public class Prop { @NotNull(message = "pid不能为空") @Min(value = 1, message = "pid必须为正整数") private Long pid; @NotNull(message = "vid不能为空") @Min(value = 1, message = "vid必须为正整数") private Long vid; @NotBlank(message = "pidName不能为空") private String pidName; @NotBlank(message = "vidName不能为空") private String vidName; } 属性这个实体也有自己的验证机制,比如pid和vid不能为空,pidName和vidName不能为空等。 现在我们有个ItemController接受一个Item的入参,想要对Item进行验证,如下所示: @RestController public class ItemController { @RequestMapping("/item/add") public void addItem(@Validated Item item, BindingResult bindingResult) { doSomething; } } 在上图中,如果Item实体的props属性不额外加注释,只有@NotNull和@Size,无论入参采用@Validated还是@Valid验证,Spring Validation框架只会对Item的id和props做非空和数量验证,不会对props字段里的Prop实体进行字段验证,也就是@Validated和@Valid加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List中有Prop的pid为空或者是负数,入参验证不会检测出来。 为了能够进行嵌套验证,必须手动在Item实体的props字段上明确指出这个字段里面的实体也要进行验证。由于@Validated不能用在成员属性(字段)上,但是@Valid能加在成员属性(字段)上,而且@Valid类注解上也说明了它支持嵌套验证功能,那么我们能够推断出:@Valid加在方法参数时并不能够自动进行嵌套验证,而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated或@Valid来进行嵌套验证。 我们修改Item类如下所示: public class Item { @NotNull(message = "id不能为空") @Min(value = 1, message = "id必须为正整数") private Long id; @Valid // 嵌套验证必须用@Valid @NotNull(message = "props不能为空") @Size(min = 1, message = "props至少要有一个自定义属性") private List<Prop> props; } 然后我们在ItemController的addItem函数上再使用@Validated或者@Valid,就能对Item的入参进行嵌套验证。此时Item里面的props如果含有Prop的相应字段为空的情况,Spring Validation框架就会检测出来,bindingResult就会记录相应的错误。 总结一下@Validated和@Valid在嵌套验证功能上的区别:
-
windows下进程间通信的(13种方法)-摘 要 本文讨论了进程间通信与应用程序间通信的含义及相应的实现技术,并对这些技术的原理、特性等进行了深入的分析和比较。 ---- 关键词 信号 管道 消息队列 共享存储段 信号灯 远程过程调用 Socket套接字 MQSeries 1 引言 ---- 进程间通信的主要目的是实现同一计算机系统内部的相互协作的进程之间的数据共享与信息交换,由于这些进程处于同一软件和硬件环境下,利用操作系统提供的的编程接口,用户可以方便地在程序中实现这种通信;应用程序间通信的主要目的是实现不同计算机系统中的相互协作的应用程序之间的数据共享与信息交换,由于应用程序分别运行在不同计算机系统中,它们之间要通过网络之间的协议才能实现数据共享与信息交换。进程间通信和应用程序间通信及相应的实现技术有许多相同之处,也各有自己的特色。即使是同一类型的通信也有多种的实现方法,以适应不同情况的需要。 ---- 为了充分认识和掌握这两种通信及相应的实现技术,本文将就以下几个方面对这两种通信进行深入的讨论:问题的由来、解决问题的策略和方法、每种方法的工作原理和实现、每种实现方法的特点和适用的范围等。 2 进程间的通信及其实现技术 ---- 用户提交给计算机的任务最终都是通过一个个的进程来完成的。在一组并发进程中的任何两个进程之间,如果都不存在公共变量,则称该组进程为不相交的。在不相交的进程组中,每个进程都独立于其它进程,它的运行环境与顺序程序一样,而且它的运行环境也不为别的进程所改变。运行的结果是确定的,不会发生与时间相关的错误。 ---- 但是,在实际中,并发进程的各个进程之间并不是完全互相独立的,它们之间往往存在着相互制约的关系。进程之间的相互制约关系表现为两种方式: ---- (1) 间接相互制约:共享CPU ---- (2) 直接相互制约:竞争和协作 ---- 竞争——进程对共享资源的竞争。为保证进程互斥地访问共享资源,各进程必须互斥地进入各自的临界段。 ---- 协作——进程之间交换数据。为完成一个共同任务而同时运行的一组进程称为同组进程,它们之间必须交换数据,以达到协作完成任务的目的,交换数据可以通知对方可以做某事或者委托对方做某事。 ---- 共享CPU问题由操作系统的进程调度来实现,进程间的竞争和协作由进程间的通信来完成。进程间的通信一般由操作系统提供编程接口,由程序员在程序中实现。UNIX在这个方面可以说最具特色,它提供了一整套进程间的数据共享与信息交换的处理方法——进程通信机制(IPC)。因此,我们就以UNIX为例来分析进程间通信的各种实现技术。 ---- 在UNIX中,文件(File)、信号(Signal)、无名管道(Unnamed Pipes)、有名管道(FIFOs)是传统IPC功能;新的IPC功能包括消息队列(Message queues)、共享存储段(Shared memory segment)和信号灯(Semapores)。 ---- (1) 信号 ---- 信号机制是UNIX为进程中断处理而设置的。它只是一组预定义的值,因此不能用于信息交换,仅用于进程中断控制。例如在发生浮点错、非法内存访问、执行无效指令、某些按键(如ctrl-c、del等)等都会产生一个信号,操作系统就会调用有关的系统调用或用户定义的处理过程来处理。 ---- 信号处理的系统调用是signal,调用形式是: ---- signal(signalno,action) ---- 其中,signalno是规定信号编号的值,action指明当特定的信号发生时所执行的动作。 ---- (2) 无名管道和有名管道 ---- 无名管道实际上是内存中的一个临时存储区,它由系统安全控制,并且独立于创建它的进程的内存区。管道对数据采用先进先出方式管理,并严格按顺序操作,例如不能对管道进行搜索,管道中的信息只能读一次。 ---- 无名管道只能用于两个相互协作的进程之间的通信,并且访问无名管道的进程必须有共同的祖先。 ---- 系统提供了许多标准管道库函数,如: pipe——打开一个可以读写的管道; close——关闭相应的管道; read——从管道中读取字符; write——向管道中写入字符; ---- 有名管道的操作和无名管道类似,不同的地方在于使用有名管道的进程不需要具有共同的祖先,其它进程,只要知道该管道的名字,就可以访问它。管道非常适合进程之间快速交换信息。 ---- (3) 消息队列(MQ) ---- 消息队列是内存中独立于生成它的进程的一段存储区,一旦创建消息队列,任何进程,只要具有正确的的访问权限,都可以访问消息队列,消息队列非常适合于在进程间交换短信息。 ---- 消息队列的每条消息由类型编号来分类,这样接收进程可以选择读取特定的消息类型——这一点与管道不同。消息队列在创建后将一直存在,直到使用msgctl系统调用或iqcrm -q命令删除它为止。 ---- 系统提供了许多有关创建、使用和管理消息队列的系统调用,如: ---- int msgget(key,flag)——创建一个具有flag权限的MQ及其相应的结构,并返回一个唯一的正整数msqid(MQ的标识符); ---- int msgsnd(msqid,msgp,msgsz,msgtyp,flag)——向队列中发送信息; ---- int msgrcv(msqid,cmd,buf)——从队列中接收信息; ---- int msgctl(msqid,cmd,buf)——对MQ的控制操作; ---- (4) 共享存储段(SM) ---- 共享存储段是主存的一部分,它由一个或多个独立的进程共享。各进程的数据段与共享存储段相关联,对每个进程来说,共享存储段有不同的虚拟地址。系统提供的有关SM的系统调用有: ---- int shmget(key,size,flag)——创建大小为size的SM段,其相应的数据结构名为key,并返回共享内存区的标识符shmid; ---- char shmat(shmid,address,flag)——将当前进程数据段的地址赋给shmget所返回的名为shmid的SM段; ---- int shmdr(address)——从进程地址空间删除SM段; ---- int shmctl (shmid,cmd,buf)——对SM的控制操作; ---- SM的大小只受主存限制,SM段的访问及进程间的信息交换可以通过同步读写来完成。同步通常由信号灯来实现。SM非常适合进程之间大量数据的共享。 ---- (5) 信号灯 ---- 在UNIX中,信号灯是一组进程共享的数据结构,当几个进程竞争同一资源时(文件、共享内存或消息队列等),它们的操作便由信号灯来同步,以防止互相干扰。 ---- 信号灯保证了某一时刻只有一个进程访问某一临界资源,所有请求该资源的其它进程都将被挂起,一旦该资源得到释放,系统才允许其它进程访问该资源。信号灯通常配对使用,以便实现资源的加锁和解锁。 ---- 进程间通信的实现技术的特点是:操作系统提供实现机制和编程接口,由用户在程序中实现,保证进程间可以进行快速的信息交换和大量数据的共享。但是,上述方式主要适合在同一台计算机系统内部的进程之间的通信。 3 应用程序间的通信及其实现技术 ---- 同进程之间的相互制约一样,不同的应用程序之间也存在竞争和协作的关系。UNIX操作系统也提供一些可用于应用程序之间实现数据共享与信息交换的编程接口,程序员可以通过自己编程来实现。如远程过程调用和基于TCP/IP协议的套接字(Socket)编程。但是,相对普通程序员来说,它们涉及的技术比较深,编程也比较复杂,实现起来困难较大。 ---- 于是,一种新的技术应运而生——通过将有关通信的细节完全掩盖在某个独立软件内部,即底层的通讯工作和相应的维护管理工作由该软件内部来实现,用户只需要将通信任务提交给该软件去完成,而不必理会它的具体工作过程——这就是所谓的中间件技术。 ---- 我们在这里分别讨论这三种常用的应用程序间通信的实现技术——远程过程调用、会话编程技术和MQSeries消息队列技术。其中远程过程调用和会话编程属于比较低级的方式,程序员参与的程度较深,而MQSeries消息队列则属于比较高级的方式,即中间件方式,程序员参与的程度较浅。 ---- 4.1 远程过程调用(RPC)