首先吐槽一句,官方的demos寫的真的不好,坑爹啊。對於小白來說,開發官方demos為我所用太難了。為什么呢?因為它Dalsa的DALSA.SaperaLT.SapClassBasic.dll中,不僅有采圖的代碼庫,還有用於顯示的UI庫(它不是用Winform的PictureBox顯示圖片,而是用它自家的UI顯示圖片),demos把采圖程序和UI庫雜糅在一起,而且隱藏了少部分細節。
后來我在網上狂搜資料,搜到了兩個大佬的兩篇好文章:
dalsa 8k線陣網口相機c#開發
https://blog.csdn.net/baidu_30028771/article/details/64628784
DALSA相機SDK不完全教程
文章一的代碼是一個完整的例子,是可以直接采到圖的。文章二的代碼缺少關鍵的GetCameraInfo()方法,是不能直接運行的,但是這篇文章的講解更全面、深入,可以說兩篇都是必備的啦。
我為什么要寫這兩篇文章呢?因為我想集合這兩家之長,再加入一點自己的經驗、代碼,並且提供完整的源代碼方便大家開發。很懺愧 ,只做了一點微小的貢獻。
我的開發硬件、軟件信息:
操作系統:windows 10、windows 7 64bit都有
線掃相機:Dalsa Linea Mono 4k 26 kHz GigE (LA-GM-04K08A)
IDE :Visual studio 2013
第一篇文章我直接運行代碼報錯了,是到了跟Dalsa相關的dll的語句時報錯的。Win 10系統很扯淡,報錯的提示一點都看不懂,我換Win 7的系統后,也報錯,但是明確把錯誤原因找出來了。其實是Dalsa的dll中有低版本.Net Framework的代碼,導致不兼容。解決的辦法,網上一大推,核心就一句:在app.config的合適位置,加這句話useLegacyV2RuntimeActivationPolicy=”true”。
如果沒有app.config文件,你就需要創建這個文件。
我的app.config文件內容如下:
<?xml version="1.0" encoding="utf-8"?> <configuration> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> </startup> </configuration>
現在你需要把我上文提到的兩篇博客仔細看一下了。
……
看完了嗎?看完了的話,接着往下看我的文章。
首先學習一下相機配置文件(.ccf)如何生成:
① 打開相機軟件Sapera CamExpert,確保相機已經正常工作,然后自己改變到合適的參數;
② 點擊軟件左上角的File——Save As...,選擇文件夾路徑,修改文件名。
我的解決方案資源結構如下:
因為我是用Halcon顯示圖片,因此我添加了兩個dll引用。除了app.config之外,我所有自己編寫的代碼全部在Form1.cs中。其實相當於我把全部源代碼一字不漏全告訴你了。
我的Form1界面如下:
(點擊Init初始化,會彈出該線陣相機的型號)
然后點擊“snap”的話,它會連續采集15張圖,並保存,如下:
Form1.cs的全部內容如下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using DALSA.SaperaLT.SapClassBasic; 10 using HalconDotNet; 11 12 namespace WinDalsa 13 { 14 public partial class Form1 : Form 15 { 16 private SapLocation m_ServerLocation; // 設備的連接地址 17 private SapAcqDevice m_AcqDevice; // 采集設備 18 private SapBuffer m_Buffers; // 緩存對象 19 private SapAcqDeviceToBuf m_Xfer; // 傳輸對象 20 21 public Form1() 22 { 23 InitializeComponent(); 24 } 25 26 27 private void Form1_Load(object sender, EventArgs e) 28 { 29 30 } 31 32 private void Form1_FormClosed(object sender, FormClosedEventArgs e) 33 { 34 DestroyObjects(); 35 DisposeObjects(); 36 } 37 38 39 private void btn_init_Click(object sender, EventArgs e) 40 { 41 CreateNewObjects(); 42 } 43 44 private void btn_setting_Click(object sender, EventArgs e) 45 { 46 //設置曝光值,為了設置的值不超限,需要獲取曝光值的允許范圍(主要是最大值) 47 double valuetemp = GetMaxValue("ExposureTime"); 48 if (valuetemp > 0) 49 { 50 m_AcqDevice.SetFeatureValue("ExposureTime", valuetemp); 51 } 52 53 m_AcqDevice.SetFeatureValue("Gain", "9.9"); 54 } 55 56 57 // 獲得相機部分屬性的值 58 private void btn_getValue_Click(object sender, EventArgs e) 59 { 60 string deviceModelName; 61 string deviceUserId; 62 string pixelFormat; 63 string triggerMode; 64 65 double acquisitionLineRate; //行頻和曝光時間不能設置為int類型 66 double exposureTime; 67 double gain; 68 int width; 69 int height; 70 int sensorWidth; 71 int sensorHeight; 72 73 74 m_AcqDevice.GetFeatureValue("DeviceModelName", out deviceModelName); //Linea M4096-7um 75 m_AcqDevice.GetFeatureValue("DeviceUserID", out deviceUserId); //空 76 m_AcqDevice.GetFeatureValue("PixelFormat", out pixelFormat); //Mono8 77 m_AcqDevice.GetFeatureValue("TriggerMode", out triggerMode); //Off 78 79 m_AcqDevice.GetFeatureValue("AcquisitionLineRate", out acquisitionLineRate); //10000.0 80 m_AcqDevice.GetFeatureValue("ExposureTime", out exposureTime); //70.0 81 m_AcqDevice.GetFeatureValue("Gain", out gain); //9.0 82 m_AcqDevice.GetFeatureValue("Width", out width); //4096 83 m_AcqDevice.GetFeatureValue("Height", out height); //2800 84 m_AcqDevice.GetFeatureValue("SensorWidth", out sensorWidth); //4096 85 m_AcqDevice.GetFeatureValue("SensorHeight", out sensorHeight); //1 86 87 } 88 89 90 #region 單步采集、連續采集、凍結采集 91 private void btn_snap_Click(object sender, EventArgs e) 92 { 93 94 //Snap()只采集一張,如果是Snap(15)則連續采集15張 95 m_Xfer.Snap(15);//m_Xfer.Snap(m_Buffers.Count) 96 } 97 98 private void btn_grab_Click(object sender, EventArgs e) 99 { 100 m_Xfer.Grab(); 101 } 102 103 private void btn_freeze_Click(object sender, EventArgs e) 104 { 105 m_Xfer.Freeze(); //還有m_Xfer.Abort()的用法; 106 107 } 108 109 #endregion 110 111 112 113 //得到所有連接的相機信息,並將它們加入到ArrayList里面去 114 public bool GetCameraInfo(out string sCameraName, out int nIndex) 115 { 116 sCameraName = ""; 117 nIndex = 0; 118 119 int serverCount = SapManager.GetServerCount(); 120 int GenieIndex = 0; 121 System.Collections.ArrayList listServerNames = new System.Collections.ArrayList(); 122 123 bool bFind = false; 124 string serverName = ""; 125 for (int serverIndex = 0; serverIndex < serverCount; serverIndex++) 126 { 127 if (SapManager.GetResourceCount(serverIndex, SapManager.ResourceType.AcqDevice) != 0) 128 { 129 serverName = SapManager.GetServerName(serverIndex); 130 listServerNames.Add(serverName); 131 GenieIndex++; 132 bFind = true; 133 } 134 } 135 136 int count = 1; 137 string deviceName = ""; 138 foreach (string sName in listServerNames) 139 { 140 deviceName = SapManager.GetResourceName(sName, SapManager.ResourceType.AcqDevice, 0); 141 count++; 142 } 143 144 sCameraName = serverName; 145 nIndex = GenieIndex; 146 147 return bFind; 148 } 149 150 151 152 private bool CreateNewObjects() 153 { 154 155 string Name; 156 int Index; 157 bool RTemp = GetCameraInfo(out Name, out Index); 158 if (RTemp) 159 { 160 MessageBox.Show(Name); 161 } 162 else 163 { 164 MessageBox.Show("Get camera info false!"); 165 return false; 166 } 167 168 169 m_ServerLocation = new SapLocation(Name, 0); 170 171 172 // 創建采集設備,new SapAcqDevice()的括號中第二個參數既可以寫配置文件路徑,也可以寫false,猜測false是用相機當前的設置 173 //m_AcqDevice = new SapAcqDevice(m_ServerLocation, false); 174 m_AcqDevice = new SapAcqDevice(m_ServerLocation, @"C:\Users\xh6300\Desktop\dalsa_win7_develop\gray\T_Linea_M4096-7um_Default_Default_2800.ccf"); 175 176 177 if (m_AcqDevice.Create() == false) 178 { 179 DestroyObjects(); 180 DisposeObjects(); 181 return false; 182 } 183 // 創建緩存對象 184 if (SapBuffer.IsBufferTypeSupported(m_ServerLocation, SapBuffer.MemoryType.ScatterGather)) 185 { 186 m_Buffers = new SapBufferWithTrash(2, m_AcqDevice, SapBuffer.MemoryType.ScatterGather); 187 } 188 else 189 { 190 m_Buffers = new SapBufferWithTrash(2, m_AcqDevice, SapBuffer.MemoryType.ScatterGatherPhysical); 191 } 192 if (m_Buffers.Create() == false) 193 { 194 DestroyObjects(); 195 DisposeObjects(); 196 return false; 197 } 198 199 m_AcqDevice.SetFeatureValue("AcquisitionLineRate", 20000.0); //注意:行頻在相機工作時不能設置(曝光、增益可以),最好在初始化階段設置 200 201 // 創建傳輸對象 202 m_Xfer = new SapAcqDeviceToBuf(m_AcqDevice, m_Buffers); 203 m_Xfer.XferNotify += new SapXferNotifyHandler(m_Xfer_XferNotify); 204 m_Xfer.XferNotifyContext = this; 205 m_Xfer.Pairs[0].EventType = SapXferPair.XferEventType.EndOfFrame; 206 m_Xfer.Pairs[0].Cycle = SapXferPair.CycleMode.NextWithTrash; 207 if (m_Xfer.Pairs[0].Cycle != SapXferPair.CycleMode.NextWithTrash) 208 { 209 DestroyObjects(); 210 DisposeObjects(); 211 return false; 212 } 213 if (m_Xfer.Create() == false) 214 { 215 DestroyObjects(); 216 DisposeObjects(); 217 return false; 218 } 219 return true; 220 } 221 222 223 private void DestroyObjects() 224 { 225 if (m_Xfer != null && m_Xfer.Initialized) 226 m_Xfer.Destroy(); 227 if (m_Buffers != null && m_Buffers.Initialized) 228 m_Buffers.Destroy(); 229 if (m_AcqDevice != null && m_AcqDevice.Initialized) 230 m_AcqDevice.Destroy(); 231 } 232 233 private void DisposeObjects() 234 { 235 if (m_Xfer != null) 236 { m_Xfer.Dispose(); m_Xfer = null; } 237 if (m_Buffers != null) 238 { m_Buffers.Dispose(); m_Buffers = null; } 239 if (m_AcqDevice != null) 240 { m_AcqDevice.Dispose(); m_AcqDevice = null; } 241 } 242 243 244 private static int picCountNum = 0; //統計采集了多少張圖,有利於理解m_Xfer.Snap(15)中15的意思 245 246 void m_Xfer_XferNotify(object sender, SapXferNotifyEventArgs argsNotify) 247 { 248 //首先需判斷此幀是否是廢棄幀,若是則立即返回,等待下一幀(但這句話似乎有時候m_Xfer.Snap(n)時會導致丟幀,可以注釋掉試試) 249 if (argsNotify.Trash) return; 250 251 //獲取m_Buffers的地址(指針),只要知道了圖片內存的地址,其實就能有各種辦法搞出圖片了(例如轉成Bitmap) 252 IntPtr addr; 253 m_Buffers.GetAddress(out addr); 254 255 //觀察buffer中的圖片的一些屬性值,語句后注釋里面的值是可能的值 256 int count = m_Buffers.Count; //2 257 SapFormat format = m_Buffers.Format; //Uint8 258 double rate = m_Buffers.FrameRate; //30.0,連續采集時,這個值會動態變化 259 int height = m_Buffers.Height; //2800 260 int weight = m_Buffers.Width; //4096 261 int pixd = m_Buffers.PixelDepth; //8 262 263 //顯示實時幀率 264 UpdateFrameRate(); 265 lbl_FrameRate.BeginInvoke(new Action(() => { lbl_FrameRate.Text = m_Buffers.FrameRate.ToString();})); 266 267 //利用halcon從內存中采集圖片並保存 268 HObject ImageTemp = null; 269 HOperatorSet.GenImage1(out ImageTemp, "byte", 4096, 2000, addr);//取內存數據,生成圖像,halcon實現 270 271 hWindowControl1.HalconWindow.SetPart(0, 0, 2000, 4096); 272 HOperatorSet.DispObj(ImageTemp, hWindowControl1.HalconWindow); 273 picCountNum++; 274 HOperatorSet.WriteImage(ImageTemp, "bmp", 0, "C:\\Users\\xh6300\\Desktop\\tt\\" + picCountNum); 275 276 } 277 278 279 private void UpdateFrameRate() 280 { 281 if (m_Xfer.UpdateFrameRateStatistics()) 282 { 283 float framerate = 0.0f; 284 SapXferFrameRateInfo stats = m_Xfer.FrameRateStatistics; 285 286 if (stats.IsBufferFrameRateAvailable) 287 framerate = stats.BufferFrameRate; 288 else if (stats.IsLiveFrameRateAvailable && !stats.IsLiveFrameRateStalled) 289 framerate = stats.LiveFrameRate; 290 291 m_Buffers.FrameRate = framerate; 292 } 293 } 294 295 296 297 //獲得相機參數的最大值(行頻和曝光時間是近似倒數的關系,獲得參數最大值可以防止設置參數超限) 298 private double GetMaxValue(string featureName) 299 { 300 SapFeature feature = new SapFeature(m_ServerLocation); 301 if (!feature.Create()) 302 return -1; 303 if (!m_AcqDevice.GetFeatureInfo(featureName, feature)) 304 return -1; 305 double maxValue = 0; 306 if (!feature.GetValueMax(out maxValue)) 307 return -1; 308 return maxValue; 309 } 310 311 312 //這個一般用得少,最小值一般是很小的數(比如Gain最小0.125,width最小128),我們一般不會設置這樣的數 313 private double GetMinValue(string featureName) 314 { 315 SapFeature feature = new SapFeature(m_ServerLocation); 316 if (!feature.Create()) 317 return -1; 318 if (!m_AcqDevice.GetFeatureInfo(featureName, feature)) 319 return -1; 320 int minValue = 0; 321 if (!feature.GetValueMin(out minValue)) 322 return -1; 323 return minValue; 324 } 325 } 326 }
當你完全理解了這篇文章(以及我提到的兩篇),Dalsa網口線陣相機的開發基本就沒啥問題了,當然這時候你返回去看官方demos會有新的收獲,比如我上面的UpdateFrameRate()就是從官方demos中剝離出來的,該函數可以得到采圖時的實際幀率。
有什么問題可以留言詢問。