一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何在樹(shù)莓派 AI HAT+上進(jìn)行YOLO姿態(tài)估計(jì)?

上海晶珩電子科技有限公司 ? 2025-07-20 20:34 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

大家好,接下來(lái)會(huì)為大家開(kāi)一個(gè)樹(shù)莓派5和YOLO的連載專(zhuān)題。

內(nèi)容包括四個(gè)部分:

在樹(shù)莓派5上使用YOLO進(jìn)行物體和動(dòng)物識(shí)別-入門(mén)指南

在樹(shù)莓派5上開(kāi)啟YOLO姿態(tài)估計(jì)識(shí)別之旅!

如何在樹(shù)莓派 AI HAT+上進(jìn)行YOLO目標(biāo)檢測(cè)?

如何在樹(shù)莓派 AI HAT+上進(jìn)行YOLO姿態(tài)估計(jì)?

今天是第四部分:如何在樹(shù)莓派 AI HAT+上進(jìn)行YOLO姿態(tài)估計(jì)?

如果大家對(duì)這個(gè)專(zhuān)題感興趣,記得關(guān)注樹(shù)莓派開(kāi)發(fā)者,這樣你將會(huì)第一時(shí)間收到我們的內(nèi)容更新通知。

在本指南中,我們將介紹如何在樹(shù)莓派AI HAT上進(jìn)行YOLO姿態(tài)估計(jì)設(shè)置,并探討如何將其與您自己的Python代碼結(jié)合使用,以便在項(xiàng)目中應(yīng)用姿態(tài)估計(jì)。我們將介紹如何安裝所需的硬件和固件,以及如何設(shè)置和使用姿態(tài)估計(jì)流程。完成本指南后,您將了解整個(gè)設(shè)置過(guò)程,并掌握我們準(zhǔn)備好的三個(gè)不同示例腳本。借助這些演示腳本,我們將使用手勢(shì)控制媒體播放器、根據(jù)手臂角度控制伺服電機(jī),以及用身體玩水果忍者游戲。

與我們的其他大多數(shù)計(jì)算機(jī)視覺(jué)指南一樣,本指南也十分有趣,讓我們開(kāi)始吧!

考驗(yàn)?zāi)阌⒄Z(yǔ)聽(tīng)力的時(shí)候到了,你可以選擇觀看視頻演示。


目錄:

所需材料

硬件組裝

安裝樹(shù)莓派操作系統(tǒng)

安裝AI HAT軟件和Python流程

運(yùn)行姿態(tài)估計(jì)演示

示例代碼1:基礎(chǔ)姿態(tài)估計(jì)代碼

更改相機(jī)分辨率

示例代碼2:手勢(shì)媒體控制

示例代碼3:伺服控制

示例代碼4:水果忍者

接下來(lái)做什么?


所需材料

要完成本指南,您需要準(zhǔn)備以下物品:

樹(shù)莓派5 - 2GB或更大容量的型號(hào)均可。

AI HAT+板 - 本指南適用于13 TOPS和26 TOPS兩個(gè)版本。TOPS是衡量AI加速器速度的指標(biāo),因此26 TOPS版本的AI Hat+速度大約是13 TOPS版本的兩倍。這意味著26 TOPS版本能夠以更高的幀率運(yùn)行比13 TOPS版本更復(fù)雜、更強(qiáng)大的模型。

引腳擴(kuò)展器(視情況而定)- AI Hat+附帶了一個(gè)樹(shù)莓派引腳擴(kuò)展器,但通常長(zhǎng)度不足以完全穿過(guò)HAT。如果您打算在樹(shù)莓派上插入其他硬件或以其他方式使用引腳,則需要一個(gè)這樣的擴(kuò)展器來(lái)訪問(wèn)它們。

樹(shù)莓派攝像頭模塊 - 我們使用的是攝像頭模塊V3,但幾乎任何官方攝像頭模塊均可使用。

攝像頭適配器線 - 樹(shù)莓派5使用的CSI攝像頭線尺寸與之前型號(hào)不同,您的攝像頭可能附帶的是較舊的寬線,因此請(qǐng)仔細(xì)檢查。攝像頭模塊V3肯定需要一根適配器線。您還可以選擇更長(zhǎng)的線,如300mm和500mm!

散熱解決方案 - 對(duì)于樹(shù)莓派5本身,我們使用的是主動(dòng)散熱器。雖然AI Hat+可以在不使用散熱器的情況下運(yùn)行,但如果您長(zhǎng)時(shí)間運(yùn)行它,一個(gè)小型的自粘散熱片可能是一項(xiàng)值得的投資。一點(diǎn)散熱措施可能會(huì)大有幫助。

電源

Micro SD卡 - 容量至少為16GB。

顯示器和Micro-HDMI轉(zhuǎn)HDMI線

鼠標(biāo)和鍵盤(pán)

*所需物品可以直接聯(lián)系我們進(jìn)行購(gòu)買(mǎi)。


硬件安裝

步驟1:安裝引腳擴(kuò)展器

d2993e02-6565-11f0-a486-92fbcf53809c.jpg

在樹(shù)莓派上安裝任何硬件之前,請(qǐng)確保已關(guān)閉電源并斷開(kāi)與任何電源的連接。

首先將GPIO引腳擴(kuò)展器安裝在樹(shù)莓派的引腳上。如果您使用的是更長(zhǎng)的引腳擴(kuò)展器,請(qǐng)?jiān)诖颂幨褂?。注意不要彎曲這些引腳,因?yàn)樗鼈兒荛L(zhǎng),很容易彎曲。

如果您在樹(shù)莓派上使用散熱片或散熱器,現(xiàn)在是安裝的時(shí)候了。

步驟2:安裝支柱

d2b27e58-6565-11f0-a486-92fbcf53809c.jpg

安裝隨AI HAT附帶的4個(gè)支架。支架附帶4個(gè)長(zhǎng)螺絲和4個(gè)短螺絲,使用哪一種都無(wú)妨。

步驟3:連接PCIe線

d2c09ccc-6565-11f0-a486-92fbcf53809c.jpg

要將HAT上的PCIe電纜安裝到樹(shù)莓派上,請(qǐng)先抬起樹(shù)莓派PCIe插槽上的棕色卡扣。將電纜插入插槽,確保其牢固且垂直地固定在插槽內(nèi)。然后將卡扣推回原位以固定電纜。

注意:避免過(guò)度彎曲或扭曲此電纜,因?yàn)樗赡茌^為脆弱。

步驟4:放置AI HAT

d2df9460-6565-11f0-a486-92fbcf53809c.jpg

現(xiàn)在將HAT滑動(dòng)到針腳延長(zhǎng)器上,直到它平放在支架上。在此過(guò)程中請(qǐng)小心不要損壞PCIe電纜。

您的樹(shù)莓派在HAT下方可能會(huì)露出部分GPIO接口——這是正?,F(xiàn)象。

步驟5:安裝攝像頭

d2ecacfe-6565-11f0-a486-92fbcf53809c.jpg

相機(jī)使用的連接器與PCIe連接器采用類(lèi)似的卡扣式連接設(shè)計(jì)。在相機(jī)和樹(shù)莓派的連接器插槽上,先抬起卡扣,將電纜插入并確保其垂直對(duì)齊,然后將連接器按下固定到位。

步驟6:擰緊螺絲

d3087182-6565-11f0-a486-92fbcf53809c.jpg

最后,用剩下的4顆螺絲將HAT固定好。如果你選擇使用自粘式散熱片在AI HAT上,將其放置在電路板中央的銀色處理單元上。

就這樣,我們完成了!


安裝樹(shù)莓派操作系統(tǒng)

d312fc2e-6565-11f0-a486-92fbcf53809c.png

首先,我們需要將樹(shù)莓派操作系統(tǒng)安裝到Micro SD卡上。使用樹(shù)莓派燒錄工具,選擇樹(shù)莓派5作為設(shè)備,選擇樹(shù)莓派 OS(64位)作為操作系統(tǒng),并選擇您的microSD卡作為存儲(chǔ)設(shè)備。

https://www.raspberrypi.com/software/

注意:在MicroSD卡上安裝樹(shù)莓派操作系統(tǒng)將清除卡上的所有數(shù)據(jù)。

此過(guò)程可能需要幾分鐘時(shí)間來(lái)下載操作系統(tǒng)并安裝。完成后,將卡插入樹(shù)莓派并啟動(dòng)。樹(shù)莓派將進(jìn)行首次安裝,請(qǐng)確保將其連接到互聯(lián)網(wǎng)。


安裝AI HAT軟件和Python流程

如果您之前按照我們的目標(biāo)檢測(cè)指南設(shè)置過(guò)這些流程,則無(wú)需重復(fù)這些步驟,可以直接進(jìn)入演示代碼部分。

d32a2872-6565-11f0-a486-92fbcf53809c.jpg

首先安裝運(yùn)行AI HAT所需的固件和軟件。打開(kāi)一個(gè)新的終端窗口,首先使用以下命令更新樹(shù)莓派:

sudo aptupdate&&sudo aptfull-upgrade

在這些步驟中,系統(tǒng)可能會(huì)詢(xún)問(wèn)您是否要安裝某些內(nèi)容,只需按“y”并回車(chē)即可。

現(xiàn)在使用以下命令安裝HAT固件:

sudoapt install hailo-all

此安裝過(guò)程可能需要5到10分鐘才能完成。完成后重啟樹(shù)莓派。如果您想成為高級(jí)用戶(hù),可以在終端中輸入以下命令重啟:

reboot

現(xiàn)在我們將安裝Hailo的Python流程軟件和示例,但什么是流程呢?

與AI HAT硬件本身通信非常復(fù)雜,所需的代碼也相當(dāng)復(fù)雜。我們將設(shè)置并安裝一個(gè)姿態(tài)估計(jì)流程,它只是一組代碼和軟件,使我們能夠更輕松地與HAT交互。它本質(zhì)上是將我們更簡(jiǎn)單、更易讀的代碼轉(zhuǎn)換為后臺(tái)的所有操作,以使HAT運(yùn)行。

d333d908-6565-11f0-a486-92fbcf53809c.jpg

要安裝流程及其所需的庫(kù),首先通過(guò)在終端中輸入以下命令來(lái)復(fù)制它們的GitHub存儲(chǔ)庫(kù):

gitclonehttps://github.com/hailo-ai/hailo-rpi5-examples.git

這將在樹(shù)莓派的主文件夾中下載一個(gè)名為“hailo-rpi5-examples”的文件夾,這將是我們要使用的一個(gè)重要位置。

d3527368-6565-11f0-a486-92fbcf53809c.jpg

在安裝流程之前,我們需要使用更改目錄命令告訴終端從該文件夾中工作:

cdhailo-rpi5-examples

終端中的藍(lán)色文本顯示文件位置,表明您已成功運(yùn)行此命令。現(xiàn)在我們將運(yùn)行shell腳本安裝程序:

./install.sh

此安裝過(guò)程可能需要10 - 20分鐘,因?yàn)樗€會(huì)安裝我們將使用的所有YOLO模型。

安裝完成后,再次重啟樹(shù)莓派。


運(yùn)行姿態(tài)估計(jì)演示

讓我們運(yùn)行一些演示代碼!在之前的步驟中,我們從Hailo下載了一些示例流程以及使用這些流程的示例Python腳本。在本教程中,我們將使用姿態(tài)估計(jì)流程 - 它被稱(chēng)為“pose_estimation_pipeline.py”,位于hailo_rpi5-examples/basic_pipelines下。

d36a164e-6565-11f0-a486-92fbcf53809c.jpg

運(yùn)行這些Python腳本的最簡(jiǎn)單方法是通過(guò)終端。首先使用更改目錄命令更改終端的工作位置,這與我們之前使用的命令相同:

cdhailo-rpi5-examples

安裝步驟還創(chuàng)建了一個(gè)虛擬環(huán)境(也稱(chēng)為Venv)。這本質(zhì)上是一個(gè)隔離的虛擬工作空間,我們可以在其中安裝軟件包并進(jìn)行實(shí)驗(yàn),而不會(huì)影響樹(shù)莓派操作系統(tǒng)的其他部分。我們需要使用的所有軟件包都已安裝在此Venv中,我們可以通過(guò)在終端中輸入以下命令來(lái)進(jìn)入:

sourcesetup_env.sh

您可以通過(guò)查看終端左側(cè)括號(hào)中的Venv名稱(chēng)來(lái)確認(rèn)您正在Venv中工作,如右側(cè)圖像所示。如果您已進(jìn)入Venv并看到更改目錄命令的藍(lán)色文本,那么您現(xiàn)在就可以運(yùn)行Python腳本了。如果您關(guān)閉終端或重啟樹(shù)莓派,則需要再次運(yùn)行這些命令以返回此狀態(tài)。

d38132b6-6565-11f0-a486-92fbcf53809c.jpg

我們將運(yùn)行名為“pose_estimation.py”的演示Python代碼,該代碼位于“basic_pipelines”文件夾中,因此命令如下:

python basic_pipelines/pose_estimation.py

您應(yīng)該會(huì)看到一個(gè)新窗口彈出,顯示人們過(guò)馬路的視頻,以及YOLO姿態(tài)估計(jì)模型識(shí)別人體并進(jìn)行姿態(tài)估計(jì)。恭喜!您已成功在AI HAT上設(shè)置并運(yùn)行計(jì)算機(jī)視覺(jué)。

如圖所示,HAT應(yīng)輸出一些內(nèi)容。首先,它應(yīng)識(shí)別人體,在其周?chē)L制一個(gè)邊界框,并在邊界框上方顯示識(shí)別的置信度。然后,對(duì)于每個(gè)檢測(cè)到的人,它將在身體的特定部位放置紫色點(diǎn),并在這些點(diǎn)之間繪制線條,以可視化人的方向。這些點(diǎn)被稱(chēng)為關(guān)鍵點(diǎn),它們是我們?cè)诖a中將使用的基本元素。

d38132b6-6565-11f0-a486-92fbcf53809c.jpg

要使用攝像頭作為輸入視頻源來(lái)運(yùn)行Python代碼,我們需要將其指定為參數(shù)或選項(xiàng)。我們可以通過(guò)輸入以下命令來(lái)獲取姿態(tài)估計(jì)流程的所有可用選項(xiàng)列表:

python basic_pipelines/pose_estimation.py --help

d3aa773e-6565-11f0-a486-92fbcf53809c.jpg

這里有一些有用的選項(xiàng)可供探索,您應(yīng)該找個(gè)時(shí)間看看,但我們感興趣的是使用“--input”選項(xiàng)更改源。在這里,我們可以看到我們可以指定文件或攝像頭作為輸入,并且我們可以使用以下命令使用攝像頭模塊運(yùn)行檢測(cè)腳本:

python basic_pipelines/pose_estimation.py --input rpi


示例代碼1:基礎(chǔ)姿態(tài)估計(jì)代碼

現(xiàn)在,我們已經(jīng)使用攝像頭運(yùn)行了姿態(tài)估計(jì),讓我們深入了解如何修改此代碼以在我們的項(xiàng)目中應(yīng)用。這里涉及很多復(fù)雜性,有數(shù)千行代碼在后臺(tái)運(yùn)行,但其中大部分都在流程中完成。由于大部分操作都在后臺(tái)完成,這意味著我們只需要處理一個(gè)相當(dāng)精簡(jiǎn)且易于理解的單個(gè)文件(我們稱(chēng)之為高級(jí)代碼)。在上一節(jié)中,我們運(yùn)行了這個(gè)高級(jí)文件,它被稱(chēng)為“pose_estimation.py”。盡管它已經(jīng)簡(jiǎn)化,但仍然相當(dāng)復(fù)雜,包含許多移動(dòng)部分,因此我們進(jìn)一步簡(jiǎn)化了代碼,以便在本節(jié)中查看演示代碼。如果您想深入研究原始代碼,我們?cè)谀繕?biāo)檢測(cè)指南中對(duì)其進(jìn)行了詳細(xì)介紹 - 雖然是針對(duì)目標(biāo)檢測(cè)的,但足以讓您入門(mén)。

打開(kāi)Thonny,創(chuàng)建一個(gè)新腳本,將以下代碼粘貼進(jìn)去,然后將其保存到包含我們所有其他腳本的“basic_pipelines”文件夾中。請(qǐng)確保以.py結(jié)尾命名,以確保它保存為Python腳本。如果您需要幫助完成此步驟,視頻演示了此過(guò)程。以下是完整代碼:

importgigi.require_version('Gst','1.0')fromgi.repositoryimportGst, GLibimportosimportnumpyasnpimportcv2importhailoimportthreadingimporttimefromqueueimportQueue, Emptyfromhailo_apps_infra.hailo_rpi_commonimport( get_caps_from_pad, get_numpy_from_buffer, app_callback_class,)fromhailo_apps_infra.pose_estimation_pipelineimportGStreamerPoseEstimationApp# Import your libraries up here as usual# Inside this function is where you place the rest of your code as usualdefcustom_processing_thread(pose_estimator): # This sleep gives enough time for the HAT to fire up and start detecting - important but not ma mandatory time.sleep(2)
whileTrue: # We can call this function to get the latest position of a specific keypoint position = pose_estimator.get_body_part_coordinates('left_wrist') print(position)
# Another function but this time we input 3 different keypoints and get the angle between then angle = pose_estimator.calculate_body_part_angle('left_shoulder','left_elbow','left_wrist') print(angle)
time.sleep(0.1)# The rest of the code starts here and handles the operation of the hat and all other neccesary calculations# The hat should update all of its detection data 30 times a second.classPoseDataManager: def__init__(self): """ Manages pose estimation data across threads Allows safe access to the latest detection data """ self.latest_detection_lock = threading.Lock() self.latest_detection =None self.latest_width =None self.latest_height =None
defupdate_detection(self, detection, width, height): """ Update the latest detection data thread-safely
:param detection: Hailo detection object :param width: Frame width :param height: Frame height """ withself.latest_detection_lock: self.latest_detection = detection self.latest_width = width self.latest_height = height
defget_latest_detection(self): """ Retrieve the latest detection data thread-safely
Tuple of (detection, width, height) or (None, None, None) """ withself.latest_detection_lock: return( self.latest_detection, self.latest_width, self.latest_height )classPoseEstimator: def__init__(self, pose_data_manager): """ Initialize PoseEstimator with a PoseDataManager
:param pose_data_manager: Shared data management object """ self.pose_data_manager = pose_data_manager self.keypoints = self._get_keypoints()
def_get_keypoints(self): """Get the COCO keypoints correspondence map.""" return{ 'nose':0, 'left_eye':1, 'right_eye':2, 'left_ear':3, 'right_ear':4, 'left_shoulder':5, 'right_shoulder':6, 'left_elbow':7, 'right_elbow':8, 'left_wrist':9, 'right_wrist':10, 'left_hip':11, 'right_hip':12, 'left_knee':13, 'right_knee':14, 'left_ankle':15, 'right_ankle':16, }
defget_body_part_coordinates(self, body_part, significant_figures=4): """ Get normalized coordinates for a specific body part from latest detection
:param body_part: Name of the body part (e.g., 'left_eye') :param significant_figures: Number of decimal places to round to Tuple of normalized (x, y) coordinates or None """ # Get latest detection detection, width, height = self.pose_data_manager.get_latest_detection()
ifdetectionisNoneorwidthisNoneorheightisNone: returnNone
# If no landmarks, return None landmarks = detection.get_objects_typed(hailo.HAILO_LANDMARKS) iflen(landmarks) ==0: returnNone
# Get bbox and points bbox = detection.get_bbox() points = landmarks[0].get_points()
# Get the specific keypoint keypoint_index = self.keypoints[body_part] point = points[keypoint_index]
# Directly use the normalized coordinates from the point # Clamp the values between 0 and 1, then round to specified significant figures norm_x =round(max(0,min(1, point.x())), significant_figures) norm_y =round(max(0,min(1, point.y())), significant_figures)
return(norm_x, norm_y)
defcalculate_body_part_angle(self, point_a_name, point_b_name, point_c_name): """ Calculate angle between three body parts directly by name, returning an angle in the full 0 to 360 degree range.
:param point_a_name: First body part name (e.g., 'left_shoulder') :param point_b_name: Vertex body part name (e.g., 'left_elbow') :param point_c_name: Third body part name (e.g., 'left_wrist') Angle in degrees or None if coordinates can't be retrieved """ # Get coordinates for each body part point_a = self.get_body_part_coordinates(point_a_name) point_b = self.get_body_part_coordinates(point_b_name) point_c = self.get_body_part_coordinates(point_c_name)
# Check if any coordinates are None ifany(pointisNoneforpointin[point_a, point_b, point_c]): returnNone
# Convert to numpy arrays a = np.array(point_a) b = np.array(point_b) c = np.array(point_c)
# Calculate vectors ba = a - b bc = c - b
# Calculate angle using arctan2 for full 360-degree range angle = np.degrees(np.arctan2(np.linalg.det([ba, bc]), np.dot(ba, bc)))
# Ensure the angle is between 0 and 360 degrees ifangle returnangleclassuser_app_callback_class(app_callback_class): def__init__(self, pose_data_manager): """ Initialize with a PoseDataManager
:param pose_data_manager: Shared data management object """ super().__init__() self.pose_data_manager = pose_data_managerdefapp_callback(pad, info, user_data): # Get the GstBuffer from the probe info buffer = info.get_buffer() ifbufferisNone: returnGst.PadProbeReturn.OK # Get the caps from the pad format, width, height = get_caps_from_pad(pad) # Get the detections from the buffer roi = hailo.get_roi_from_buffer(buffer) detections = roi.get_objects_typed(hailo.HAILO_DETECTION) # Find the person detection person_detection =None fordetectionindetections: ifdetection.get_label() =="person": person_detection = detection break # If a person is detected, update the shared data ifperson_detectionisnotNone: user_data.pose_data_manager.update_detection(person_detection, width, height) returnGst.PadProbeReturn.OKif__name__ =="__main__": # Create PoseDataManager first pose_data_manager = PoseDataManager()
# Create an instance of the user app callback class with pose_data_manager user_data = user_app_callback_class(pose_data_manager)
# Create pose estimator pose_estimator = PoseEstimator(pose_data_manager)
# Start the custom processing thread processing_thread = threading.Thread( target=custom_processing_thread, args=(pose_estimator,), daemon=True ) processing_thread.start() # Run the GStreamer pipeline app = GStreamerPoseEstimationApp(app_callback, user_data) app.run()

要運(yùn)行此代碼,我們將使用與之前相同的命令行,但這次使用我們保存的文件名。我們將此代碼保存為“pose_simple.py”,因此命令如下:

python basic_pipelines/pose_simple.py --input rpi

d3bb6d96-6565-11f0-a486-92fbcf53809c.jpg

如果一切正常,您應(yīng)該會(huì)看到與之前相同的窗口彈出,但這次在shell中會(huì)額外打印兩樣?xùn)|西。這是代碼中兩個(gè)實(shí)用函數(shù)的結(jié)果 - 一個(gè)用于查找特定關(guān)鍵點(diǎn)(默認(rèn)情況下代碼會(huì)跟蹤您的左手腕)的位置,另一個(gè)用于計(jì)算3個(gè)點(diǎn)之間的角度(默認(rèn)情況下計(jì)算您的肘部角度)。

讓我們深入代碼,了解如何使用這些內(nèi)容以及它們的含義。

代碼像所有Python代碼一樣,以一個(gè)區(qū)域開(kāi)始,用于放置所有導(dǎo)入行。在此處導(dǎo)入您的庫(kù),就像您通常所做的那樣。

importgigi.require_version('Gst','1.0')fromgi.repositoryimportGst, GLibimportosimportnumpyasnpimportcv2importhailoimportthreadingimporttimefromqueueimportQueue, Emptyfromhailo_rpi_commonimport( get_caps_from_pad, get_numpy_from_buffer, app_callback_class,)frompose_estimation_pipelineimportGStreamerPoseEstimationApp# Import your libraries up here as usual

然后我們進(jìn)入這個(gè)名為“custom_processing_thread”的函數(shù)。您將在此處放置所有常規(guī)代碼。此函數(shù)內(nèi)部有一個(gè)while True循環(huán),可以像您通常使用的while True循環(huán)一樣處理,并且在其上方,您可以放置所有通常在導(dǎo)入部分之后出現(xiàn)的代碼 - 所有只運(yùn)行一次的代碼,如設(shè)置引腳和硬件、聲明變量等。非常重要的一點(diǎn)是,此部分有一個(gè)2秒的休眠,這給了HAT啟動(dòng)并開(kāi)始運(yùn)行姿態(tài)估計(jì)的時(shí)間。如果您在HAT啟動(dòng)之前嘗試獲取關(guān)鍵點(diǎn)或角度數(shù)據(jù),可能會(huì)出錯(cuò),因此這確保了不會(huì)發(fā)生這種情況。

# Inside this function is where you place the rest of your code as usualdefcustom_processing_thread(pose_estimator): # This sleep gives enough time for the HAT to fire up and start detecting - important but not ma mandatory time.sleep(2)
whileTrue: # We can call this function to get the latest position of a specific keypoint position = pose_estimator.get_body_part_coordinates('left_wrist') print(position)
# Another function but this time we input 3 different keypoints and get the angle between then angle = pose_estimator.calculate_body_part_angle('left_shoulder','left_elbow','left_wrist') print(angle)
time.sleep(0.1)

在此內(nèi)部,有兩個(gè)函數(shù)用于獲取我們打印到shell的信息。第一個(gè)函數(shù)允許您獲取特定關(guān)鍵點(diǎn)的x和y坐標(biāo)位置。因此,在代碼中,我們獲取左手腕的位置,這將檢索HAT最新計(jì)算的姿態(tài)數(shù)據(jù)(每秒約30次更新新數(shù)據(jù)):

position= pose_estimator.get_body_part_coordinates('left_wrist')

此函數(shù)可用于獲取17個(gè)可用關(guān)鍵點(diǎn)中任何一個(gè)的位置數(shù)據(jù),只需輸入關(guān)鍵點(diǎn)名稱(chēng)即可。如果您在演示代碼中向下滾動(dòng)一點(diǎn),可以找到這些關(guān)鍵點(diǎn)的列表,但為了方便起見(jiàn),這里也列出了。請(qǐng)注意,每個(gè)關(guān)鍵點(diǎn)還與一個(gè)數(shù)字相關(guān)聯(lián),您可能會(huì)遇到使用此編號(hào)系統(tǒng)的代碼,但在此代碼中不需要。

'nose':0,'left_eye':1,'right_eye':2,'left_ear':3,'right_ear':4,'left_shoulder':5,'right_shoulder':6,'left_elbow':7,'right_elbow':8,'left_wrist':9,'right_wrist':10,'left_hip':11,'right_hip':12,'left_knee':13,'right_knee':14,'left_ankle':15,'right_ankle':16,

這些關(guān)鍵點(diǎn)使用相對(duì)坐標(biāo),范圍從0到1。在橫跨屏幕的x軸上,屏幕左側(cè)為0,右側(cè)為1,中間為0.5。在上下移動(dòng)的y軸上,屏幕頂部為0,底部為1。右側(cè)圖像展示了幀中左手腕的坐標(biāo)。

d3d71870-6565-11f0-a486-92fbcf53809c.jpg

第二個(gè)函數(shù)接受3個(gè)關(guān)鍵點(diǎn)名稱(chēng),并允許您計(jì)算由身體上這3個(gè)不同部位形成的角度:

angle= pose_estimator.calculate_body_part_angle('left_shoulder','left_elbow','left_wrist')

它返回的角度是基于第二個(gè)關(guān)鍵點(diǎn)作為參考,第一個(gè)和最后一個(gè)關(guān)鍵點(diǎn)之間的角度。它也總是從攝像頭的角度順時(shí)針測(cè)量。以下是左肩、左肘和左手腕之間測(cè)量的3個(gè)角度:

d3e38bbe-6565-11f0-a486-92fbcf53809c.jpg

此部分之后是另外200行代碼,幸運(yùn)的是,您無(wú)需觸摸或理解這些代碼。所有這些代碼都用于操作HAT并運(yùn)行所有必要的計(jì)算,以便我們可以使用上述兩個(gè)函數(shù)來(lái)獲取基本數(shù)據(jù)。我們將上述所有代碼放在一個(gè)函數(shù)中的原因是,此第二部分在一個(gè)線程中運(yùn)行它。這本質(zhì)上是一種同時(shí)運(yùn)行多個(gè)代碼部分的方式 - 從此處開(kāi)始的200行代碼和我們的custom_processing_thread內(nèi)部的代碼同時(shí)運(yùn)行,當(dāng)我們調(diào)用其中一個(gè)函數(shù)時(shí),我們只是獲取這200行代碼中最新計(jì)算的數(shù)據(jù)。

這就是關(guān)于此基礎(chǔ)演示代碼您需要了解的所有內(nèi)容!對(duì)于大多數(shù)人來(lái)說(shuō),這應(yīng)該足以開(kāi)始在您自己的項(xiàng)目中應(yīng)用姿態(tài)估計(jì)。從這里開(kāi)始,我們將添加一些額外功能,并查看一些以不同方式利用此基礎(chǔ)代碼的代碼示例。


更改相機(jī)分辨率

您可能已經(jīng)注意到,攝像頭的視野相當(dāng)狹窄 - 看起來(lái)有點(diǎn)縮放,因此讓我們快速看看如何修改這一點(diǎn)。在basic_pipelines文件夾中,有一個(gè)名為“hailo_rpi_common”的文件。此文件包含HAT的一些基本操作,如攝像頭輸入分辨率。請(qǐng)注意,您在此處所做的任何更改也將影響從此文件夾中運(yùn)行的其他流程,因此,如果您還使用此流程中的目標(biāo)檢測(cè)腳本,則此處所做的更改也將影響它。

在大約第195行,您將找到負(fù)責(zé)更改攝像頭輸入分辨率的行。請(qǐng)注意,這不會(huì)更改YOLO處理的分辨率,而只是更改攝像頭最初捕獲的分辨率。默認(rèn)情況下,分辨率為1536x840,但您可以將其更改為另一個(gè)標(biāo)準(zhǔn)分辨率大小。我們?cè)谀承┓直媛氏掠龅搅诵阅軉?wèn)題和崩潰,因此您可能需要一些試錯(cuò)。但是,我們發(fā)現(xiàn)1920x1080是一個(gè)穩(wěn)定且足夠高的分辨率。以下是結(jié)果:

ifsource_type =='rpi': source_element = ( f'libcamerasrc name={name}! ' f'video/x-raw, format={video_format}, width=1920, height=1080 ! '

更改分辨率后,我們的視野會(huì)更寬,如下所示。

d40abc70-6565-11f0-a486-92fbcf53809c.jpg

在此部分中,我們可以做的另一件方便的事情是刪除占用shell空間的FPS打印輸出。在大約第385行,您將找到一個(gè)名為“on_fps_measurement”的函數(shù)(您也可以按ctrl + F搜索此函數(shù))。此行正在將FPS讀數(shù)打印到shell,您可以通過(guò)像這樣注釋掉它來(lái)禁用它:

defon_fps_measurement(self, sink, fps, droprate, avgfps): #print(f"FPS: {fps:.2f}, Droprate: {droprate:.2f}, Avg FPS: {avgfps:.2f}") returnTrue


示例代碼2:手勢(shì)媒體控制

d412c82a-6565-11f0-a486-92fbcf53809c.jpg

在此示例代碼中,我們將使用一個(gè)名為wtype的庫(kù),根據(jù)特定手勢(shì)模擬鍵盤(pán)輸入。要使用wtype,需要先安裝它。我們需要將其安裝到我們一直使用的同一個(gè)虛擬環(huán)境中。為此,我們需要確保終端處于我們通常運(yùn)行腳本之前所需的狀態(tài):

cdhailo-rpi5-examplessourcesetup_env.sh

進(jìn)入此狀態(tài)后,我們可以使用以下命令安裝wtype:

sudoapt install wtype

現(xiàn)在創(chuàng)建一個(gè)新腳本,粘貼以下代碼,并將其保存到與上一個(gè)腳本相同的basic_pipelines文件夾中。要運(yùn)行此腳本,您需要使用與之前相同的命令行,但更改為您保存此腳本的名稱(chēng)。

importgigi.require_version('Gst','1.0')fromgi.repositoryimportGst, GLibimportosimportnumpyasnpimportcv2importhailoimportthreadingimporttimefromqueueimportQueue, Emptyfromhailo_apps_infra.hailo_rpi_commonimport( get_caps_from_pad, get_numpy_from_buffer, app_callback_class,)fromhailo_apps_infra.pose_estimation_pipelineimportGStreamerPoseEstimationAppimportsubprocessdefcustom_processing_thread(pose_estimator):
# This gives enough time for the HAT to fire up and start detecting time.sleep(2)
whileTrue: # get the positions of all the relevant body parts left_wrist = pose_estimator.get_body_part_coordinates('left_wrist') right_wrist = pose_estimator.get_body_part_coordinates('right_wrist') nose = pose_estimator.get_body_part_coordinates('nose')
# if the second element (the y coordinate) of the wrists are higher than the ifleft_wrist[1] < nose[1]?and?right_wrist[1] < nose [1]:
# Pause Youtube subprocess.run(['wtype','k']) # sleep for 2 seconds so we don't trigger this hundreds of times when we raise arms time.sleep(2)
time.sleep(0.1)
classPoseDataManager: def__init__(self): """ Manages pose estimation data across threads Allows safe access to the latest detection data """ self.latest_detection_lock = threading.Lock() self.latest_detection =None self.latest_width =None self.latest_height =None
defupdate_detection(self, detection, width, height): """ Update the latest detection data thread-safely
:param detection: Hailo detection object :param width: Frame width :param height: Frame height """ withself.latest_detection_lock: self.latest_detection = detection self.latest_width = width self.latest_height = height
defget_latest_detection(self): """ Retrieve the latest detection data thread-safely
Tuple of (detection, width, height) or (None, None, None) """ withself.latest_detection_lock: return( self.latest_detection, self.latest_width, self.latest_height )classPoseEstimator: def__init__(self, pose_data_manager): """ Initialize PoseEstimator with a PoseDataManager
:param pose_data_manager: Shared data management object """ self.pose_data_manager = pose_data_manager self.keypoints = self._get_keypoints()
def_get_keypoints(self): """Get the COCO keypoints correspondence map.""" return{ 'nose':0, 'left_eye':1, 'right_eye':2, 'left_ear':3, 'right_ear':4, 'left_shoulder':5, 'right_shoulder':6, 'left_elbow':7, 'right_elbow':8, 'left_wrist':9, 'right_wrist':10, 'left_hip':11, 'right_hip':12, 'left_knee':13, 'right_knee':14, 'left_ankle':15, 'right_ankle':16, }
defget_body_part_coordinates(self, body_part, significant_figures=4): """ Get normalized coordinates for a specific body part from latest detection
:param body_part: Name of the body part (e.g., 'left_eye') :param significant_figures: Number of decimal places to round to Tuple of normalized (x, y) coordinates or None """ # Get latest detection detection, width, height = self.pose_data_manager.get_latest_detection()
ifdetectionisNoneorwidthisNoneorheightisNone: returnNone
# If no landmarks, return None landmarks = detection.get_objects_typed(hailo.HAILO_LANDMARKS) iflen(landmarks) ==0: returnNone
# Get bbox and points bbox = detection.get_bbox() points = landmarks[0].get_points()
# Get the specific keypoint keypoint_index = self.keypoints[body_part] point = points[keypoint_index]
# Directly use the normalized coordinates from the point # Clamp the values between 0 and 1, then round to specified significant figures norm_x =round(max(0,min(1, point.x())), significant_figures) norm_y =round(max(0,min(1, point.y())), significant_figures)
return(norm_x, norm_y)
defcalculate_body_part_angle(self, point_a_name, point_b_name, point_c_name): """ Calculate angle between three body parts directly by name, returning an angle in the full 0 to 360 degree range.
:param point_a_name: First body part name (e.g., 'left_shoulder') :param point_b_name: Vertex body part name (e.g., 'left_elbow') :param point_c_name: Third body part name (e.g., 'left_wrist') Angle in degrees or None if coordinates can't be retrieved """ # Get coordinates for each body part point_a = self.get_body_part_coordinates(point_a_name) point_b = self.get_body_part_coordinates(point_b_name) point_c = self.get_body_part_coordinates(point_c_name)
# Check if any coordinates are None ifany(pointisNoneforpointin[point_a, point_b, point_c]): returnNone
# Convert to numpy arrays a = np.array(point_a) b = np.array(point_b) c = np.array(point_c)
# Calculate vectors ba = a - b bc = c - b
# Calculate angle using arctan2 for full 360-degree range angle = np.degrees(np.arctan2(np.linalg.det([ba, bc]), np.dot(ba, bc)))
# Ensure the angle is between 0 and 360 degrees ifangle returnangleclassuser_app_callback_class(app_callback_class): def__init__(self, pose_data_manager): """ Initialize with a PoseDataManager
:param pose_data_manager: Shared data management object """ super().__init__() self.pose_data_manager = pose_data_managerdefapp_callback(pad, info, user_data): # Get the GstBuffer from the probe info buffer = info.get_buffer() ifbufferisNone: returnGst.PadProbeReturn.OK # Get the caps from the pad format, width, height = get_caps_from_pad(pad) # Get the detections from the buffer roi = hailo.get_roi_from_buffer(buffer) detections = roi.get_objects_typed(hailo.HAILO_DETECTION) # Find the person detection person_detection =None fordetectionindetections: ifdetection.get_label() =="person": person_detection = detection break # If a person is detected, update the shared data ifperson_detectionisnotNone: user_data.pose_data_manager.update_detection(person_detection, width, height) returnGst.PadProbeReturn.OKif__name__ =="__main__": # Create PoseDataManager first pose_data_manager = PoseDataManager()
# Create an instance of the user app callback class with pose_data_manager user_data = user_app_callback_class(pose_data_manager)
# Create pose estimator pose_estimator = PoseEstimator(pose_data_manager)
# Start the custom processing thread processing_thread = threading.Thread( target=custom_processing_thread, args=(pose_estimator,), daemon=True ) processing_thread.start() # Run the GStreamer pipeline app = GStreamerPoseEstimationApp(app_callback, user_data) app.run()

此代碼旨在解決我在工作室中遇到的一個(gè)問(wèn)題。我經(jīng)常在觀看YouTube時(shí)需要暫停,但我可能在房間的另一邊,雙手都拿著東西。此代碼通過(guò)在我將雙手舉過(guò)頭頂時(shí)按下“K”鍵(YouTube的暫停/播放快捷鍵)來(lái)解決此問(wèn)題。

d4293d9e-6565-11f0-a486-92fbcf53809c.png

以下是我們?nèi)绾涡薷幕A(chǔ)腳本以實(shí)現(xiàn)此功能。首先,我們需要導(dǎo)入subprocess庫(kù),我們將使用它來(lái)運(yùn)行wtype。像往常一樣,在頂部導(dǎo)入。

importsubprocess

然后,在custom_processing_thread中,我們保留了重要的2秒休眠。然后,在while True循環(huán)開(kāi)始時(shí),我們首先獲取左手腕、右手腕和鼻子的關(guān)鍵點(diǎn)位置:

defcustom_processing_thread(pose_estimator): # This gives enough time for the HAT to fire up and start detecting time.sleep(2) whileTrue: # get the positions of all the relevant body parts left_wrist = pose_estimator.get_body_part_coordinates('left_wrist') right_wrist = pose_estimator.get_body_part_coordinates('right_wrist') nose = pose_estimator.get_body_part_coordinates('nose')

然后,我們有一個(gè)if語(yǔ)句比較這些關(guān)鍵點(diǎn)的坐標(biāo)。當(dāng)我們使用上述函數(shù)獲取坐標(biāo)時(shí),它實(shí)際上返回一個(gè)包含兩個(gè)數(shù)字的列表,第一個(gè)是x,第二個(gè)是y。如果我們只想處理其中一個(gè)坐標(biāo),可以提取第一個(gè)或第二個(gè)元素。例如,要獲取左手腕的x坐標(biāo),我們可以使用:

left_wrist_x= left_wrist[0]

要獲取y坐標(biāo),我們可以使用:

left_wrist_y= left_wrist[1]

因此,在代碼的下一部分中,我們比較這些關(guān)鍵點(diǎn)的y坐標(biāo)(都使用[1]獲?。?,如果左手腕和右手腕的y坐標(biāo)都小于鼻子的y坐標(biāo),則按下k鍵。請(qǐng)記住,屏幕頂部為0,底部為1,因此如果y坐標(biāo)較小,則位置較高。

# if the second element (the y coordinate) of the wrists are higher than the ifleft_wrist[1] < nose[1] and right_wrist[1] < nose [1]:? ? ? ? ? ? # Pause Youtube? ? ? ? ? ? subprocess.run(['wtype',?'k'])? ? ? ? ? ? # sleep for 2 seconds so we don't trigger this hundreds of times when we raise arms? ? ? ? ? ? time.sleep(2)


示例代碼3:舵機(jī)控制

在這個(gè)演示中,我們將根據(jù)人物身上關(guān)鍵點(diǎn)所生成的角度來(lái)控制舵機(jī)。以下是完整代碼:

importgigi.require_version('Gst','1.0')fromgi.repositoryimportGst, GLibimportosimportnumpyasnpimportcv2importhailoimportthreadingimporttimefromqueueimportQueue, Emptyfromhailo_apps_infra.hailo_rpi_commonimport( get_caps_from_pad, get_numpy_from_buffer, app_callback_class,)fromhailo_apps_infra.pose_estimation_pipelineimportGStreamerPoseEstimationAppfromgpiozeroimportAngularServodefcustom_processing_thread(pose_estimator):
elbow_servo = AngularServo(18, min_pulse_width=0.0006, max_pulse_width=0.0023)
shoulder_servo = AngularServo(19, min_pulse_width=0.0006, max_pulse_width=0.0023)

# This gives enough time for the HAT to fire up and start detecting time.sleep(2)
whileTrue:
elbow_angle = pose_estimator.calculate_body_part_angle('left_shoulder','left_elbow','left_wrist') elbow_angle =max(0,min(elbow_angle,180))
shoulder_angle = pose_estimator.calculate_body_part_angle('right_shoulder','left_shoulder','left_elbow') shoulder_angle =max(0,min(shoulder_angle,180))
print(elbow_angle, shoulder_angle)

elbow_servo.angle = elbow_angle shoulder_servo.angle = shoulder_angle
time.sleep(0.05)classPoseDataManager: def__init__(self): """ Manages pose estimation data across threads Allows safe access to the latest detection data """ self.latest_detection_lock = threading.Lock() self.latest_detection =None self.latest_width =None self.latest_height =None
defupdate_detection(self, detection, width, height): """ Update the latest detection data thread-safely
:param detection: Hailo detection object :param width: Frame width :param height: Frame height """ withself.latest_detection_lock: self.latest_detection = detection self.latest_width = width self.latest_height = height
defget_latest_detection(self): """ Retrieve the latest detection data thread-safely
Tuple of (detection, width, height) or (None, None, None) """ withself.latest_detection_lock: return( self.latest_detection, self.latest_width, self.latest_height )classPoseEstimator: def__init__(self, pose_data_manager): """ Initialize PoseEstimator with a PoseDataManager
:param pose_data_manager: Shared data management object """ self.pose_data_manager = pose_data_manager self.keypoints = self._get_keypoints()
def_get_keypoints(self): """Get the COCO keypoints correspondence map.""" return{ 'nose':0, 'left_eye':1, 'right_eye':2, 'left_ear':3, 'right_ear':4, 'left_shoulder':5, 'right_shoulder':6, 'left_elbow':7, 'right_elbow':8, 'left_wrist':9, 'right_wrist':10, 'left_hip':11, 'right_hip':12, 'left_knee':13, 'right_knee':14, 'left_ankle':15, 'right_ankle':16, }
defget_body_part_coordinates(self, body_part, significant_figures=4): """ Get normalized coordinates for a specific body part from latest detection
:param body_part: Name of the body part (e.g., 'left_eye') :param significant_figures: Number of decimal places to round to Tuple of normalized (x, y) coordinates or None """ # Get latest detection detection, width, height = self.pose_data_manager.get_latest_detection()
ifdetectionisNoneorwidthisNoneorheightisNone: returnNone
# If no landmarks, return None landmarks = detection.get_objects_typed(hailo.HAILO_LANDMARKS) iflen(landmarks) ==0: returnNone
# Get bbox and points bbox = detection.get_bbox() points = landmarks[0].get_points()
# Get the specific keypoint keypoint_index = self.keypoints[body_part] point = points[keypoint_index]
# Directly use the normalized coordinates from the point # Clamp the values between 0 and 1, then round to specified significant figures norm_x =round(max(0,min(1, point.x())), significant_figures) norm_y =round(max(0,min(1, point.y())), significant_figures)
return(norm_x, norm_y)
defcalculate_body_part_angle(self, point_a_name, point_b_name, point_c_name): """ Calculate angle between three body parts directly by name, returning an angle in the full 0 to 360 degree range.
:param point_a_name: First body part name (e.g., 'left_shoulder') :param point_b_name: Vertex body part name (e.g., 'left_elbow') :param point_c_name: Third body part name (e.g., 'left_wrist') Angle in degrees or None if coordinates can't be retrieved """ # Get coordinates for each body part point_a = self.get_body_part_coordinates(point_a_name) point_b = self.get_body_part_coordinates(point_b_name) point_c = self.get_body_part_coordinates(point_c_name)
# Check if any coordinates are None ifany(pointisNoneforpointin[point_a, point_b, point_c]): returnNone
# Convert to numpy arrays a = np.array(point_a) b = np.array(point_b) c = np.array(point_c)
# Calculate vectors ba = a - b bc = c - b
# Calculate angle using arctan2 for full 360-degree range angle = np.degrees(np.arctan2(np.linalg.det([ba, bc]), np.dot(ba, bc)))
# Ensure the angle is between 0 and 360 degrees ifangle returnangleclassuser_app_callback_class(app_callback_class): def__init__(self, pose_data_manager): """ Initialize with a PoseDataManager
:param pose_data_manager: Shared data management object """ super().__init__() self.pose_data_manager = pose_data_managerdefapp_callback(pad, info, user_data): # Get the GstBuffer from the probe info buffer = info.get_buffer() ifbufferisNone: returnGst.PadProbeReturn.OK # Get the caps from the pad format, width, height = get_caps_from_pad(pad) # Get the detections from the buffer roi = hailo.get_roi_from_buffer(buffer) detections = roi.get_objects_typed(hailo.HAILO_DETECTION) # Find the person detection person_detection =None fordetectionindetections: ifdetection.get_label() =="person": person_detection = detection break # If a person is detected, update the shared data ifperson_detectionisnotNone: user_data.pose_data_manager.update_detection(person_detection, width, height) returnGst.PadProbeReturn.OKif__name__ =="__main__": # Create PoseDataManager first pose_data_manager = PoseDataManager()
# Create an instance of the user app callback class with pose_data_manager user_data = user_app_callback_class(pose_data_manager)
# Create pose estimator pose_estimator = PoseEstimator(pose_data_manager)
# Start the custom processing thread processing_thread = threading.Thread( target=custom_processing_thread, args=(pose_estimator,), daemon=True ) processing_thread.start() # Run the GStreamer pipeline app = GStreamerPoseEstimationApp(app_callback, user_data) app.run()

這段代碼被設(shè)置為控制連接到GPIO引腳18和19的兩個(gè)舵機(jī),在我們的示例中,我們用樂(lè)高積木搭建了一個(gè)由兩部分組成的機(jī)械臂,并將這兩個(gè)舵機(jī)安裝其上。隨后,代碼會(huì)獲取一個(gè)人左肩和左肘的角度,并將舵機(jī)調(diào)整至這些角度,從而使機(jī)械臂能夠模仿人的動(dòng)作。

d43fac6e-6565-11f0-a486-92fbcf53809c.jpg

為實(shí)現(xiàn)這一功能,我們首先從gpiozero庫(kù)中導(dǎo)入AngularServo,這是一個(gè)控制舵機(jī)的便捷方法:

fromgpiozeroimportAngularServo

接著,在custom_processing_thread函數(shù)中,我們?cè)O(shè)置兩個(gè)舵機(jī):

defcustom_processing_thread(pose_estimator): elbow_servo= AngularServo(18, min_pulse_width=0.0006, max_pulse_width=0.0023) shoulder_servo= AngularServo(19, min_pulse_width=0.0006, max_pulse_width=0.0023) # This gives enough time for the HAT to fire up and start detecting time.sleep(2)

然后,在我們的while True循環(huán)中,我們獲取左肘(通過(guò)左肩、左肘和左手腕關(guān)鍵點(diǎn))和左肩(通過(guò)右肩、左肩和左肘關(guān)鍵點(diǎn))的角度。在每行代碼之后,我們都設(shè)置了一個(gè)最大值和最小值的限制。這確保了我們的角度保持在0到180度之間,防止我們將像190或300這樣的角度意外輸入到舵機(jī)中,從而避免錯(cuò)誤。之后,我們只需將舵機(jī)設(shè)置為這些角度。

while True: elbow_angle = pose_estimator.calculate_body_part_angle('left_shoulder','left_elbow','left_wrist') elbow_angle =max(0,min(elbow_angle,180)) shoulder_angle = pose_estimator.calculate_body_part_angle('right_shoulder','left_shoulder','left_elbow') shoulder_angle =max(0,min(shoulder_angle,180)) print(elbow_angle, shoulder_angle) elbow_servo.angle = elbow_angle shoulder_servo.angle = shoulder_angle time.sleep(0.05)


示例代碼4:水果忍者

在這個(gè)最后的代碼示例中,我們創(chuàng)建了一個(gè)通過(guò)姿態(tài)估計(jì)控制的水果忍者游戲,所有功能都集成在這個(gè)單一的Python腳本中:

importthreadingimportqueueimportpygameimportrandomimportmathfromcollectionsimportnamedtupleimportgigi.require_version('Gst','1.0')fromgi.repositoryimportGst, GLibimportnumpyasnpimportcv2importhailofromhailo_apps_infra.hailo_rpi_commonimport(get_caps_from_pad,get_numpy_from_buffer,app_callback_class,)fromhailo_apps_infra.pose_estimation_pipelineimportGStreamerPoseEstimationAppimporttime# Game constantsWINDOW_WIDTH =900WINDOW_HEIGHT =600FPS =60GRAVITY =0.5FRUIT_TYPES = ['apple','orange','watermelon']BLADE_TRAIL_LENGTH =8POSITION_QUEUE_SIZE =1INITIAL_SPAWN_RATE =120# Higher number means slower spawningMIN_SPAWN_RATE =10# Fastest spawn rate possibleSPAWN_RATE_DECREASE =1# How much to decrease spawn rate per fruitSTARTING_LIVES =3GAME_OVER_COUNTDOWN =5# Seconds before new game starts# ColorsWHITE = (255,255,255)RED = (255,0,0)GREEN = (0,255,0)BLUE = (0,255,255)BLACK = (0,0,0)# Game objectsFruit = namedtuple('Fruit', ['x','y','vel_x','vel_y','radius','type','sliced'])BladePoint = namedtuple('BladePoint', ['x','y'])classPoseNinjaCallback(app_callback_class):def__init__(self):super().__init__()self.left_hand_pos = (WINDOW_WIDTH //4, WINDOW_HEIGHT //2)self.right_hand_pos = (3* WINDOW_WIDTH //4, WINDOW_HEIGHT //2)self.use_frame =Trueself.position_queue = queue.Queue(maxsize=POSITION_QUEUE_SIZE)classPoseNinja:def__init__(self):pygame.init()self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))pygame.display.set_caption("Pose Ninja")self.clock = pygame.time.Clock()# Initialize game stateself.reset_game()# Initialize pose estimationself.user_data = PoseNinjaCallback()self.app = GStreamerPoseEstimationApp(self.pose_callback, self.user_data)defreset_game(self):self.fruits = []self.score =0self.lives = STARTING_LIVESself.frame_count =0self.left_blade_trail = []self.right_blade_trail = []self.current_spawn_rate = INITIAL_SPAWN_RATEself.game_over =Falseself.game_over_timer =0self.running =Truedefpose_callback(self, pad, info, user_data):buffer = info.get_buffer()ifbufferisNone:returnGst.PadProbeReturn.OKroi = hailo.get_roi_from_buffer(buffer)detections = roi.get_objects_typed(hailo.HAILO_DETECTION)fordetectionindetections:ifdetection.get_label() =="person":landmarks = detection.get_objects_typed(hailo.HAILO_LANDMARKS)iflen(landmarks) !=0:points = landmarks[0].get_points()bbox = detection.get_bbox()format, width, height = get_caps_from_pad(pad)# Constants for y-axis scaling relative to frame heightY_MIN =0.22* heightY_MAX =0.78* heightY_RANGE = Y_MAX - Y_MIN# Left wrist (index 9)left_point = points[9]left_x = WINDOW_WIDTH -int((left_point.x() * bbox.width() + bbox.xmin()) * width * WINDOW_WIDTH / width)raw_y = (left_point.y() * bbox.height() + bbox.ymin()) * heightnormalized_y = (raw_y - Y_MIN) / Y_RANGEleft_y =int(normalized_y * WINDOW_HEIGHT)# Right wrist (index 10)right_point = points[10]right_x = WINDOW_WIDTH -int((right_point.x() * bbox.width() + bbox.xmin()) * width * WINDOW_WIDTH / width)raw_y = (right_point.y() * bbox.height() + bbox.ymin()) * heightnormalized_y = (raw_y - Y_MIN) / Y_RANGEright_y =int(normalized_y * WINDOW_HEIGHT)try:whilenotself.user_data.position_queue.empty():self.user_data.position_queue.get_nowait()self.user_data.position_queue.put_nowait(((left_x, left_y), (right_x, right_y)))exceptqueue.Full:passreturnGst.PadProbeReturn.OKdefspawn_fruit(self):x = random.randint(200, WINDOW_WIDTH-200)y = WINDOW_HEIGHT +50vel_x = random.uniform(-3,3)vel_y = random.uniform(-25, -16)radius = random.randint(20,40)fruit_type = random.choice(FRUIT_TYPES)returnFruit(x, y, vel_x, vel_y, radius, fruit_type,False)defupdate_fruits(self):new_fruits = []forfruitinself.fruits:ifnotfruit.sliced:new_x = fruit.x + fruit.vel_xnew_y = fruit.y + fruit.vel_ynew_vel_y = fruit.vel_y + GRAVITY# Check if fruit is droppedifnew_y > WINDOW_HEIGHT +100:ifnotfruit.sliced:self.lives -=1ifself.lives <=?0:self.game_over =?Trueself.game_over_timer = GAME_OVER_COUNTDOWN * FPS# Convert to frameselse:new_fruits.append(Fruit(new_x, new_y, fruit.vel_x, new_vel_y,fruit.radius, fruit.type,?False))self.fruits = new_fruitsdef?update_blade_trails(self):try:left_pos, right_pos = self.user_data.position_queue.get_nowait()self.user_data.left_hand_pos = left_posself.user_data.right_hand_pos = right_posexcept?queue.Empty:passself.left_blade_trail.append(BladePoint(*self.user_data.left_hand_pos))self.right_blade_trail.append(BladePoint(*self.user_data.right_hand_pos))while?len(self.left_blade_trail) > BLADE_TRAIL_LENGTH:self.left_blade_trail.pop(0)whilelen(self.right_blade_trail) > BLADE_TRAIL_LENGTH:self.right_blade_trail.pop(0)defcheck_slices(self):forblade_trailin[self.left_blade_trail, self.right_blade_trail]:iflen(blade_trail) MIN_SPAWN_RATE:self.current_spawn_rate =max(MIN_SPAWN_RATE, self.current_spawn_rate - SPAWN_RATE_DECREASE)defpoint_line_distance(self, x, y, x1, y1, x2, y2):A = x - x1B = y - y1C = x2 - x1D = y2 - y1dot = A * C + B * Dlen_sq = C * C + D * Diflen_sq ==0:returnmath.sqrt(A * A + B * B)param = dot / len_sqifparam 1:returnmath.sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2))else:returnabs(A * D - C * B) / math.sqrt(len_sq)defdraw(self):self.screen.fill(BLACK)# Draw fruitsforfruitinself.fruits:ifnotfruit.sliced:color = REDiffruit.type=='apple'else\GREENiffruit.type=='watermelon'else\(255,165,0)# Orangepygame.draw.circle(self.screen, color, (int(fruit.x),int(fruit.y)), fruit.radius)# Draw blade trailsiflen(self.left_blade_trail) >=2:pygame.draw.lines(self.screen, BLUE,False, [(p.x, p.y)forpinself.left_blade_trail],3)iflen(self.right_blade_trail) >=2:pygame.draw.lines(self.screen, GREEN,False, [(p.x, p.y)forpinself.right_blade_trail],3)# Draw score and livesfont = pygame.font.Font(None,36)score_text = font.render(f'Score:{self.score}',True, WHITE)lives_text = font.render(f'Lives:{self.lives}',True, WHITE)self.screen.blit(score_text, (10,10))self.screen.blit(lives_text, (10,50))# Draw game over countdownifself.game_over:seconds_left = self.game_over_timer // FPScountdown_text = font.render(f'New game in:{seconds_left}',True, WHITE)text_rect = countdown_text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2))self.screen.blit(countdown_text, text_rect)pygame.display.flip()defrun_pose_estimation(self):self.app.run()defrun(self):# Start pose estimation in a separate threadpose_thread = threading.Thread(target=self.run_pose_estimation)pose_thread.daemon =Truepose_thread.start()# Step 1: Wait for pose estimation to initialize (we can use a sleep or a check here)# We are ensuring pose estimation has started before opening the game windowtime.sleep(1)# Give pose estimation a bit of time to start (adjust as necessary)# Step 2: Now, create the game window after pose estimation has startedself.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))pygame.display.set_caption("Pose Ninja")# Step 3: Run the game loopwhileself.running:foreventinpygame.event.get():ifevent.type== pygame.QUIT:self.running =Falseelifevent.type== pygame.KEYDOWN:ifevent.key == pygame.K_ESCAPE:self.running =Falseifself.game_over:self.game_over_timer -=1ifself.game_over_timer <=?0:self.reset_game()else:# Spawn new fruits based on current spawn rateif?self.frame_count % self.current_spawn_rate ==?0:self.fruits.append(self.spawn_fruit())# Update game stateself.update_fruits()self.update_blade_trails()self.check_slices()# Draw everything on the game screenself.draw()# Update frame counterself.frame_count +=?1self.clock.tick(FPS)# Cleanup: Close game and pose estimation app when donepygame.quit()self.app.quit()if?__name__ ==?"__main__":game = PoseNinja()game.run()

當(dāng)我提到“我們”創(chuàng)建了這個(gè)水果忍者游戲時(shí),實(shí)際上是指我和Claude共同編寫(xiě)的。Claude是一個(gè)像Chat GPT一樣的大型語(yǔ)言模型,像大多數(shù)LLM一樣,它非常擅長(zhǎng)編寫(xiě)Python代碼。為了創(chuàng)建這個(gè)游戲,我粘貼了原始的演示代碼,并簡(jiǎn)單地要求它生成一個(gè)水果忍者游戲,使用以下提示:

d45ac1de-6565-11f0-a486-92fbcf53809c.jpg

“這是一段在樹(shù)莓派 AI HAT上運(yùn)行的姿態(tài)估計(jì)代碼。修改這段代碼,創(chuàng)建并控制一個(gè)水果忍者游戲。將左右手腕設(shè)為刀刃。讓水果跳躍并落下,包含一個(gè)生命系統(tǒng),游戲結(jié)束后在5秒倒計(jì)時(shí)后開(kāi)始新游戲。請(qǐng)讓刀刃留下軌跡。隨著時(shí)間推移,通過(guò)增加水果生成速度使游戲逐漸變得更難。為了實(shí)現(xiàn)這一點(diǎn),請(qǐng)使用Pygame,并且不需要額外的庫(kù)或資源——我應(yīng)該只需要粘貼你生成的代碼并運(yùn)行它?!?/p>

雖然需要幾次嘗試才能生成正確的代碼,并在之后進(jìn)行了一些微調(diào),但讓LLM執(zhí)行這樣的高級(jí)任務(wù)并利用這種姿態(tài)估計(jì)代碼是完全可能的,這可能超出了你的技能范圍(即使我對(duì)pygame也不是很精通)。此外,如果你希望學(xué)習(xí)這段代碼,LLM也非常擅長(zhǎng)分解和解釋它生成的代碼——只需詢(xún)問(wèn)它即可!

我們不會(huì)詳細(xì)解釋這段代碼的工作原理,因?yàn)樗喈?dāng)復(fù)雜,我們也不期望任何人都能理解。我們只是將其作為一個(gè)很酷的示例包含在內(nèi),展示了可以實(shí)現(xiàn)的功能、這段代碼的用途,以及它如何與LLM結(jié)合使用——這是2020年代“創(chuàng)客”的一項(xiàng)非常“酷”的技能。


接下來(lái)做什么?

現(xiàn)在,我們已經(jīng)設(shè)置好了樹(shù)莓派和AI HAT,并運(yùn)行了幾個(gè)示例代碼,為你提供了一些關(guān)于如何在項(xiàng)目中應(yīng)用姿態(tài)估計(jì)的想法?,F(xiàn)在唯一需要做的就是弄清楚如何利用它。我們有一些關(guān)于Pi的通用指南可以幫助你入門(mén),例如,如何控制直流電機(jī)步進(jìn)電機(jī)、舵機(jī),甚至通過(guò)繼電器控制電磁閥(你可以使用繼電器控制幾乎任何東西)。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 嵌入式
    +關(guān)注

    關(guān)注

    5152

    文章

    19678

    瀏覽量

    317774
  • 目標(biāo)檢測(cè)
    +關(guān)注

    關(guān)注

    0

    文章

    227

    瀏覽量

    16028
  • 樹(shù)莓派
    +關(guān)注

    關(guān)注

    121

    文章

    2016

    瀏覽量

    107511
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    DIY一個(gè)樹(shù)莓擴(kuò)展板

     Arduino的擴(kuò)展板被稱(chēng)為Shield,而樹(shù)莓的擴(kuò)展板則稱(chēng)作HAT(Hardware Attached on Top),意為附加在樹(shù)莓
    發(fā)表于 08-04 14:37 ?7742次閱讀

    完整指南:如何使用樹(shù)莓5、Hailo AI Hat、YOLO、Docker進(jìn)行自定義數(shù)據(jù)集訓(xùn)練?

    今天,我將展示如何使用令人印象深刻的HailoAIHat在樹(shù)莓5上訓(xùn)練、編譯和部署自定義模型。注意:文章內(nèi)的鏈接可能需要科學(xué)上網(wǎng)。HailoAIHat根據(jù)你的設(shè)置,在樹(shù)莓5的CPU
    的頭像 發(fā)表于 06-28 08:23 ?1306次閱讀
    完整指南:如何使用<b class='flag-5'>樹(shù)莓</b><b class='flag-5'>派</b>5、Hailo <b class='flag-5'>AI</b> <b class='flag-5'>Hat</b>、<b class='flag-5'>YOLO</b>、Docker<b class='flag-5'>進(jìn)行</b>自定義數(shù)據(jù)集訓(xùn)練?

    樹(shù)莓5,Raspberry Pi 5 評(píng)測(cè)

    一起關(guān)機(jī)。HAT規(guī)范的修訂版HAT+將解決這個(gè)問(wèn)題,Raspberry Pi會(huì)提供相關(guān)說(shuō)明。 我們能使用現(xiàn)有的散熱片和風(fēng)扇對(duì)樹(shù)莓5
    發(fā)表于 06-19 14:51

    MCC推出用于樹(shù)莓的MCC 118電壓測(cè)量HAT模塊

    HAT)提供8通道模擬電壓輸入,基于樹(shù)莓的數(shù)據(jù)采集/數(shù)據(jù)記錄系統(tǒng)。每張MCC 118最大采樣率為100kS/s,可以進(jìn)行單電壓點(diǎn)和電壓波形采集。最多可在單塊
    發(fā)表于 08-30 10:28

    MCC基于樹(shù)莓HAT模塊

    ),數(shù)模轉(zhuǎn)換器(DAC)或條件數(shù)字輸入和輸出(DIO)。但是,可以通過(guò)USB端口或支持SPI和I2C的GPIO的40-pin接頭擴(kuò)展這些功能。直接和樹(shù)莓GPIO進(jìn)行堆棧式連接的設(shè)備稱(chēng)為HAT
    發(fā)表于 09-05 11:45

    樹(shù)莓MCC118

    on Top),用于市場(chǎng)上最流行的單板計(jì)算機(jī)樹(shù)莓(Raspberry Pi)HAT是種附加板,帶有40W GPIO(通用輸入/輸出)連接器,符合Raspberry PiHAT規(guī)范。MCC 118
    發(fā)表于 01-21 09:22

    【POE HAT擴(kuò)展板試用連載】樹(shù)莓3B+電路板POE供電應(yīng)用

    項(xiàng)目名稱(chēng):樹(shù)莓3B+電路板POE供電應(yīng)用試用計(jì)劃:申請(qǐng)理由本人嵌入式領(lǐng)域有十余年的開(kāi)發(fā)經(jīng)驗(yàn),去年購(gòu)買(mǎi)了一塊樹(shù)莓3B+的電路板用來(lái)設(shè)計(jì)一款廚余垃圾處理機(jī)器的控制器,計(jì)劃利用這塊擴(kuò)展板
    發(fā)表于 01-09 11:49

    如何搞定樹(shù)莓AI服務(wù)開(kāi)發(fā)

    微軟專(zhuān)家教你,如何搞定樹(shù)莓 AI 服務(wù)開(kāi)發(fā)
    發(fā)表于 05-29 06:00

    樹(shù)莓ReSpeaker 2 Mics Pi HAT電路原理圖免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是樹(shù)莓ReSpeaker 2 Mics Pi HAT電路原理圖免費(fèi)下載。
    發(fā)表于 06-16 14:22 ?0次下載

    樹(shù)莓ReSpeaker 2 Mics Pi HAT的PCB圖免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是樹(shù)莓ReSpeaker 2 Mics Pi HAT的PCB圖免費(fèi)下載。
    發(fā)表于 06-16 08:00 ?0次下載
    <b class='flag-5'>樹(shù)莓</b><b class='flag-5'>派</b>ReSpeaker 2 Mics Pi <b class='flag-5'>HAT</b>的PCB圖免費(fèi)下載

    樹(shù)莓新推AI HAT+:26 TOPS高性能版本震撼登場(chǎng)

     在成功推出樹(shù)莓AI套件與AI攝像頭后,樹(shù)莓再次擴(kuò)大其A
    的頭像 發(fā)表于 11-07 13:44 ?1335次閱讀

    如何將 M.2 HAT+ 與 Raspberry Pi 5 一起使用?

    邊緣連接器。您可以連接任何使用2230或2242尺寸的設(shè)備。M.2HAT+最大可提供3A的電源輸出。M.2HAT+使用了樹(shù)莓HAT+規(guī)范
    的頭像 發(fā)表于 03-25 09:48 ?307次閱讀
    如何將 M.2 <b class='flag-5'>HAT+</b> 與 Raspberry Pi 5 一起使用?

    樹(shù)莓5上使用YOLO進(jìn)行物體和動(dòng)物識(shí)別-入門(mén)指南

    大家好,接下來(lái)會(huì)為大家開(kāi)一個(gè)樹(shù)莓5和YOLO的專(zhuān)題。內(nèi)容包括四個(gè)部分:在樹(shù)莓5上使用YOLO
    的頭像 發(fā)表于 07-17 17:16 ?169次閱讀
    在<b class='flag-5'>樹(shù)莓</b><b class='flag-5'>派</b>5上使用<b class='flag-5'>YOLO</b><b class='flag-5'>進(jìn)行</b>物體和動(dòng)物識(shí)別-入門(mén)指南

    樹(shù)莓5上開(kāi)啟YOLO姿態(tài)估計(jì)識(shí)別之旅!

    大家好,接下來(lái)會(huì)為大家開(kāi)一個(gè)樹(shù)莓5和YOLO的連載文章。內(nèi)容包括四個(gè)部分:在樹(shù)莓5上使用YOLO
    的頭像 發(fā)表于 07-18 15:31 ?124次閱讀
    在<b class='flag-5'>樹(shù)莓</b><b class='flag-5'>派</b>5上開(kāi)啟<b class='flag-5'>YOLO</b><b class='flag-5'>姿態(tài)</b><b class='flag-5'>估計(jì)</b>識(shí)別之旅!

    何在樹(shù)莓 AI HAT+上進(jìn)行YOLO目標(biāo)檢測(cè)?

    大家好,接下來(lái)會(huì)為大家開(kāi)一個(gè)樹(shù)莓5和YOLO的連載專(zhuān)題。內(nèi)容包括四個(gè)部分:在樹(shù)莓5上使用YOLO
    的頭像 發(fā)表于 07-19 08:34 ?168次閱讀
    如<b class='flag-5'>何在</b><b class='flag-5'>樹(shù)莓</b><b class='flag-5'>派</b> <b class='flag-5'>AI</b> <b class='flag-5'>HAT+</b><b class='flag-5'>上進(jìn)行</b><b class='flag-5'>YOLO</b>目標(biāo)檢測(cè)?