管理員
統計數據
原文網址 石小川 2019-11-29 12:26:52
這是幾年前實驗光學雷達並取得3D點雲資料的程式,程式已經不見了只能回味!
p.s. 建議3D點雲資料用開源軟體MeshLab讀取呈現3D效果比我專業多了
原文網址 石小川 2019-11-29 09:11:49
p.s. 雷達是機器的眼睛,以後還會多方介紹這類的遙測技術,掌握這門知識對於設計智慧型機器幫助會很大 。 光學雷達(LiDAR)這幾年很紅, 從汽車自動駕駛、火炮射控、巡弋飛彈,、機器人或掃地機等都可看到蹤影, 因為有人問我光學雷達的點雲(point cloud)資料如何產生, 所以今天簡單講解一下點雲基礎。 其實原理很簡單, 由雷達發射的仰角α(elevation), 水平角β(azimuth)及接收訊號的距離r(distance), 參考下面圖的座標三角函數關係就可得知目標物的方位等資訊P(x’, y’, z’), 一連串的數據產生這就是點雲的故事了。其格式如下: Point cloud = {P1, P2, P3…..Pn}; //表示一組點雲數據 Pi = [xi’, yi’, zi’]; //每一個點雲就是一個座標 更進階的點雲還加入目標物的RGB顏色, 灰階值等資訊φi, 則格式會變成 Pi = [xi’, yi’, zi’, φi]; 下面是點雲公式推導, 參考一下就好. 定義符號, 仰角 : α(elevation) 水平角 : β(azimuth) 距離 : r(distance) r’ = r * cosα (1) x’ = r’ * cosβ (2) y’ = r’ * sinβ (3) z’ = r * sinα (4) 將 (1)式代入(2), (3)式, 重新整理如下: x’ = r * cosα * cosβ (5) y’ = r * cosα * sinβ (6) z’ = r * sinα (7) 從LiDAR發射光點及接收訊號, 可以知道(α,β,r)參數, 所以很快就可求出目標物的方位值(x’, y’, z’)。 p.s. 下面程式(LiDAR + 3軸雲台控制)是我以前用Arduino寫來測試LiDAR用的, 有點長慎入, 日子久了有點不知程式在寫啥! 程式沒有最佳化但可以動作, 純粹Demo點雲公式如何使用給有興趣的好友參考一下, 有興趣的朋友也可改成Python版本嘉惠其他人, 如果有不解地也可私我。 //Program of LiDAR******************************** #include <string.h> #include <Servo.h> #include <LIDARLite.h> //----------------------------------------------------------------------------- #define Def_minAzimuth 30 #define Def_maxAzimuth 160 #define Def_standAzimuth 90 #define Def_minElevation 0 #define Def_maxElevation 60 #define Def_standElevation 0 #define Def_offsetElevation 90 //----------------------------------------------------------------------------- LIDARLite _lidar; Servo _servoX; Servo _servoY; // Minimum and maximum servo angle in degrees // Modify to avoid hitting limits imposed by pan/tilt bracket geometry int _minPosX = 30; //0; int _maxPosX = 160; // 180; //Servo real position int _minPosY = 90; int _maxPosY = 100; int _midPosX = 90; //(_maxPosX + _minPosX) / 2; int _midPosY = 100; //(_maxPosY + _minPosY) / 2; boolean _scanning = false; boolean _scanDirectionX = false; boolean _scanDirectionY = false; int _posX = 0; int _posY = 0; //pole coordinate int _Azimuth = 0; int _Elevation = 0; int _Distance = 0; unsigned int _loopCount = 0; //----------------------------------------------------------------------------- void setup() { _lidar.begin(0, true); _lidar.configure(0); _servoX.attach(2); _servoY.attach(3); _Azimuth = Def_standAzimuth; //90 //_Elevation = Def_standElevation + Def_offsetElevation; //0 - 90 _Elevation = Def_standElevation; //0 ServoHome(_Azimuth, _Elevation); LidarON(); Serial.begin(115200); delay(100); } //end setup() //----------------------------------------------------------------------------- void loop() { int px, py; int length, count; char command[12], code[12]; String data[6]; delay(1000); if(length = Serial.available()) { Serial.readBytes(command,length); command[length]= '\0'; count = CommandParse(command, data, " "); DataParse(data[0], code); if(code[0] == 'C') { switch(code[1]) { case '0': //Serial.println("C0 OK!"); break; case '1': ServoHome(_Azimuth, _Elevation); Serial.println("C1 " + String(_Azimuth) + " " + String(_Elevation)); delay(1000); break; case '2': //指向測距 DataParse(data[1], code); _Azimuth = atoi(code); DataParse(data[2], code); _Elevation = atoi(code); LidarScan(_Azimuth, _Elevation, true); //px= _Azimuth; //py = _Elevation + 90; //ServoMove(px, py); //delay(100); Serial.println("C2 " + String(_Azimuth) + " " + String(_Elevation)); break; case '3': Serial.println("C3"); break; case '4': //全面掃描 LidarScan(_Azimuth, _Elevation, false); ServoHome(_Azimuth, _Elevation); //Serial.println("C4"); break; } //end switch(command[1]) } //end if(command[0] == 'C') else { /* while(Serial.read() != -1) { delay(100); } */ //Serial.println("Error"); } } //end if(length = Serial.available()) } //end loop() //----------------------------------------------------------------------------- int CommandParse(char *command, String data, char delimit) { int index; char *ptr; index = 0; ptr = strtok(command, delimit); data[index] = (String)ptr; index++; while ((ptr = strtok(NULL, delimit))) { data[index] = (String)ptr; index++; } //end while ((ptr = strtok(NULL, delimit))) return index; } //----------------------------------------------------------------------------- void DataParse(String data, char *code) { int i; for(i=0; i < data.length(); i++) code[i] = data.c_str()[i]; code[i] = '\0'; } //----------------------------------------------------------------------------- //azimuth : 0 - 180. elevation : 0 - 90 void ServoHome(int azimuth, int elevation) { int i; //Handle Azimuth if(azimuth >= Def_standAzimuth) // >= 90 { for(i = azimuth; i > Def_standAzimuth; i--) { _servoX.write(i); delay(10); } } else if(azimuth < Def_standAzimuth) // < 90 { for(i = azimuth; i < Def_standAzimuth; i++) { _servoX.write(i); delay(10); } } _Azimuth = i; //Handle Elevation if(elevation >= Def_standElevation) // <= 0 { for(i = elevation; i > Def_standElevation; i--) { _servoY.write(i + Def_offsetElevation); delay(10); } } else if(elevation < Def_standElevation) { for(i = elevation; i < Def_standElevation; i++) { _servoY.write(i + Def_offsetElevation); delay(10); } } _Elevation = i; } //end void ServoHome() //----------------------------------------------------------------------------- //azimuth, elevation, is currently position void LidarScan(int azimuth, int elevation, bool enable) { int i, j; unsigned int loopcount, distance; bool bDirection; if(enable) //單點掃描 { ServoMove(azimuth, elevation + Def_offsetElevation); delay(1000); //少了distance會錯誤 distance = _lidar.distance(false); Serial.println(String(azimuth) + " " + String(elevation) + " " + String(distance)); i = azimuth; j = elevation; } else //全區掃描 { //initial servo start point x=0, y=0 for(i = azimuth; i > Def_minAzimuth; i--) { _servoX.write(i); delay(10); } //initial servo start point y=0 for(i = elevation; i > Def_minElevation; i--) { _servoY.write(i + Def_offsetElevation); delay(10); } loopcount = 0; bDirection = true; for(j = Def_minElevation; j < Def_maxElevation; j++) // Scan Y { if(bDirection) { bDirection = false; for(i = Def_minAzimuth; i < Def_maxAzimuth; i++) { loopcount++; if (loopcount % 100 == 0) distance = _lidar.distance(); else distance = _lidar.distance(false); Serial.println(String(i) + " " + String(j) + " " + String(distance)); ServoMove(i, j + Def_offsetElevation); } //end for(i = Def_minAzimuth; i < Def_maxAzimuth; i++) } else { bDirection = true; for(i = Def_maxAzimuth; i > Def_minAzimuth; i--) { loopcount++; if (loopcount % 100 == 0) distance = _lidar.distance(); else distance = _lidar.distance(false); Serial.println(String(i) + " " + String(j) + " " + String(distance)); ServoMove(i, j + Def_offsetElevation); } //end for(i = Def_maxAzimuth; i > Def_minAzimuth; i--) } /* for(i = Def_minAzimuth; i < Def_maxAzimuth; i++) { loopcount++; if (loopcount % 100 == 0) distance = _lidar.distance(); else distance = _lidar.distance(false); Serial.println(String(i) + " " + String(j) + " " + String(distance)); ServoMove(i, j + Def_offsetElevation); } //end for(i = Def_minAzimuth; i < Def_maxAzimuth; i++) delay(10); for(i = Def_maxAzimuth; i > Def_minAzimuth; i--) { loopcount++; if (loopcount % 100 == 0) distance = _lidar.distance(); else distance = _lidar.distance(false); Serial.println(String(i) + " " + String(j) + " " + String(distance)); ServoMove(i, j + Def_offsetElevation); } //end for(i = Def_maxAzimuth; i > Def_minAzimuth; i--) delay(10); */ } _Azimuth = i; _Elevation = j; } } //----------------------------------------------------------------------------- void ServoX(int px) { _servoX.write(px); delay(10); } //end void ServoMove() //----------------------------------------------------------------------------- void ServoY(int py) { _servoY.write(py); delay(10); } //end void ServoMove() //----------------------------------------------------------------------------- void ServoMove(int px, int py) { _servoX.write(px); _servoY.write(py); delay(10); } //end void ServoMove() //----------------------------------------------------------------------------- void LidarON() { _scanning = true; _loopCount =0; } //end void LidarON() //----------------------------------------------------------------------------- void LidarOFF() { _scanning = false; _loopCount =0; } //end void LidarOFF() //----------------------------------------------------------------------------- /* bool LidarScan() { if(_scanning) { _loopCount ++; if (_loopCount % 100 == 0) _Distance = _lidar.distance(); else _Distance = _lidar.distance(false); _Azimuth = _posX; _Elevation = _posY - 90; Serial.println(String(_Azimuth) + " " + String(_Elevation) + " " + String(_Distance)); ServoMove(_posX, _posY); if(_scanDirectionX) _posX++; else _posX--; if(_posX >= _maxPosX || _posX <= _minPosX) { _scanDirectionX = !_scanDirectionX; if(_scanDirectionY) _posY--; else _posY++; if(_posY > _maxPosY || _posY < _minPosY) { _scanDirectionY = !_scanDirectionY; LidarOFF(); //ServoHome(); return true; } } } //end if(_scanning) return false; } //end void Lidar()
原文網址 石小川 2019-11-28 09:37:13
連續三天鐵人po文結束,先給好朋友吸收一下資訊,找時間再發文了。 這個研習交流廳是大家平等共有的園地,有賴大家一起愛護,也歡迎各界朋友踴躍貼文、發言和研討,科學的進步就是要如此! Thanks^^
原文網址 石小川 2019-11-27 15:30:33
現今開發應用程式已經很少不用視窗當作人機介面(HMI)了,同樣的,如果沒有用圖控方式操作自動化設備的話,相信機器應該也很難賣得出去,就是因為圖控是很重要的一環,所以我特別在這一章節簡潔的介紹一個開發圖控介面不錯的工具 – PyQt5給讀者參考,慎選好的開發工具的確可以讓你省下不少寶貴的時間。 這裡會用一個實例讓讀者了解Python結合圖形視窗的設計流程,熟悉之後在設計儀表控制、機器自動控制等介面將不在是難事, 有興趣的讀者還可改成流行的拖放( drag and drop )圖控應用程式,投資自已學習這門工具是值得的,下面就一步步來實現。 3. 1 安裝 PyQt5 前面如果沒有安裝,現在趕快安裝,方法如下: 在Anaconda Prompt 視窗(Windows 命令提示字元)下先後輸入 pip install PyQt5 pip install PyQt5-tools 安裝完成後最好重開機,在路徑資料夾Anaconda3\Library\bin內會有個執行 檔案designer.exe,請拉到桌面方便使用(注意你的路徑及資料夾可能跟我的是不一樣),至此安裝結束, 接下來可以打開designer設計圖控介面了,如圖3-1所示,我們會用它來設計一個簡單的圖控介面,並展示如何用Python程式引用這個介面的相關知識。 3. 2 認識QT Designer 認識一下圖3-1 QT Designer視窗中4個紅字標註區域。 區域1是元件盒,裡面提供很多的元件控制項,每個項目的功能不同 ,例如常用到的按鈕(Push Button)、單選紐(Radio Button)、及 標籤(Label) 、文字方塊(TextEdit)等,可以直接拖放到區域2主視窗中擺放,在區域3物件 指示器(Object Inspector)中可索引切換擺放在主視窗中不同的元件,利用屬性編輯器(Property Editor)對選中的元件編輯如寬度、高度、佈局、字型等屬性。 區域4是信號/信號槽編輯器,這是PyQt5非常重要的核心機制,當按紐按下後就會發射一個信號(signal),信號槽(slot)接收到這個對應的信號後就會處理這個事件。 Qt Designer 這一個視覺化的GUI設計工具,對程式設計師來說真的是一大福音,便利了許多, 有一點要提想讀者的是PyQt5在GPL協議是可以完全免費使用,但如果不打算開源準備應用於商業活動還是得付費。當然啦,熟悉了Designer UI設計後,一大票相似免費軟體等著你,不怕被綁架! 如果讀者想更深入了解Qt5及程式設計,可至Qt官網下載資料好好研究。 Qt 官網 : https://www.qt.io/download-qt-installer 3. 3 使用QT Designer 設計一個UI介面 步驟 1. 點擊下拉選單[檔案]新增一個新表單(圖3-2)。 圖 3-2 新建一個新表單。 步驟2. 選Widget 表單(圖3-3)。 圖 3-3 Widget表單。 步驟3. 拖放元件到表單(圖3-4)。 從元件盒拖放一個Label 及Push Button 到表單(Form)上(圖 3-4)。 可在物件指示器及屬性編輯器上修改元件名稱、屬性。 在此為了簡化問題,我就維持不變,讀者熟悉後可自已動手試試。 圖 3-4 設計表單。 步驟4. 編輯信號與信號槽。 要讓按鈕按下有作用,還需要將信號和槽關聯起來, 如圖3-5點擊工具列[編輯信號與信號槽] 圖 3-5 編輯信號與信號槽 步驟5. 關聯信號與信號槽。 滑鼠游標移到表單(Form)的元件按鈕(PushButton)處,左鍵按著不放移動到 表單可看到一個接地線,之後放開左鍵會彈跳出一個設定連線的視窗(圖3-6)。 圖3-6 關聯信號與信號槽 步驟6. 編輯信號槽事件函數。 選定 pushButton 發射信號是clicked(),之後點擊右邊Form下的編輯按鈕 (圖3-7)和(圖3-8)。 圖3-7 pushButton -> clicked() 步驟7. 新增信號槽事件函數。 按+新增一個自訂pushButton_click()事件函數()(圖3-8), 按OK鍵離開。 圖3-8 編輯信號槽 步驟8. UI設計完成。 自動跳回主視窗後可看到自建的UI成形了,不要忘記存檔,例如檔名myUI.ui (副檔名ui),要增加或修正UI也可開啟舊檔編輯,不需要從無到有再搞一 次,是不是挺方便的。 圖3-9 UI 步驟9. ui轉檔py。 Qt Designer 儲存的是.ui檔,結構類似XML格式,因為我們期望是要給Python引用,所以我們必須想辦法將.ui檔轉換成.py檔才行。 PyQt5提供一個命令列工具pyuic5可以很輕鬆地完成轉換工作,使用方式如下 : pyuic5 source.ui -o destination.py 其中 -o : 是輸出参数,表示要產生一個文件 destination.py : 要產生.py格式的文件 source.ui : 由Qt Designer設計UI的.ui原始文件 舉個例子, 假設你設計出來的UI檔名是 myUI.ui,想轉檔成myUI.py格式 可以照著圖3-10這樣做,在相同路徑下會自動產生myUI.py檔。 如果你常用pyuic5,且對Python語法熟悉的話,建議可自已寫個簡單腳本,將上述命令列改成自動化執行,這個就留給讀者自已練習了。 圖3-10 透過pyuic5將.ui 轉換成.py 上述步驟雖多其實並不複雜,讀者還是要實際操作一次才會真正了解其中的奧義,UI美不美觀端看個人造詣,但設計的方法都是一樣的,多練習了。 緊接著下個單元會舉一個實例說明Python引用myUI.py的方法。 3. 4 Python 引用UI介面 新建一個程式碼myApp.py並繼承介面檔(myUI)的Ui_Form類別即可(打開 myUI.py 檔就可看到類別class Ui_Form)。 其中myUI.py介面檔必須放在myApp相同資料夾內,否則會錯誤。 程式碼執行如圖3-11,按一下PushButton會show出 "Hi this is my first coding !!!",結果正如預期完全正確。myApp.py可當作一個樣板程式,讀者可慢慢擴增一些新功能,建議每加入一個功能最好就執行一次,沒問題了再增加,以免欲速則不達,將來花更多的時間debug,會抓到瘋,畢竟最複雜的程式也是從最基本的一磚一瓦造出來的,除非讀者寫程式功力深厚以及擁有的程式庫經過多年考驗沒問題,否則還是聽我的準沒錯。 myApp.py程式碼如下: - - coding: utf-8 - - import sys from PyQt5.QtWidgets import QMainWindow, Qapplication from myUI import Ui_Form #------------------------------------------------------------------------------ class MyWindow(QMainWindow, Ui_Form): def init (self, parent=None): super(MyWindow, self). init (parent) self.setupUi(self) #------------------------------------------------------------------------- def pushButton_click(self): self.label.setText("Hi this is my first coding !!!") #------------------------------------------------------------------------------ def main(): app = QApplication(sys.argv) myWin = MyWindow() myWin.show() sys.exit(app.exec_()) if name == ' main ': main() 圖3-11 myApp 程式碼及執行結果
陳必凱 2019-11-27 15:40:03
曾彥禮 2019-11-27 16:24:58
哇
曾希哲 2019-11-27 19:40:06
有designer 寫起來跟Visual basic 一樣方便
林志強 2019-11-27 22:08:29
周英男 2019-11-27 23:22:18
好想學
原文網址 石小川 2019-11-27 11:09:22
窮忙一陣子後今天終於可偷閒寫一下文章跟大家分享一下上回談的G-Code產生器了. 我用最簡單的CNC圓形切割來圖文解釋一下, 相信應該可以很快了解其中運作原理.
先解釋一下Entity名詞, Entity在CAD工程上是實體的意思, AutoCAD出圖後的元件我們稱為Entity, 如圓弧, 直線, 雲行線等皆是實體. 上回提過由DWG/DXF檔案 經過程式剖析分解後產生一連串Entity的資料, 如圖一, 是圓的Entity, 將它送入GCodeCircle(Entity) 函式就可很快地產生切圓的G-Code, 下面是GCodeCircle(Entity) C#版程式範例:
//------------------------------------------------------------------------- public string GCodeCircle(EntityRecord entityrecord) { string gCode = ""; Point3D center = new Point3D(entityrecord.Circle.Center.X, entityrecord.Circle.Center.Y, entityrecord.Circle.Center.Z); double radius = entityrecord.Circle.Radius; Point3D point = new Point3D(0, 0, 0); point.X = center.X + radius; point.Y = center.Y; gCode += "G00 X" + (point.X - center.X).ToString("#0.0000") + " " + "Y"+ (point.Y - center.Y).ToString("#0.0000") + " " + "Z5.0000"+ "\n"; //此段是為了說明已經簡化許多參數 gCode += "G01" + " " + "Z-1.0000"+ " " + "F200"+ "\n"; gCode += "G02" + " " + "I-"+ radius.ToString("#0.0000"); return gCode; }
圖二是產生的G-Code, 可直接送去機器做切割一個100mm的正圓.
另外之前有位朋友問我5軸聯動切向跟隨實作問題, 我列出部分程式碼給您參考一下, 基本上我大部分實作都是圓弧直線插補ArcLine()就可完成.
//--------------------------------------------------------------------------- //圓弧直線跟隨插補 //pos : Z軸直線運動位置 //axisNum = 3, 圓弧+直線運動軸數 X Y Z public boolArcLine(int startx, intstarty, int endx, intendy, int cx, intcy, int[] pos, int[] axis, int dir = 0, intaxisNum = 3, double acc = 10, doubletgvel = 100, double endvel = 0, doublefeedRate = 1.0, int wait = 1, intfifo = (int)FIFO_SEL.SEL_PFIFO1, boolbAbs = true) { int st; if(axisNum < 2 || axisNum > MAX_NAXIS) return false; //清空PFIFO st = IMC_Pkg.PKG_IMC_PFIFOclear(gHandle, fifo); if(st == 0) return false; //設置加速度和進給率 if(SetPFIFO(acc, feedRate, fifo) == false) return false; //映射軸 st = IMC_Pkg.PKG_IMC_AxisMap(gHandle, axis, axisNum, fifo); if(st == 0) return false; double dx,dy; dx = startx - cx; dy = starty - cy; double r1 = Math.Sqrt(dx * dx + dy * dy); dx = endx - cx; dy = endy - cy; double r2 = Math.Sqrt(dx * dx + dy * dy); //判斷, 如果起點到圓心距離r1 != 終點到圓心距離r2 表示圓弧軌跡不正確, 跳出不執行 if(r1 != r2) return false; //由當前位置移動到指定位置 if(bAbs) //絕對位置 st = IMC_Pkg.PKG_IMC_ArcLine_Pos(gHandle, endx, endy, cx, cy, dir, pos, axisNum - 2, tgvel, endvel, wait, fifo); //絕對位置 else st = IMC_Pkg.PKG_IMC_ArcLine_Dist(gHandle, endx, endy, cx, cy, dir, pos, axisNum - 2, tgvel, endvel, wait, fifo); //相對位置 if(st == 0) return false; return true; }
圖三是我執行雷射切割的機台, 雷射頭是5.5W藍光雷射, 下回再分享此G-Code雷射切割成果.
p.s. 在我的APP程式直接畫圖我是直接呼叫運動卡驅動CNC, 沒有經過G-Code產生器這道手續, 加快CNC處理速度, 除非是匯入DWG/DXF檔案才會呼叫G-Code 產生器。
鍾小生 2019-11-27 12:24:32
小聲問,那套App有試用版嗎?
原文網址 石小川 2019-11-27 10:57:00
對於想用CNC製作一些零組件的朋友來說,要學會AutoCAD、SolidWork、uG、Fusion360、ARTCAM、PowerMill、 MasterCAM、Mach3等,我相信會嚇跑一票人的熱血。其實CAD/CAM套裝軟體很強但有很多功能幾百年也用不到,只是浪費錢而已,在接工廠自動控制客製化軟體方面絕大部分作動都很單純,我這裡是將所有功能精簡成 : /檔案/機器/刀具/G-Code sender,只要讀檔或在螢幕寫字、繪圖後自動產生G-Code直接送至CNC銑床或雷射加工,盡量能由機器代勞的就盡量隱藏在程式裏面,操作介面就只有一個螢幕面板Panel。
要做到這點須先解析DWG檔案結構,後續追加功能才有辦法走下去,AutoCAD DWG檔案簡直就是一個複雜的巨型圖層資料庫而且每兩年改版一次,建議還是用DXF圖檔相容性較高,有機會再談談DXF檔結構。這裡先分享一下DWG解析方法給有興趣研發的好友參考一 下。
基本上程式要解析其中的資料結構如下,有點長刪減一些函數但原理不變。
讀取DWG檔後打開資料庫取實體EntityName的字串名稱(約50個標籤名稱)一層層解析後繪圖即可原圖重現,看你要加圖、刪圖或送去產生G-Code等,另外如果用不著的功能就略過,沒必要全部實作,有興趣可一起討論,下回再討論G-Code產生器。
p.s. 下列範例是C#版本,有興趣的朋友歡迎自行改成Python版本。
//------------------------------------------------------------------- public void Draw(List<CadParse> cadparselist) { foreach(CadParse cadParse in cadparselist) { switch(cadParse.EntityName) { case "AcDbAlignedDimension": break; case "AcDbArc": break; case "AcDbArcDimension": break; case "AcDbBlockReference": break; case "AcDbBody": break; case "AcDbCircle": DrawCircle(cadParse); break; case "AcDbPoint": break; case "AcDbDiametricDimension": break; case "AcDbViewport": break; case "AcDbEllipse": DrawEllipse (cadParse); break; case "AcDbFace": break; case "AcDbHatch": break; case "AcDbLeader": break; case "AcDbLine": DrawLine(cadParse); break; case "AcDb2LineAngularDimension": break; case "AcDbMInsertBlock": break; case "AcDbMline": break; case "AcDbMText": break; case "AcDbOle2Frame": break; case "AcDbOrdinateDimension": break; case "AcDb3PointAngularDimension": break; case "AcDbPolyFaceMesh": break; case "AcDbPolygonMesh": break; case "AcDbPolyline": DrawPolyLine(cadParse); break; case "AcDb2dPolyline": break; case "AcDb3dPolyline": break; case "AcDbProxyEntity": break; case "AcDbRadialDimension": break; case "AcDbRasterImage": break; case "AcDbRay": break; case "AcDbRegion": break; case "AcDbRotatedDimension": break; case "AcDbShape": break; case "AcDbSolid": break; case "AcDb3dSolid": break; case "AcDbSpline": break; case "AcDbTable": break; case "AcDbTrace": break; case "AcDbWipeout": break; case "AcDbXline": break; case "AcDbPdfReference": break; case "AcDbDwfReference": break; case "AcDbDgnReference": break; } //end switch(cadParse.EntityName) } //end foreach(CadParse cadParse in _cadParseList) } //end public void Draw()
原文網址 石小川 2019-11-27 10:42:21
雙眼攝影機測距 II – 原理解析
接續昨天所談的,雙眼測距就是簡單的三角測距公式,我導一下公式,稍微解釋就可馬上了解。
@符號定義如下: P : 待測物。 Z : 相機與待測物的距離。 d: 視差。 f: 攝影機焦距。 T: 左右攝影機投影中心的距離。 Xl: 左影像平面成像座標。 Xr: 右影像平面成像座標。
當然商品有很多know-how,但原理就是如此,為了說明我簡化了儀器,假設 (a) 左右焦距是相同的 fl = fr = f。 (b) 左右相機特性完全相同,也完美矯正過。
除非想製作雷達,否則可不理會(a)、(b)兩項完美假設。
OK, 進入主題,沒多大學問,如圖所示,只要應用幾何三角比例關係;
T / d = Z / f (1)
Z = f . T / d (2)
令 d = Xl - Xr (3)
Z = f . T / Xl - Xr (4)
其中變數f、T、d皆是可從相機求得。
所以Z(距離或稱深度)就可以很容易的由(4)式求出來了。
看是不是很簡單, 希望我沒有誤人子弟^^
Shaoe Chen 2019-11-27 10:45:38
感謝 學習不少
林志強 2019-11-27 10:55:01
? ? ? ?
蔡則昌 2019-11-28 13:51:37
原文網址 石小川 2019-11-27 10:39:11
雙眼攝影機測距 I – 設備實體製作(像蛙鏡,就稱蛙鏡好了)
其實這個就是光學雷達(Lidar)的一種。在機器人視覺中模擬人眼雙目測距是最基本功夫,而且設備簡單只要兩個攝像頭加上演算法即可實現測距,沒有雷射輔助的話10公尺的誤差在1公分以內, 算是可接受範圍,精準度要看個人調校技巧了,加上雷射光或其他結構光輔助更可進一步將RGB影像加上深度資訊,換句話說就是上回談到的點雲圖了,這對機器人視覺來說太有用了。
在研讀聚合物(Polymer)時,對於壓克力的物理化學特性就深受其吸引,所以做產品或實驗時常拿壓克力練功。 因為壓克力有易碎特性,此次用CNC銑時參數調慢些, 轉速S6000, 進給F300, 每層下切0.5mm,成果還不錯只是慢了些花了兩個鐘頭,下次實驗再調快否則有點大材小用,對不起這台專門銑不銹鋼模具的機器。
圖示是實作的設備,兩個攝像頭(Camera x 2) + 一個雷射頭(Laser x 1),外加演算法,就這麼簡單! 至於雙眼測距原理就是上童軍課的三角測距法,下回 “雙眼攝影機測距 I I – 原理解析” 再來聊聊了^^
原文網址 石小川 2019-11-26 12:36:22
[語法簡單明瞭] Python語法簡單易學,最重要的是還有大量功能強大的免費模組可下載,其強大的應用層面已經發展到令人不可忽視的重要地位,甚至NASA也拿來當作航太人機介面的控制語言。早期接的專案我都是用Assembly、C、C++、C#等設計自動控制系統,這幾年我很多是改用Python來撰寫,好處是取得系統傳回的資料後,可很容易且快速的結合各種海量模組演算法發展出很專業的人工智慧機器。 [Python是跨平台語言] Python本身沒有支援特定硬體控制的功能,也正因為如此它才能夠跨平台,但這不是原罪,相反的卻是它的優點,換句話說,在x86、Arm、Arduino、PC、手機或平板等不同的作業系統環境下,相同的程式皆可以很容易互相移植過去正常執行,這 print('免費資源 + 免費的模組 + 簡單易學') 根本是一場世界革命,能不紅嗎! 綜合以上講了一大堆,無非就是要說服工程師們是該改變自已接受世界脈動的時候了,我也不例外,以此共勉之! [軟體開發] 接下來進入主題談論如何用Python開發一個多軸的CNC平台,會舉這個CNC專題當作例子是因為在我眼裡,CNC其實就是一個機器人,我的經驗是~只要搞懂CNC軟硬體知識後,無論是自駕車、四軸無人機或是工具機皆是囊中物,至於想要用它做些甚麼,端看你無限的想像力而定! 因為Python沒有直接存取硬體的介面,我的方法是用Python當作主程式,將底層存取硬體的API程式(動態連結程式庫 DLL)封裝成Python可調用的格式即可,如此一來多年使用C++/C#寫的程式庫都可引用了。因為程式很長, 為免洗板我簡略敘說過程如下: (A). 封裝DLL程式庫成PyCNC.py 模組,其中 class __IMC_Pkg()就是封裝成類別的名稱, 將來Python與機器溝通的介面就是依靠這項。 #--------------------------------------------------------------------------- #這是被呼叫的模組 : PyCNC.py #--------------------------------------------------------------------------- #需引用ctypes import ctypes import ctypes.wintypes #------------------------------------------------------------------------------ class __IMC_Pkg(): def __init__(self): self.ptr = ctypes.WinDLL('IMC_Pkg.dll') #_stdcall #--------------------------------------------------------------------------- def Open(self, netcardId, imcId): self.ptr.PKG_IMC_Open.argtypes = (ctypes.c_int, ctypes.c_int) self.ptr.PKG_IMC_Open.restype = ctypes.POINTER(ctypes.c_voidp) return self.ptr.PKG_IMC_Open(netcardId, imcId) #--------------------------------------------------------------------------- def Close(self, handle): self.ptr.PKG_IMC_Close.argtypes = (ctypes.POINTER(ctypes.c_voidp), ) self.ptr.PKG_IMC_Close.restype = ctypes.c_int return self.ptr.PKG_IMC_Close(handle) #--------------------------------------------------------------------------- def MoveAbs(self, handle, pos, startvel, tgvel, wait, axis): self.ptr.PKG_IMC_MoveAbs.argtypes = (ctypes.POINTER(ctypes.c_voidp), ctypes.c_int, ctypes.c_double, ctypes.c_double, ctypes.c_int, ctypes.c_int) self.ptr.PKG_IMC_MoveAbs.restype = ctypes.c_int return self.ptr.PKG_IMC_MoveAbs(handle, pos, startvel, tgvel, wait, axis) #--------------------------------------------------------------------------- def MoveAbs_P(self, handle, pos, startvel, tgvel, wait, axis): #P 輔助座標 self.ptr.PKG_IMC_MoveAbs_P.argtypes = (ctypes.POINTER(ctypes.c_voidp), ctypes.c_int, ctypes.c_double, ctypes.c_double, ctypes.c_int, ctypes.c_int) self.ptr.PKG_IMC_MoveAbs_P.restype = ctypes.c_int return self.ptr.PKG_IMC_MoveAbs_P(handle, pos, startvel, tgvel, wait, axis) . . (B). 主程式motion.py 要引用也很簡單, 只要加上PyCNC.py模組,就可以調用IMC_Pkg所有的功能 #--------------------------------------------------------------------------- #這是主程式 : motion.py #--------------------------------------------------------------------------- from CNC import PyCNC IMC_Pkg = PyCNC.__IMC_Pkg() #--------------------------------------------------------------------------- if(self.gHandle != None): IMC_Pkg.Close(self.gHandle) self.gHandle = IMC_Pkg.Open(netcardId, imcId) if(self.gHandle != None): #//if(IsOpen()) if(IMC_Pkg.InitCfg(self.gHandle) != 0): self.nAxis = IMC_Pkg.GetNaxis(self.gHandle) #取得設備支援軸數 p, self.Position = self.GetPosition(3) (C). 建議開發環境安裝Anaconda開發包, 在設計Python程式時可少走很多冤枉路,為了相容以前DLL程式庫,我是下載Anaconda3 - 32bit - Python3.7.3版本。 p.s. (1). IMC3xx/IMC4xx系列運動控制卡所有函數(IMC_Pkg.dll)我都有封裝成PyCNC.py以方便Python呼叫,有興趣的朋友可加入我臉書討論。 (2). 圖-1. 八軸運動+I/O+AD/DA控制卡應用圖例 圖-2. motion.py 主程式的視窗介面 影片-1. 使用motion.py 展示CNC軸控,也順便Demo飛行搖桿控制3軸及發射雷射 影片-2. 電腦執行motion.py情形 影片-3. 使用motion.py 執行G-Code,用雷射雕刻一個正圓 (3). 下回有機會再補上 : "使用Python語言做自動控制的方法 - I I 硬體篇"及 "使用Python語言做自動控制的方法 - I I I 通信篇" (UART、USB、TCP/IP)兩篇才算完整,並且教導大家如何組裝一台三軸運動控制平台的知識(機械結構及控制電路的接法),對於想創業設計自已機器的朋友不要錯過了。在臉書不適合長篇大論所以只能簡單敘述,總感覺見樹不見林,當然我也有開相關的指導課程,有興趣的朋友可上我臉書或私訊我。
Grass Lin 2019-11-26 13:12:09
感謝
Pizfinfin Albert 2019-11-26 18:32:35
我也深深有感;程式語言是積木;是工具;怎麼用工具變出創意及藝術那就是真的需要天份+努力.
林志強 2019-11-27 10:55:44
川哥 滿足我對知識的渴望
原文網址 石小川 2019-11-26 11:30:30
討論主題
發文排行榜
回文排行榜
熱門關鍵字