格特征提取利器vtkFeatureEdges全解析)
做網(wǎng)格數(shù)據(jù)處理的時候“特征提取”這四個字恐怕是我被問到最多的高頻詞之一。一搜特征提取方法網(wǎng)上鋪天蓋地都是圖像領(lǐng)域的SIFT、ORB或者點云領(lǐng)域的法向估計、FPFH描述子可當(dāng)你手里拿的是一個由頂點、邊、三角面片構(gòu)成的多邊形網(wǎng)格時最趁手的工具其實是VTK自帶的vtkFeatureEdges。這個類長期躺在vtkFiltersCore包里不起眼出場率卻極高模型分割前的棱線定位、配準(zhǔn)前的輪廓線抽取、網(wǎng)格簡化時想要保留硬邊背后幾乎都有它的影子。今天這篇VTK手冊就想把這個多邊形網(wǎng)格特征提取利器從頭拆透——不只是貼一段能跑的代碼而是把每個開關(guān)的含義、FeatureAngle的取值邏輯、提取后線條為什么老斷成碎片、怎么配合鼠標(biāo)拾取和Qt界面做交互一條一條講清楚。適合三類讀者正在用VTK做網(wǎng)格顯示與處理的開發(fā)人員、需要從CT/MRI等值面里抽輪廓線的影像工程師、以及把網(wǎng)格簡化與分割當(dāng)日常工作的圖形學(xué)同學(xué)。順便說個常見誤區(qū)。很多人搜特征提取方法搜到的是圖像特征或者點云特征描述子然后試圖直接套到三角網(wǎng)格上。圖像是規(guī)則網(wǎng)格三角網(wǎng)格是非規(guī)則拓?fù)鋬烧邔Α班徲颉钡亩x完全不一樣。vtkFeatureEdges恰恰是基于網(wǎng)格本身的拓?fù)溧徲颉簿褪沁叺墓蚕砻骊P(guān)系——來定義特征。理解這一點你才不會拿圖像特征提取的思路去調(diào)網(wǎng)格提取的參數(shù)。1. 先搞清楚特征邊在網(wǎng)格里到底是怎么定義的1.1 一張三角網(wǎng)格里邊其實只有四種狀態(tài)一個三角網(wǎng)格表面每條邊無非處于四種狀態(tài)之一。第一種是邊界邊整條邊只屬于一個面片邊緣直接暴露在外模型的開口、裁切邊緣都會出現(xiàn)它。第二種是特征邊一條邊同時屬于兩個面片但這兩個面片在這個位置上“折”得比較厲害二面角大于給定閾值機械零件的棱線、牙齒模型的牙尖棱脊都屬于這一類。第三種是非流形邊一條邊被三個甚至更多的面片同時占有這是網(wǎng)格數(shù)據(jù)里典型的“不該出現(xiàn)但偏要出現(xiàn)”的結(jié)構(gòu)通常意味著該區(qū)域拓?fù)溆衅茡p。第四種是流形邊即普通的、只被兩個面片共享且夾角平緩的邊模型表面絕大多數(shù)邊都是這種。vtkFeatureEdges干的事情就是把這四種邊全部掃描出來再按你的需要挑著輸出。想提取閉合輪廓用邊界邊想要模型上的折痕棱邊用特征邊想做網(wǎng)格完整性診斷打開非流形邊。我在做網(wǎng)格工具鏈時經(jīng)常把“邊類型統(tǒng)計”當(dāng)成網(wǎng)格體檢的第一步因為不同類型數(shù)量的比例往往能直接暴露這個模型是CAD導(dǎo)出的、CT重建的還是激光掃描拼接的。1.2 在VTK管線里它處在什么位置vtkFeatureEdges繼承自vtkPolyDataAlgorithm輸入是vtkPolyData輸出還是vtkPolyData。輸入可以是STL、OBJ、VTP直接讀進來的網(wǎng)格也可以是vtkMarchingCubes從DICOM體數(shù)據(jù)里跑出來的等值面。輸出里不再包含任何面片只有頂點和線單元每條線單元對應(yīng)一條原始邊。它是完全非破壞性的既不改網(wǎng)格幾何也不動拓?fù)渲话选澳男┻吺翘卣鬟叀钡慕Y(jié)論復(fù)制到一張新的PolyData里非常適合插在管線中間做后續(xù)再處理。我一般把它放在“讀取-清理-預(yù)處理”之后、下游算法之前。為什么不跳過預(yù)處理因為重復(fù)點會直接毀掉邊共享關(guān)系的判斷一個看起來封閉的模型可能因為頂點坐標(biāo)不完全一致而處處開縫特征邊提取結(jié)果立刻變成一堆亂線。更隱蔽的是法向不一致會讓二面角計算整體偏移該提的特征邊提不出來、不該提的冒出來。這兩個坑我在第4章會逐一展開。2. 核心參數(shù)逐個拆解這些開關(guān)一個都別亂開2.1 四個開關(guān)的默認(rèn)行為先記在心里vtkFeatureEdges提供的方法名非常直白看一眼就知道是干嘛的方法默認(rèn)狀態(tài)作用典型場景BoundaryEdgesOn/OffOn輸出只被一個面片使用的邊界邊網(wǎng)格開口輪廓、區(qū)域切割線FeatureEdgesOn/OffOn輸出二面角大于FeatureAngle的折痕邊棱線、特征線提取NonManifoldEdgesOn/OffOff輸出被三個及以上面片共用的非流形邊網(wǎng)格拓?fù)湓\斷與修復(fù)ManifoldEdgesOn/OffOff輸出普通平緩邊畫完整線框、輸出網(wǎng)格全部邊SetFeatureAngle(angle)30.0特征角閾值單位度核心調(diào)參對象ColoringOn/OffOff讓不同類型邊帶不同顏色標(biāo)量特征類型診斷我第一次用它時把所有開關(guān)一股腦打開輸出瞬間變成幾百萬條線段整個模型幾乎每條邊都被“提”了出來完全不是特征線的樣子。原因就是ManifoldEdgesOn把常規(guī)的普通邊全部放行了。經(jīng)驗之談默認(rèn)設(shè)置通常已經(jīng)夠用只有想診斷網(wǎng)格流形性時才臨時打開非流形邊只有要導(dǎo)出完整線框時才打開流形邊。日常項目里“邊界邊特征邊”這對組合占到了九成場景。2.2 FeatureAngle才是靈魂折痕到底多“折”才算特征FeatureAngle默認(rèn)是30度。判定邏輯可以從二面角理解一條邊屬于兩個相鄰三角形兩個三角形各自有法向量把法向夾角與FeatureAngle比較大于閾值就標(biāo)記為特征邊。直觀想象就是拿一張紙對折折痕處兩半平面夾角越小折痕越鋒利。對立方體而言任意相鄰面的法向夾角是90度遠(yuǎn)大于30度所以12條棱全部會被提取對球面或平面相鄰面法向夾角趨近于0度自然提不出什么東西。選閾值沒有通吃值跟模型來源強相關(guān)。我平時積累的參數(shù)經(jīng)驗工程CAD類網(wǎng)格幾何精度高特征角設(shè)15到30度都很合適地形表面或掃描重建的粗糙網(wǎng)格表面本來就有密集小起伏設(shè)5到10度能保留主要山脊線設(shè)30度以上基本什么都提不出來醫(yī)學(xué)影像重建的等值面普遍帶噪聲常用40到60度去抓真正的銳利邊界。調(diào)試時別憑感覺寫個小腳本把提取到的線條總數(shù)、平均長度打出來一邊改閾值一邊對比兩三輪就能鎖定這個模型適合的區(qū)間。需要提醒一點二面角計算依賴兩個面的法向量方向是統(tǒng)一的。如果輸入網(wǎng)格的法向沒有整理過部分面片朝外、部分朝內(nèi)算出來的夾角會偏差巨大。遇到提取結(jié)果詭異時先跑一遍vtkPolyDataNormals把法向統(tǒng)一再回頭調(diào)FeatureAngle這個順序不能反。2.3 輸出精度與分類著色兩個容易被忽視的小開關(guān)SetOutputPointsPrecision控制輸出點坐標(biāo)的數(shù)據(jù)類型默認(rèn)跟隨輸入。如果后面接vtkTubeFilter做可視化、或者要做精確坐標(biāo)計算建議顯式指定高精度。VTK里常用的取值是DEFAULT_PRECISION、SINGLE_PRECISION和DOUBLE_PRECISION我實際用double的場景更多因為網(wǎng)格處理鏈路上精度丟失往往在不易察覺處發(fā)生等到后續(xù)模塊報錯再回溯就比較痛苦。ColoringOn則是調(diào)試神器。打開后輸出的點屬性或單元屬性里會帶一個類型標(biāo)量邊界邊、特征邊、非流形邊會得到不同取值。配合vtkLookupTable和Mapper按Cell數(shù)據(jù)著色白花花一片的特征線立刻變成“紅是開口、藍(lán)是棱線、綠是壞邊”的體檢報告。這個能力放在大型網(wǎng)格上特別值錢你不可能用肉眼去逐條檢查成千上萬條邊。3. 實操全過程把特征線從網(wǎng)格里干凈地拿出來3.1 最小可運行代碼Python和C雙版本先放Python版這也是我平時做原型最快的方式import vtk sphere vtk.vtkSphereSource() sphere.SetPhiResolution(40) sphere.SetThetaResolution(40) sphere.Update() feature vtk.vtkFeatureEdges() feature.SetInputData(sphere.GetOutput()) feature.BoundaryEdgesOn() feature.FeatureEdgesOn() feature.NonManifoldEdgesOff() feature.ManifoldEdgesOff() feature.SetFeatureAngle(30.0) feature.ColoringOn() feature.Update() edge_data feature.GetOutput() print(特征線總數(shù):, edge_data.GetNumberOfLines())C版本邏輯完全一樣只是調(diào)用方式不同#include vtkSmartPointer.h #include vtkSphereSource.h #include vtkFeatureEdges.h #include vtkPolyData.h auto sphere vtkSmartPointervtkSphereSource::New(); sphere-SetPhiResolution(40); sphere-SetThetaResolution(40); sphere-Update(); auto feature vtkSmartPointervtkFeatureEdges::New(); feature-SetInputData(sphere-GetOutput()); feature-BoundaryEdgesOn(); feature-FeatureEdgesOn(); feature-NonManifoldEdgesOff(); feature-ManifoldEdgesOff(); feature-SetFeatureAngle(30.0); feature-ColoringOn(); feature-Update(); vtkPolyData* edgeData feature-GetOutput(); std::cerr 特征線總數(shù): edgeData-GetNumberOfLines() std::endl;兩個版本有一個共同易錯點用SetInputConnection時下游如果沒顯式Update某些情況下拿到的輸出是空的。穩(wěn)妥做法是取值前顯式調(diào)用一次Update不要賭渲染管線一定會幫你觸發(fā)。另外GetOutput返回的指針由VTK管線管理不需要你自己delete。如果你的輸入直接來自VTK數(shù)據(jù)源用SetInputConnection(source-GetOutputPort())更符合管線習(xí)慣我上面寫SetInputData更多是處理已經(jīng)從文件加載進內(nèi)存的vtkPolyData。注意vtkFeatureEdges的輸入必須是包含面單元的vtkPolyData。如果你手上只有散點云先走一遍表面重建流程別指望它直接處理點云。3.2 特征線渲染別用細(xì)線直接糊臉套一層Tube光拿到線數(shù)據(jù)還不夠渲染時通常要當(dāng)作高亮層疊在原網(wǎng)格上面。直接用vtkPolyDataMapper渲染lines特征線只有一兩個像素粗根本看不清。我通常會給線套一層vtkTubeFilter讓每條線變成有半徑的管道視覺效果立竿見影tubes vtk.vtkTubeFilter() tubes.SetInputConnection(feature.GetOutputPort()) tubes.SetRadius(0.02) tubes.SetNumberOfSides(8) tubes.CappingOn() mapper_line vtk.vtkPolyDataMapper() mapper_line.SetInputConnection(tubes.GetOutputPort()) actor_line vtk.vtkActor() actor_line.SetMapper(mapper_line) actor_line.GetProperty().SetColor(1.0, 0.0, 0.0) mapper_mesh vtk.vtkPolyDataMapper() mapper_mesh.SetInputData(sphere.GetOutput()) actor_mesh vtk.vtkActor() actor_mesh.SetMapper(mapper_mesh) actor_mesh.GetProperty().SetOpacity(0.3)這里Tube半徑要結(jié)合模型尺度。模型包圍盒對角線長10個單位時半徑0.02幾乎看不見取包圍盒對角線長度的1/200作為初始值再按需微調(diào)。還有個細(xì)節(jié)原網(wǎng)格設(shè)為半透明之后特征線疊在上面才不會被完全遮住。如果后續(xù)還要做交互拾取建議把特征線的顯示層級調(diào)高一點避免拾取時命中了帶著三角形面的原網(wǎng)格而不是細(xì)線。3.3 交互式提取用鼠標(biāo)坐標(biāo)定位最近的特征線純靜態(tài)提取始終差點意思。網(wǎng)格很復(fù)雜、特征線很多時你經(jīng)常會想要點一下某條特征線看它對應(yīng)模型的哪個結(jié)構(gòu)。這個需求落到VTK里就兩件事獲取鼠標(biāo)坐標(biāo)再做拾取。用vtkCellPicker最合適因為它能返回你點中的單元編號而特征線輸出的每個線單元正好就是一個cell拾取到之后再取它的世界坐標(biāo)就很方便。下面是我項目里的一個精簡模板假設(shè)你已經(jīng)創(chuàng)建了渲染窗口和交互器變量名是iren核心是往交互器上掛觀察者在左鍵點擊事件里做pickpicker vtk.vtkCellPicker() picker.SetTolerance(0.005) def on_left_button(obj, event): x, y obj.GetEventPosition() renderer obj.GetCurrentRenderer() picker.Pick(x, y, 0, renderer) if picker.GetCellId() 0: world_pos picker.GetPickPosition() print(f點擊特征線屏幕坐標(biāo)({x},{y}), 世界坐標(biāo)({world_pos[0]:.3f}, {world_pos[1]:.3f}, {world_pos[2]:.3f})) else: print(沒有點到特征線) style vtk.vtkInteractorStyleTrackballCamera() iren.SetInteractorStyle(style) style.AddObserver(LeftButtonPressEvent, on_left_button)這里有個坐標(biāo)變換的細(xì)節(jié)經(jīng)常讓新手困惑窗口坐標(biāo)系通常是左上角為原點VTK內(nèi)部渲染坐標(biāo)系卻是左下角為原點所以直接用屏幕坐標(biāo)去對應(yīng)世界坐標(biāo)時Y軸方向偏差在所難免。最穩(wěn)的辦法不是手動算坐標(biāo)軸而是像我上面這樣把位置交給vtkCellPicker去處理讓它返回正確的世界坐標(biāo)拾取位置。拿到鼠標(biāo)坐標(biāo)之后能做更多事我用vtkKdTree先建特征線頂點索引拾取后立刻找離點擊位置最近的特征點再用vtkGlyph3D在對應(yīng)位置放一個高亮小球。整個流程純疊加層不修改原始網(wǎng)格就算幾十萬條特征線也能保持實時響應(yīng)。另外picker對象建議在初始化時創(chuàng)建并復(fù)用不要在回調(diào)里反復(fù)new。頻繁創(chuàng)建picker對象雖然不會崩但會有無謂的分配開銷交互密集時性能差異能感覺出來。4. 常見問題與排查技巧實錄這些坑我全踩過4.1 特征數(shù)量爆炸幾十萬條線糊滿整個屏幕這個現(xiàn)象多半由兩個因素觸發(fā)。第一FeatureAngle設(shè)得太小表面噪聲全被當(dāng)成特征第二誤開了ManifoldEdgesOn普通邊全部涌出。排查第一步打印特征線數(shù)量、總長度和長度分布。噪聲引起的特征線普遍很短調(diào)大閾值后數(shù)量會出現(xiàn)明顯斷崖如果數(shù)量紋絲不動回頭檢查是不是開著流形邊。第二步如果閾值調(diào)到很高數(shù)量還是不降那基本能確認(rèn)輸入網(wǎng)格太臟先用vtkSmoothPolyDataFilter做平滑、或者重新remesh再跑特征提取不要拿原始掃描網(wǎng)格硬扛。注意關(guān)于FeatureAngle的調(diào)參我的習(xí)慣是“少量多次”。每次只改2到5度記錄輸出線數(shù)量和平均線長連續(xù)對比三條記錄后再確定最終值。一次猛調(diào)15度往往會把真正需要的棱線連帶刪光。4.2 特征線斷成碎片連不成一條完整折痕這是所有特征提取繞不開的痛。vtkFeatureEdges按邊輸出每條輸出對應(yīng)一個2點的vtkLine cell所以就算兩條線幾何上首尾相連在拓?fù)渖弦彩莾蓚€獨立cell。想要拿到連續(xù)曲線至少兩步先用vtkCleanPolyData把重復(fù)坐標(biāo)點合并再用vtkStripper把相鄰線段串成polyline。如果串出來的polyline還是斷的大概率是FeatureAngle剛好卡在邊界上個別關(guān)鍵邊被漏標(biāo)。我自己用的補線工具大致分四步第一步vtkCleanPolyData合并重復(fù)點第二步遍歷所有l(wèi)ine cell建立每個點的鄰接線列表第三步找出所有只有一條鄰接線的自由端點按距離閾值聚成組同一組內(nèi)兩個自由端用直線段或者沿網(wǎng)格最短路補上第四步聚完后再整體用vtkStripper串一次。這個方法比單純調(diào)閾值可控得多尤其適合機械零件上的圓角過渡線因為那些地方的角度本來就是漸變的閾值怎么設(shè)都容易斷。4.3 邊界邊數(shù)量對不上要么為0要么多到離譜邊界邊數(shù)量異常先別懷疑算法八成是輸入網(wǎng)格的真實狀態(tài)。第一種情況網(wǎng)格看起來開放但邊界邊為0這通常意味著數(shù)據(jù)是“假封閉”各個片體被焊死了你的預(yù)期就要調(diào)整。第二種情況邊界邊多得離譜說明模型存在大量細(xì)小裂縫常見于從標(biāo)簽圖像轉(zhuǎn)等值面的過程或者重復(fù)點導(dǎo)致面片之間拓?fù)浠ゲ幌噙B。用vtkCleanPolyData清理一遍重復(fù)點再統(tǒng)一計算一次法向大概率能解決九成的邊界誤判。處理完還不對就用vtkPolyDataNormals檢查法向方向是否一致方向不一致的網(wǎng)格特征邊和邊界邊都會同時錯亂。4.4 大數(shù)據(jù)集上的性能等待別讓特征提取卡住交互幾十萬面片對vtkFeatureEdges來說單次耗時其實不算夸張真正煩人的是GUI里拖動滑塊反復(fù)調(diào)FeatureAngle每次都整管更新界面直接卡死。我的三級降載方案可以分享第一級提取前先用vtkDecimatePro把面數(shù)降到能接受的水平第二級緩存特征提取結(jié)果只有FeatureAngle變化超過設(shè)定步長才重算第三級在Qt界面里把重算丟給后臺線程避免阻塞渲染。三級疊加之后再大的數(shù)據(jù)交互起來也算順滑。還有一種更隱蔽的性能問題輸入網(wǎng)格里如果有大量重復(fù)點vtkFeatureEdges會花很多時間在一致性檢查上。所以在上游加一次vtkCleanPolyData不光是正確性問題也是性能問題。5. 進階玩法特征邊的價值在提取之后才真正放大5.1 用ColoringOn給網(wǎng)格做“拓?fù)潴w檢”打開ColoringOn特征邊就會帶上分類標(biāo)量此時給Mapper配一個vtkLookupTable打開CellData著色模式表面立刻變成“紅是開口、藍(lán)是棱線、綠是壞邊”的體檢圖。這個技巧對來源不明的網(wǎng)格尤其實用。有一次我處理一個牙科掃描網(wǎng)格打開分類著色才發(fā)現(xiàn)模型某個區(qū)域藏了大量非流形邊后果是該區(qū)域的任何分割、簡化操作都不正常。那段區(qū)域后來做了一遍remesh后續(xù)分割算法的準(zhǔn)確性立刻提升。在大型模型上靠肉眼逐條檢查邊的狀態(tài)是不可能的分類著色就是最高效的篩選方式。5.2 特征邊 網(wǎng)格簡化把硬邊保護起來做網(wǎng)格簡化的人都知道面片從100萬壓到10萬機械零件上的棱線和圓角往往先被磨平。vtkFeatureEdges在這里可以扮演“保護名單”的角色先提取特征邊把特征邊涉及的頂點索引收集起來再做帶約束的簡化。vtkDecimatePro本身有PreserveEdges之類的選項但實際效果和數(shù)據(jù)結(jié)構(gòu)相關(guān)性很強并不總是可靠。我現(xiàn)在更常自己維護一個頂點集合把它作為簡化時“不允許刪除”的名單傳給簡化器實測下來棱線可以保留八成左右。收集特征頂點集合的思路很簡單遍歷輸出的lines用vtkCellArray取出每個line的兩個點Id寫進一個set容器。偽代碼大概是這樣的std::setvtkIdType protectedIds; vtkCellArray* lines edgeData-GetLines(); vtkIdType npts; const vtkIdType* pts; for (lines-InitTraversal(); lines-GetNextCell(npts, pts); ) { for (vtkIdType i 0; i npts; i) protectedIds.insert(pts[i]); }這個集合可以作為約束簡化的輸入。把特征邊當(dāng)成簡化保形的起點而不是終點才是它真正有價值的使用方式。5.3 把特征提取模塊嵌進Qt界面實時調(diào)參桌面工具里特征提取經(jīng)常需要一個帶滑塊的調(diào)參面板。VTK 9時代推薦用QVTKOpenGLNativeWidget配合vtkGenericOpenGLRenderWindow而不是老舊的QVTKWidget。核心代碼長這樣from PyQt5.QtWidgets import QSlider, QVBoxLayout, QWidget from vtk.qt.QVTKOpenGLNativeWidget import QVTKOpenGLNativeWidget import vtk class FeatureWidget(QWidget): def __init__(self, mesh): super().__init__() self.vtk_widget QVTKOpenGLNativeWidget() self.render_window vtk.vtkGenericOpenGLRenderWindow() self.vtk_widget.setRenderWindow(self.render_window) self.feature vtk.vtkFeatureEdges() self.feature.SetInputData(mesh) self.feature.FeatureEdgesOn() self.feature.BoundaryEdgesOn() self.feature.SetFeatureAngle(30.0) self.feature.Update() self.slider QSlider() self.slider.setRange(1, 90) self.slider.setValue(30) self.slider.valueChanged.connect(self.update_angle) layout QVBoxLayout() layout.addWidget(self.vtk_widget) layout.addWidget(self.slider) self.setLayout(layout) self.vtk_widget.GetRenderWindow().Render() def update_angle(self, value): self.feature.SetFeatureAngle(float(value)) self.feature.Update() self.vtk_widget.GetRenderWindow().Render()這里有幾個細(xì)節(jié)值得注意。第一QVTKOpenGLNativeWidget必須綁定一個vtkGenericOpenGLRenderWindow實例不能直接塞一個默認(rèn)RenderWindow進去否則OpenGL上下文會出問題。第二滑塊拖動過程要加防抖不必每個像素都觸發(fā)重算簡單的做法是只響應(yīng)release事件或者用定時器合并連續(xù)變化。第三如果后臺線程里觸發(fā)了vtkFeatureEdges重算記得把結(jié)果回傳到Qt主線程后再調(diào)用Render不要跨線程直接操作渲染窗口。5.4 特征邊作為分割、測量與配準(zhǔn)的起點特征邊提取完下游能接的東西非常多。用vtkConnectivityFilter對提取出的特征線做連通成分分析可以把模型表面按“被特征邊圍成的區(qū)域”切成獨立區(qū)塊作為交互式分割的初始掩膜。做測量工具時拾取一條特征邊后取該邊兩個鄰接面片的法向量計算夾角把這個角度回填到界面里就是一個現(xiàn)成的折角測量模塊。把特征邊點集轉(zhuǎn)成頂點數(shù)據(jù)后再走點云處理管線也常用于跨模態(tài)幾何特征對齊的預(yù)實驗。我實際項目里還遇到過一個有意思的用法把特征邊按長度排序過濾掉太短的碎線后剩下的長特征線直接作為模型“骨架”用來做對象姿態(tài)估計的粗對齊。整個過程不復(fù)雜但效果比單純用原始網(wǎng)格做全局配準(zhǔn)要快得多因為特征線的點數(shù)比面片數(shù)量少兩三個數(shù)量級。說到底vtkFeatureEdges產(chǎn)出的是底層拓?fù)湫畔⒄嬲膬r值要靠后續(xù)算法來放大它負(fù)責(zé)把“哪里是結(jié)構(gòu)線”說清楚剩下的交給業(yè)務(wù)邏輯。最后說點個人的使用體會。做了幾年網(wǎng)格處理我最大的感觸是vtkFeatureEdges這個類本身很小、參數(shù)也少真正拉開差距的往往是預(yù)處理和后續(xù)處理。很多人把特征提取看成一步濾鏡操作拿著原始網(wǎng)格直接懟進去結(jié)果不符預(yù)期就到處找替代方案。其實網(wǎng)格數(shù)據(jù)類型千差萬別臟活永遠(yuǎn)在前后兩端前端做好clean、smooth、法向統(tǒng)一后端做線條連通、過濾、分類中間那一小步交給vtkFeatureEdges是最省心的。如果你正在為棱線提不出來、輪廓斷成碎片頭疼我建議先檢查網(wǎng)格質(zhì)量再耐心調(diào)FeatureAngle最后才考慮換算法。順便分享一個小習(xí)慣每次修改參數(shù)后把輸出記錄成一行日志哪怕簡單兩句話積累幾十組數(shù)據(jù)之后你對“什么樣的網(wǎng)格配什么樣的閾值”就會形成直覺。這個小類可能永遠(yuǎn)不會登上算法頭條但它是整個網(wǎng)格處理鏈路上最可靠的基石之一。