魔方教学系统(基于QT)
之前沒能及時復盤寫下來 現在很多想不起來,慢慢回憶著寫吧
背景
之前指導本科生做的伺服電機的機械臂解魔方機器人,老師提出能不能低成本化,就嘗試了舵機和低價相機,外邊做成黑色殼子阻絕光線的方法。最終把伺服電機機械臂替換成了兩個舵機和轉動平臺組成的執行機構,四個相機減到一個相機,實現的效果很成功。然后因為計算機解魔方有個特點,可以從一種狀態解算的任何另一種狀態,所以又衍生出了用魔方機器人教學的想法。通過把打亂的魔方交給魔方機器人,造出指定的場景,從而達到反復訓練的目的。
系統組成
下位機是一塊STM32的板卡,用來控制舵機,操作相機抓圖,把魔方的角塊和顏色編碼后通過串口傳給上位機。上位機直接就是筆記本電腦,用QT寫的完整GUI界面,包括解魔方教學步驟的圖片和視頻,對應的要機器人還原的指定狀態的選擇。確定后再傳回下位機控制舵機完成還原操作。
由于這套系統還在跟公司合作,以后可能會商業化,所以不能在這里放內部結構圖和完整代碼,有之前伺服電機解魔方機器人的圖,放一些示意一下。
學習到的技術
-
顏色識別
顏色識別采用OpenCV實現。因為魔方位置和相機位置基本固定,先采用OpenCV的圖像標定函數,標定出各個角塊的坐標點,然后采用HSV顏色模型來識別顏色。之所以不用RGB顏色模型也是考慮到外界環境可能出現的光照等原因對顏色識別造成的誤差。
在魔方拍攝與識別這一問題中只需要考慮其中的H 值與S 值即可,顯然明度的問題并不會影響到本文的顏色識別,而且它最大的優點在于可以一定程度上削弱光照條件對識別結果的影響,這樣可以大大地提高本文的識別效果。當然,忽略V 值的影響雖然不會在程序中影響到最終的識別情況,但是需要指出光照條件的變化H 值與S 值是會產生較大變化的,并不存在使用了HSV 模型就可以忽略光照條件的影響。
先把魔方還原,對每一個攝像頭進行標定。單獨啟動每一個攝像頭拍攝基準照片,根據拍攝的照片在其上用鼠標選擇取色基準點。完成每一個攝像頭的標定后開始編寫程序,將每一個攝像頭的每一個取色基準點的橫縱坐標值輸入,并同時取該點周圍的8個其他像素點,讀入9個點的數據計算平均值,將得到的平均值作為最終的輸出結果。
因為本文的魔方有一種顏色是白色的,那本文大可以單獨考慮S 值來區分這一顏色。通過對白色的面進行大量地拍攝和識別發現,白色的小塊幾乎不會出現S 值超過70的情況,所以將S 上的閾值設置在70,只要是S 值低于70時,則將其認為是白色。雖然看起來這樣的判定方法是有些單純的,但它的實際效果并不差,白色的識別準確率在作者的實驗中是最高的,在36次的實驗中白色小塊沒有一次被識別錯誤的。
對于其他的五種顏色作者則都在H 值上進行劃分。通過大量的拍攝圖像識別的情況來看,在白天的實驗室內,保持一個相對恒定的光照條件時,以下的閾值設置效果較好,如果出現因為閾值不當而導致的識別出錯問題則應該根據實際情況對閾值進行調整(實際情況來看這種操作也是比較有必要的,在上午、正午、傍晚時的閾值都需要進行一定的微調,尤其是紅色的上限值和橙色的下限值,在夜晚沒有外界自然光只有室內日光燈和機器補光燈的條件下往往需要對閾值進行較大的調整)。將紅色的閾值設置為H 值大于等于0且小于7,而橙色的閾值為大于等于7且小于33,將黃色的閾值設置為大于等于33且小于51,將綠色的閾值設置為大于等于51且小于80,將藍色的閾值設置為大于等于80且小于130,將大于等于130且小于180的部分判定為紅色(這種情況是極少出現的)。
-
解法實現
人為地解魔方的方法有很多種,例如較為常見的有層先法和CFOP 法,這兩種方法具有一個極大的優勢就是它們更容易形成讓人很快就能記住的解魔方的公式,即使是一個完全沒有接觸過三階魔方的普通人,也能很快地學會層先法解魔方的公式。
而計算機的解魔方算法就有很多種了,因為計算機是不需要考慮公式和可記憶性問題的,也不需要考慮是否會花費大量的時間去計算復雜的數學問題,只要算法在數學原理上沒有錯誤,編寫成對應的程序就能完成解魔方的任務。目前已經被開發的較為完善的有二階段算法、Thistlethwaite 算法、Jaap Scherphuis 算法等等。其中有的算法可能會檢索出最小還原路徑但犧牲的是求解的時間,有的可能求解極為迅速但可能其求解出的步驟會較多。單純如果為此次的智能機器人創意大賽中的解魔方項目取得好成績考慮的話,采用二階段算法或Thistlethwaite 算法是較為合適的,因為它們能求解獲得一個步驟不那么多的還原路徑,并且求解的速度相對較快,是兩種在速度和步驟數量上比較均衡的算法。
本文解魔方算法使用Thistlethwaite 算法。
魔方解算步驟的編碼
三階魔方具有6個顏色完全不同的面,每個面都有9個小色塊。本文將這9個色塊都按照第一行為1,2,3號,第二行為4,5,6號,第三行為7,8,9號進行編號,在旋轉魔方時魔方本身的一個特點就是:每一個面最中間的那個5號色塊是并不會轉動的。因此只要保持魔方本身的姿態不變化,讓某一個顏色的色塊一直朝向某一個方向,就可以用一組字母來描述每個面。本文定義了U(up)、D(down)、L(left)、R(right)、F(front)、B(behind) 這幾個字母來描述6個面,同時本文規定,對于一個標準三階魔方其U 面為黃色,D 面為白色,L 面為橙色,R 面為紅色,F 面為藍色,B 面為綠色。至此,本文已經完成了對魔方每個面的定義,每一個魔方只要通過這種擺放姿勢擺放,給出的一個還原路徑的執行方式就唯一。
在規定了面的名稱后再對面的旋轉進行規定。顯然每個面都只能順、逆時針旋轉90度和旋轉180度,本文將其定義為“1”,“3”,“2”三個數字。需要注意的是,這里的“順時針”和“逆時針”是指當你面向這個面時的順時針和逆時針。舉個例子,將一個魔方的F 面朝向你的臉,此時的F 面順時針旋轉90度就是將1號色塊轉到3號色塊的位置這樣旋轉,而此時的B 面順時針旋轉則需要你將魔方的B 面朝向你的臉,然后將1號色塊轉到3號色塊位置這樣旋轉。
通過對面的名稱和旋轉的規定,本文得到了U、D、L、R、F、B 六個字母和1、3、2這三個數字,將其組合就得到了F1、U2、D3等等這樣的組合,這種組合形成的一系列旋轉動作就是本文的還原路徑了。例如F1 U2 D3 R3 U2 L1 U1這個還原路徑,很顯然,如果你將F 面面朝自己,這個還原路徑將變成一個唯一的動作。
Thistlethwaite 算法是由Morwen B. Thistlethwaite 這位數學家設計并以他的名字進行命名的一種計算機解魔方算法[11],整個解魔方的算法分為4個階段,每個階段完成一些工作來限制小塊的位置,當魔方的每一個小塊都只剩下一個可能的位置時就完成了魔方的還原了[6]。
使用這種算法可以得到一種步驟極少的還原魔方的路徑,但是由于其四個還原步驟都相當的復雜,并且需要大量的參考表(公式),作為一個普通的人類是完全無法使用這種方法來解魔方的,因為它的參考表(公式)多到根本無法記憶(僅僅第一個還原階段的位置數量就多達4.33*10 19種)。
對于這個算法的四個步驟做以下的簡單描述:
第一步,先進行幾步旋轉來使得魔方的狀態變成不使用U1、U3、D1和D3這四種動作就可以完成復原的狀態。這個過程通常需要不超過十步來完成。
第二步,再將魔方的狀態調整到不需要F1、F3、B1和B3這四種動作就可以完成復原的狀態。這個過程的步驟數量或多或少,最多可能會出現十幾步。
第三步,將魔方調整到只需要F2、B2、L2、R2、U2和D2動作就能完成復原的狀態。這個過程所需要的步驟數量也是不確定的,要根據魔方的實際狀態來看。通常情況下也是需要十幾步的。
第四步,只使用F2、B2、L2、R2、U2和D2動作完成最后的調整,讓所有小塊可能出現的位置都變成唯一位置,完成魔方的復原。
整個過程看起來似乎并不是很難,簡單來說就是在逐次地限制小塊的位置可能性,讓小塊可能存在的位置數量越來越少,先限制U 面和D 面的小塊,再限制F 面和B 面的小塊,最后限制L 面和R 面的小塊,當完成全部限制后,在U 面和D 面都只會存在有兩種顏色的小塊,F 面和B 面以及L 面和R 面也是如此,然后就可以通過F2、B2、L2、R2、U2 和D2 動作就能完成還原了。但是實際上執行這四個步驟時所需要花費的計算量是極為龐大的,下面的這張表格中顯示的就是執行四個步驟時需要的一些數據量。
從數學的角度上來看,它實際上是一個嵌套組的序列,算法的每一個階段都是一張查找表,用來尋找到商陪集空間中每一個元素的解。而最后一列中顯示的就是這個陪集空間的順序,也就是每一個階段的查找表的大小。至于這個因子數量的計算公式,這和這種算法在限制不同面的小塊時是如何限制的有關。[6]具體的一些數學原理就非常深奧了,涉及到太多作者并沒有接觸過的更為高深和專業數學知識,在此就不多做分析和討論了。
在第一階段時,目標是限制U 面與D 面都只能采用U2和D2的動作來進行后續的調整,其實它所做的工作在魔方層面來看就是修正魔方的棱塊的方向,因為要求U 面與D 面都只能轉半圈而不能轉四分之一圈,所以就決定了是不可能再調整四邊棱塊的了,通過這種限制就可以完成將四邊棱塊限制在一個完全正確的位置上。
在第二階段時,目標是在第一階段的基礎上開始限制F 面與B 面的小塊。如果要求在F、B、U 和D 面都只能使用半圈的旋轉方式而不能使用四分之一圈的旋轉方式,那么這階段就可以完成對于八個角塊的位置確定,并對這八個角塊的方向進行確定,讓角塊和第一階段確定的棱塊完成正確的拼接。在完成第二階段后,八個角塊和四個棱塊都會進入位置確定,角塊方向不定的一種狀態,但即使是方向不定但因為魔方本身的結果和角塊的顏色,它們依然會受到移動限制。
在第三階段,開始限制剩下的L 面與R 面的小塊,目的是完成對所有面上小塊的限制,讓每一個面都只有“相對顏色”(例如F 面為藍色,B 面為綠色,則在完成第三階段后,F 面和B 面都只會存在藍色與綠色兩種顏色,絕不會出現其他的4種顏色在這兩個面中)。在此種狀態下,所有的角塊和棱塊都已經進入了正確的位置,只需要在進行U2、D2、F2、B2、L2和R2這六種動作就能將魔方徹底地復原。
第四個階段就是只使用旋轉半圈的六種動作將魔方徹底地復原。
很顯然這個算法的搜索方法看起來很美好,但它使用的查找表實在是太大了,及時是計算機直接去運行也會有不小的計算壓力,所以就需要想辦法來縮小這個檢查表。為了減小這個檢查表,Tistlethwaite 嘗試簡化了初始的這種搜索方法,這也是為什么有時他需要的實際還原步驟是比這個商空間的“直徑”(搜索出的最佳的還原路徑)要多一些的原因。在之后他的一些學生針對這個問題進行了更深層次的研究,他們對這些嵌套組進行了完整的分析,然后改進了算法,讓搜索出的最佳還原路徑降低到五十步。再隨后,他們繼續研究了每一個階段的搜索算法,對每一個階段的搜索算法又進行了全面而深入的分析,將最佳還原路徑降低到了45步。再之后又有一名叫做Hans Kloosterman 的人繼續改進了第三階段和第四節段的算法,成功將最佳還原路徑的步驟降低到了42步,這個42步被實現的時間是在1991年[6]。再之后計算機的運算能力開始大幅度提高,借助運算性能更強的計算機,數學家Mike Reid 通過更復雜的數學分析得出的結論是從第一階段到第二階段理論上需要12步即可完成,第三階段和第四階段理論上需要18步即可完成,將其組合起來后理論上30步之內就可以完成用這種算法來求解任何一個打亂的三階魔方。
-
全局變量的使用
當時對面向對象理解不深刻,魔方的狀態,因為在魔方解算里也用到,在串口通訊時也要用到,就定義成了全局變量。也順便記錄一下用全局變量的方法:
extern char color_sides[6][9];在頭文件通過extern 聲明全局變量,其他地方需要使用時,只需include進相應的頭文件即可
但C++不提倡使用全局變量
因為全局變量bai容易導致代碼的可復用性下降,以及對象管理的困難。
試想,如果某個類使用了全局變量,則移植該類的時候,必須將全局變量也一起移植。更可怕的是,如果這個全局變量還是一個對象,并且初始化也在不同的類中實現,那么所有這些代碼將被永久捆綁在一起,無法分離了。任何一個與此全局變量相關聯的代碼一旦有改動,即可對其他使用該變量的代碼產生不可預知的影響。
影響函數的封裝性能:我們肯定是希望我們寫的函數具有重入性,就如一個黑盒子一般,只 通過函數參數就能得到返回,內部 實現要獨立,但是如果函數中使用了全局變量,這勢必就破壞了函數的封裝性,會造成對全局變量的依賴。
1 全局變量是很好。
2 但是有缺點:容易被修改錯,尤其在工程很大的時候。
3 使用時一定要控制好全局變量的修改。
4 不過小工程小項目使用全局變量是很方便的!
1.全局變量在程序執行過程中一直占用存儲單元,耗費空間。
2.使函數的通用性、移植性降低了,耦合性變強了。
3.使程序的可讀性降低,難以判斷全局變量在一定時刻的具體值。
重新構建你的數據結構,把公有數據成員抽離出來,單獨做成模塊,提供一個接口對其操作。
如果確實有大量數據需要共享的話,建議還是用單獨的類封裝一下。其實類的使用,個人認為主要還是邏輯上清晰為第一原則
-
播放器
因為要演示解魔方教學視頻,所以用QT寫了一個播放器。實現的功能主要有播放本地視頻,暫停,進度條拖動。
用到的QT庫:
QMediaplayer:用于解析音頻文件和視頻文件。
使用QMediaplayer,除了需要添加必要的頭文件之外,還需要在.pro(Qt的工程配置文件)添加QT += multimedia。下面解析有關QMediaplayer的相關知識。
QMediaPlayer播放視頻要在界面上顯示出來,還需要其他類進行輔助,比如QVideoWidget。
QVideoWidget:QVideoWidget繼承自QWidget,所有它可以作為一個普通窗口部件進行顯示,也可以嵌入到其他窗口。將QVideoWidget指定為QMediaPlayer的視頻輸出窗口后,就可以顯示播放的視頻畫面。
如果播放無圖像,并報錯:
DirectShowPlayerService::doRender: Unresolved error code 80040266原因:
Qt 中的多媒體播放,底層是使用DirectShowPlayerService,所以安裝一個DirectShow解碼器,例如LAV Filters,就可以解決運行出錯問題
另外為了實現進度條拖動跳轉功能,又用QSlider和事件過濾器單獨寫了個進度條組件加到下面。通過信號槽與播放器連接,一旦拖動進度條釋放鼠標后就發出信號量,播放器里接收,通過player->setpostion設定進度。
串口通訊
也用了QT庫。網上很多相關寫法,就不再贅述了。
#include
#include
記得配置文件.pro里要加QT += serialport
有個需要注意的地方,因為傳回來的魔方數據較長,在觸發串口接收函數后,要先加上:
while (m_serialPort->waitForReadyRead(500));延時等待一會兒,不然會出現數據不全的情況。
-
線程
因為解魔方算法需要一定時間,所以給它專門開個線程處理。
注意幾個點:
線程處理函數里不能操作圖形界面,需要操作,要發信號量出來,在主線程里操作。
線程不能指定父對象,關窗口時記得delete
QT的線程真的很簡便:
t = new QThread(this); solve = new Solution; solve->moveToThread(t); t->start();這就完成了,只要Solution類也繼承自QObject即可。
記得析構的時候,把線程退出,并且delete掉。
-
互斥鎖
因為有時候識別有錯誤,會出現解不出來的情況,所以需要定時檢測算法有沒有結果。本來想直接寫個標志位解決,但有個問題,可能查查看的時候,剛好那邊在寫,而且也分處兩個不同的線程,所以想到加個互斥鎖解決。
在Qt的多線程控制中,互斥量的訪問最簡單的控制是添加一個mutex鎖,對一個函數或者變量鎖定。
如果QMutex::lock()得不到這個鎖,那么它將會一直等直到得到該鎖為止,而另一個方法QMutex::tryLock()可以檢測當前是否可以得到這個鎖,如果可以得到則返回1,否則返回0(不會一直等,但如果可以得到鎖,那就拿到鎖,不會光判斷而不獲取鎖),該函數只執行一次,不會一直等到得到鎖為止。
最后就是設計模式的探討,不然情景選擇按鍵下全是switch,這個之后再補充。
相關代碼因為項目還在進行中,而且將來要商業化,所以不能放上來了。
總結
通過這次又對QT熟悉一點,開發更加迅速,而且也算把師弟領進門了。這個項目本身也很有趣,開發的過程中能夠樂在其中,并且也很有挑戰性,最后慢慢解出魔方,速度越來越快,真是感覺一切都值了。
參考文獻:
http://bbs.mf8-china.com/forum.php?mod=viewthread&tid=38810
https://www.cnblogs.com/sixbeauty/p/3790693.html
https://www.cnblogs.com/dupengcheng/p/7205527.html
https://blog.csdn.net/qq_36969386/article/details/85072605
總結
以上是生活随笔為你收集整理的魔方教学系统(基于QT)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 尚不成熟的单个摄像头触屏方案
- 下一篇: 计算机对水利方面的影响,计算机技术对于水