大家好,接下來會為大家開一個樹莓派5和YOLO的連載專題。
內容包括四個部分:
在樹莓派5上使用YOLO進行物體和動物識別-入門指南
在樹莓派5上開啟YOLO姿態估計識別之旅!
如何在樹莓派 AI HAT+上進行YOLO目標檢測?
如何在樹莓派 AI HAT+上進行YOLO姿態估計?
今天是第四部分:如何在樹莓派 AI HAT+上進行YOLO姿態估計?
如果大家對這個專題感興趣,記得關注樹莓派開發者,這樣你將會第一時間收到我們的內容更新通知。
在本指南中,我們將介紹如何在樹莓派AI HAT上進行YOLO姿態估計設置,并探討如何將其與您自己的Python代碼結合使用,以便在項目中應用姿態估計。我們將介紹如何安裝所需的硬件和固件,以及如何設置和使用姿態估計流程。完成本指南后,您將了解整個設置過程,并掌握我們準備好的三個不同示例腳本。借助這些演示腳本,我們將使用手勢控制媒體播放器、根據手臂角度控制伺服電機,以及用身體玩水果忍者游戲。
與我們的其他大多數計算機視覺指南一樣,本指南也十分有趣,讓我們開始吧!
考驗你英語聽力的時候到了,你可以選擇觀看視頻演示。
目錄:
所需材料
硬件組裝
安裝樹莓派操作系統
安裝AI HAT軟件和Python流程
運行姿態估計演示
示例代碼1:基礎姿態估計代碼
更改相機分辨率
示例代碼2:手勢媒體控制
示例代碼3:伺服控制
示例代碼4:水果忍者
接下來做什么?
所需材料
要完成本指南,您需要準備以下物品:
樹莓派5 - 2GB或更大容量的型號均可。
AI HAT+板 - 本指南適用于13 TOPS和26 TOPS兩個版本。TOPS是衡量AI加速器速度的指標,因此26 TOPS版本的AI Hat+速度大約是13 TOPS版本的兩倍。這意味著26 TOPS版本能夠以更高的幀率運行比13 TOPS版本更復雜、更強大的模型。
引腳擴展器(視情況而定)- AI Hat+附帶了一個樹莓派引腳擴展器,但通常長度不足以完全穿過HAT。如果您打算在樹莓派上插入其他硬件或以其他方式使用引腳,則需要一個這樣的擴展器來訪問它們。
樹莓派攝像頭模塊 - 我們使用的是攝像頭模塊V3,但幾乎任何官方攝像頭模塊均可使用。
攝像頭適配器線 - 樹莓派5使用的CSI攝像頭線尺寸與之前型號不同,您的攝像頭可能附帶的是較舊的寬線,因此請仔細檢查。攝像頭模塊V3肯定需要一根適配器線。您還可以選擇更長的線,如300mm和500mm!
散熱解決方案 - 對于樹莓派5本身,我們使用的是主動散熱器。雖然AI Hat+可以在不使用散熱器的情況下運行,但如果您長時間運行它,一個小型的自粘散熱片可能是一項值得的投資。一點散熱措施可能會大有幫助。
Micro SD卡 - 容量至少為16GB。
顯示器和Micro-HDMI轉HDMI線
鼠標和鍵盤
*所需物品可以直接聯系我們進行購買。
硬件安裝
步驟1:安裝引腳擴展器
在樹莓派上安裝任何硬件之前,請確保已關閉電源并斷開與任何電源的連接。
首先將GPIO引腳擴展器安裝在樹莓派的引腳上。如果您使用的是更長的引腳擴展器,請在此處使用。注意不要彎曲這些引腳,因為它們很長,很容易彎曲。
如果您在樹莓派上使用散熱片或散熱器,現在是安裝的時候了。
步驟2:安裝支柱
安裝隨AI HAT附帶的4個支架。支架附帶4個長螺絲和4個短螺絲,使用哪一種都無妨。
步驟3:連接PCIe線
要將HAT上的PCIe電纜安裝到樹莓派上,請先抬起樹莓派PCIe插槽上的棕色卡扣。將電纜插入插槽,確保其牢固且垂直地固定在插槽內。然后將卡扣推回原位以固定電纜。
注意:避免過度彎曲或扭曲此電纜,因為它可能較為脆弱。
步驟4:放置AI HAT
現在將HAT滑動到針腳延長器上,直到它平放在支架上。在此過程中請小心不要損壞PCIe電纜。
您的樹莓派在HAT下方可能會露出部分GPIO接口——這是正常現象。
步驟5:安裝攝像頭
相機使用的連接器與PCIe連接器采用類似的卡扣式連接設計。在相機和樹莓派的連接器插槽上,先抬起卡扣,將電纜插入并確保其垂直對齊,然后將連接器按下固定到位。
步驟6:擰緊螺絲
最后,用剩下的4顆螺絲將HAT固定好。如果你選擇使用自粘式散熱片在AI HAT上,將其放置在電路板中央的銀色處理單元上。
就這樣,我們完成了!
安裝樹莓派操作系統
首先,我們需要將樹莓派操作系統安裝到Micro SD卡上。使用樹莓派燒錄工具,選擇樹莓派5作為設備,選擇樹莓派 OS(64位)作為操作系統,并選擇您的microSD卡作為存儲設備。
https://www.raspberrypi.com/software/
注意:在MicroSD卡上安裝樹莓派操作系統將清除卡上的所有數據。
此過程可能需要幾分鐘時間來下載操作系統并安裝。完成后,將卡插入樹莓派并啟動。樹莓派將進行首次安裝,請確保將其連接到互聯網。
安裝AI HAT軟件和Python流程
如果您之前按照我們的目標檢測指南設置過這些流程,則無需重復這些步驟,可以直接進入演示代碼部分。
首先安裝運行AI HAT所需的固件和軟件。打開一個新的終端窗口,首先使用以下命令更新樹莓派:
sudo aptupdate&&sudo aptfull-upgrade
在這些步驟中,系統可能會詢問您是否要安裝某些內容,只需按“y”并回車即可。
現在使用以下命令安裝HAT固件:
sudoapt install hailo-all
此安裝過程可能需要5到10分鐘才能完成。完成后重啟樹莓派。如果您想成為高級用戶,可以在終端中輸入以下命令重啟:
reboot
現在我們將安裝Hailo的Python流程軟件和示例,但什么是流程呢?
與AI HAT硬件本身通信非常復雜,所需的代碼也相當復雜。我們將設置并安裝一個姿態估計流程,它只是一組代碼和軟件,使我們能夠更輕松地與HAT交互。它本質上是將我們更簡單、更易讀的代碼轉換為后臺的所有操作,以使HAT運行。
要安裝流程及其所需的庫,首先通過在終端中輸入以下命令來復制它們的GitHub存儲庫:
gitclonehttps://github.com/hailo-ai/hailo-rpi5-examples.git
這將在樹莓派的主文件夾中下載一個名為“hailo-rpi5-examples”的文件夾,這將是我們要使用的一個重要位置。
在安裝流程之前,我們需要使用更改目錄命令告訴終端從該文件夾中工作:
cdhailo-rpi5-examples
終端中的藍色文本顯示文件位置,表明您已成功運行此命令。現在我們將運行shell腳本安裝程序:
./install.sh
此安裝過程可能需要10 - 20分鐘,因為它還會安裝我們將使用的所有YOLO模型。
安裝完成后,再次重啟樹莓派。
運行姿態估計演示
讓我們運行一些演示代碼!在之前的步驟中,我們從Hailo下載了一些示例流程以及使用這些流程的示例Python腳本。在本教程中,我們將使用姿態估計流程 - 它被稱為“pose_estimation_pipeline.py”,位于hailo_rpi5-examples/basic_pipelines下。
運行這些Python腳本的最簡單方法是通過終端。首先使用更改目錄命令更改終端的工作位置,這與我們之前使用的命令相同:
cdhailo-rpi5-examples
安裝步驟還創建了一個虛擬環境(也稱為Venv)。這本質上是一個隔離的虛擬工作空間,我們可以在其中安裝軟件包并進行實驗,而不會影響樹莓派操作系統的其他部分。我們需要使用的所有軟件包都已安裝在此Venv中,我們可以通過在終端中輸入以下命令來進入:
sourcesetup_env.sh
您可以通過查看終端左側括號中的Venv名稱來確認您正在Venv中工作,如右側圖像所示。如果您已進入Venv并看到更改目錄命令的藍色文本,那么您現在就可以運行Python腳本了。如果您關閉終端或重啟樹莓派,則需要再次運行這些命令以返回此狀態。
我們將運行名為“pose_estimation.py”的演示Python代碼,該代碼位于“basic_pipelines”文件夾中,因此命令如下:
python basic_pipelines/pose_estimation.py
您應該會看到一個新窗口彈出,顯示人們過馬路的視頻,以及YOLO姿態估計模型識別人體并進行姿態估計。恭喜!您已成功在AI HAT上設置并運行計算機視覺。
如圖所示,HAT應輸出一些內容。首先,它應識別人體,在其周圍繪制一個邊界框,并在邊界框上方顯示識別的置信度。然后,對于每個檢測到的人,它將在身體的特定部位放置紫色點,并在這些點之間繪制線條,以可視化人的方向。這些點被稱為關鍵點,它們是我們在代碼中將使用的基本元素。
要使用攝像頭作為輸入視頻源來運行Python代碼,我們需要將其指定為參數或選項。我們可以通過輸入以下命令來獲取姿態估計流程的所有可用選項列表:
python basic_pipelines/pose_estimation.py --help
這里有一些有用的選項可供探索,您應該找個時間看看,但我們感興趣的是使用“--input”選項更改源。在這里,我們可以看到我們可以指定文件或攝像頭作為輸入,并且我們可以使用以下命令使用攝像頭模塊運行檢測腳本:
python basic_pipelines/pose_estimation.py --input rpi
示例代碼1:基礎姿態估計代碼
現在,我們已經使用攝像頭運行了姿態估計,讓我們深入了解如何修改此代碼以在我們的項目中應用。這里涉及很多復雜性,有數千行代碼在后臺運行,但其中大部分都在流程中完成。由于大部分操作都在后臺完成,這意味著我們只需要處理一個相當精簡且易于理解的單個文件(我們稱之為高級代碼)。在上一節中,我們運行了這個高級文件,它被稱為“pose_estimation.py”。盡管它已經簡化,但仍然相當復雜,包含許多移動部分,因此我們進一步簡化了代碼,以便在本節中查看演示代碼。如果您想深入研究原始代碼,我們在目標檢測指南中對其進行了詳細介紹 - 雖然是針對目標檢測的,但足以讓您入門。
打開Thonny,創建一個新腳本,將以下代碼粘貼進去,然后將其保存到包含我們所有其他腳本的“basic_pipelines”文件夾中。請確保以.py結尾命名,以確保它保存為Python腳本。如果您需要幫助完成此步驟,視頻演示了此過程。以下是完整代碼:
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 0:? ? ? ? ? ? angle +=?360
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()
要運行此代碼,我們將使用與之前相同的命令行,但這次使用我們保存的文件名。我們將此代碼保存為“pose_simple.py”,因此命令如下:
python basic_pipelines/pose_simple.py --input rpi
如果一切正常,您應該會看到與之前相同的窗口彈出,但這次在shell中會額外打印兩樣東西。這是代碼中兩個實用函數的結果 - 一個用于查找特定關鍵點(默認情況下代碼會跟蹤您的左手腕)的位置,另一個用于計算3個點之間的角度(默認情況下計算您的肘部角度)。
讓我們深入代碼,了解如何使用這些內容以及它們的含義。
代碼像所有Python代碼一樣,以一個區域開始,用于放置所有導入行。在此處導入您的庫,就像您通常所做的那樣。
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
然后我們進入這個名為“custom_processing_thread”的函數。您將在此處放置所有常規代碼。此函數內部有一個while True循環,可以像您通常使用的while True循環一樣處理,并且在其上方,您可以放置所有通常在導入部分之后出現的代碼 - 所有只運行一次的代碼,如設置引腳和硬件、聲明變量等。非常重要的一點是,此部分有一個2秒的休眠,這給了HAT啟動并開始運行姿態估計的時間。如果您在HAT啟動之前嘗試獲取關鍵點或角度數據,可能會出錯,因此這確保了不會發生這種情況。
# 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)
在此內部,有兩個函數用于獲取我們打印到shell的信息。第一個函數允許您獲取特定關鍵點的x和y坐標位置。因此,在代碼中,我們獲取左手腕的位置,這將檢索HAT最新計算的姿態數據(每秒約30次更新新數據):
position= pose_estimator.get_body_part_coordinates('left_wrist')
此函數可用于獲取17個可用關鍵點中任何一個的位置數據,只需輸入關鍵點名稱即可。如果您在演示代碼中向下滾動一點,可以找到這些關鍵點的列表,但為了方便起見,這里也列出了。請注意,每個關鍵點還與一個數字相關聯,您可能會遇到使用此編號系統的代碼,但在此代碼中不需要。
'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,
這些關鍵點使用相對坐標,范圍從0到1。在橫跨屏幕的x軸上,屏幕左側為0,右側為1,中間為0.5。在上下移動的y軸上,屏幕頂部為0,底部為1。右側圖像展示了幀中左手腕的坐標。
第二個函數接受3個關鍵點名稱,并允許您計算由身體上這3個不同部位形成的角度:
angle= pose_estimator.calculate_body_part_angle('left_shoulder','left_elbow','left_wrist')
它返回的角度是基于第二個關鍵點作為參考,第一個和最后一個關鍵點之間的角度。它也總是從攝像頭的角度順時針測量。以下是左肩、左肘和左手腕之間測量的3個角度:
此部分之后是另外200行代碼,幸運的是,您無需觸摸或理解這些代碼。所有這些代碼都用于操作HAT并運行所有必要的計算,以便我們可以使用上述兩個函數來獲取基本數據。我們將上述所有代碼放在一個函數中的原因是,此第二部分在一個線程中運行它。這本質上是一種同時運行多個代碼部分的方式 - 從此處開始的200行代碼和我們的custom_processing_thread內部的代碼同時運行,當我們調用其中一個函數時,我們只是獲取這200行代碼中最新計算的數據。
這就是關于此基礎演示代碼您需要了解的所有內容!對于大多數人來說,這應該足以開始在您自己的項目中應用姿態估計。從這里開始,我們將添加一些額外功能,并查看一些以不同方式利用此基礎代碼的代碼示例。
更改相機分辨率
您可能已經注意到,攝像頭的視野相當狹窄 - 看起來有點縮放,因此讓我們快速看看如何修改這一點。在basic_pipelines文件夾中,有一個名為“hailo_rpi_common”的文件。此文件包含HAT的一些基本操作,如攝像頭輸入分辨率。請注意,您在此處所做的任何更改也將影響從此文件夾中運行的其他流程,因此,如果您還使用此流程中的目標檢測腳本,則此處所做的更改也將影響它。
在大約第195行,您將找到負責更改攝像頭輸入分辨率的行。請注意,這不會更改YOLO處理的分辨率,而只是更改攝像頭最初捕獲的分辨率。默認情況下,分辨率為1536x840,但您可以將其更改為另一個標準分辨率大小。我們在某些分辨率下遇到了性能問題和崩潰,因此您可能需要一些試錯。但是,我們發現1920x1080是一個穩定且足夠高的分辨率。以下是結果:
ifsource_type =='rpi': source_element = ( f'libcamerasrc name={name}! ' f'video/x-raw, format={video_format}, width=1920, height=1080 ! '
更改分辨率后,我們的視野會更寬,如下所示。
在此部分中,我們可以做的另一件方便的事情是刪除占用shell空間的FPS打印輸出。在大約第385行,您將找到一個名為“on_fps_measurement”的函數(您也可以按ctrl + F搜索此函數)。此行正在將FPS讀數打印到shell,您可以通過像這樣注釋掉它來禁用它:
defon_fps_measurement(self, sink, fps, droprate, avgfps): #print(f"FPS: {fps:.2f}, Droprate: {droprate:.2f}, Avg FPS: {avgfps:.2f}") returnTrue
示例代碼2:手勢媒體控制
在此示例代碼中,我們將使用一個名為wtype的庫,根據特定手勢模擬鍵盤輸入。要使用wtype,需要先安裝它。我們需要將其安裝到我們一直使用的同一個虛擬環境中。為此,我們需要確保終端處于我們通常運行腳本之前所需的狀態:
cdhailo-rpi5-examplessourcesetup_env.sh
進入此狀態后,我們可以使用以下命令安裝wtype:
sudoapt install wtype
現在創建一個新腳本,粘貼以下代碼,并將其保存到與上一個腳本相同的basic_pipelines文件夾中。要運行此腳本,您需要使用與之前相同的命令行,但更改為您保存此腳本的名稱。
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 0:? ? ? ? ? ? angle +=?360
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()
此代碼旨在解決我在工作室中遇到的一個問題。我經常在觀看YouTube時需要暫停,但我可能在房間的另一邊,雙手都拿著東西。此代碼通過在我將雙手舉過頭頂時按下“K”鍵(YouTube的暫停/播放快捷鍵)來解決此問題。
以下是我們如何修改基礎腳本以實現此功能。首先,我們需要導入subprocess庫,我們將使用它來運行wtype。像往常一樣,在頂部導入。
importsubprocess
然后,在custom_processing_thread中,我們保留了重要的2秒休眠。然后,在while True循環開始時,我們首先獲取左手腕、右手腕和鼻子的關鍵點位置:
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')
然后,我們有一個if語句比較這些關鍵點的坐標。當我們使用上述函數獲取坐標時,它實際上返回一個包含兩個數字的列表,第一個是x,第二個是y。如果我們只想處理其中一個坐標,可以提取第一個或第二個元素。例如,要獲取左手腕的x坐標,我們可以使用:
left_wrist_x= left_wrist[0]
要獲取y坐標,我們可以使用:
left_wrist_y= left_wrist[1]
因此,在代碼的下一部分中,我們比較這些關鍵點的y坐標(都使用[1]獲取),如果左手腕和右手腕的y坐標都小于鼻子的y坐標,則按下k鍵。請記住,屏幕頂部為0,底部為1,因此如果y坐標較小,則位置較高。
# 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:舵機控制
在這個演示中,我們將根據人物身上關鍵點所生成的角度來控制舵機。以下是完整代碼:
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 0:? ? ? ? ? ? angle +=?360
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()
這段代碼被設置為控制連接到GPIO引腳18和19的兩個舵機,在我們的示例中,我們用樂高積木搭建了一個由兩部分組成的機械臂,并將這兩個舵機安裝其上。隨后,代碼會獲取一個人左肩和左肘的角度,并將舵機調整至這些角度,從而使機械臂能夠模仿人的動作。
為實現這一功能,我們首先從gpiozero庫中導入AngularServo,這是一個控制舵機的便捷方法:
fromgpiozeroimportAngularServo
接著,在custom_processing_thread函數中,我們設置兩個舵機:
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循環中,我們獲取左肘(通過左肩、左肘和左手腕關鍵點)和左肩(通過右肩、左肩和左肘關鍵點)的角度。在每行代碼之后,我們都設置了一個最大值和最小值的限制。這確保了我們的角度保持在0到180度之間,防止我們將像190或300這樣的角度意外輸入到舵機中,從而避免錯誤。之后,我們只需將舵機設置為這些角度。
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:水果忍者
在這個最后的代碼示例中,我們創建了一個通過姿態估計控制的水果忍者游戲,所有功能都集成在這個單一的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) 2:continuefor?i?in?range(len(blade_trail) -?1):p1 = blade_trail[i]p2 = blade_trail[i +?1]for?j, fruit?in?enumerate(self.fruits):if?not?fruit.sliced:dist = self.point_line_distance(fruit.x, fruit.y, p1.x, p1.y, p2.x, p2.y)if?dist < fruit.radius:self.fruits[j] = fruit._replace(sliced=True)self.score +=?1# Increase difficultyif?self.current_spawn_rate > 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 0:return?math.sqrt(A * A + B * B)elif?param >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()
當我提到“我們”創建了這個水果忍者游戲時,實際上是指我和Claude共同編寫的。Claude是一個像Chat GPT一樣的大型語言模型,像大多數LLM一樣,它非常擅長編寫Python代碼。為了創建這個游戲,我粘貼了原始的演示代碼,并簡單地要求它生成一個水果忍者游戲,使用以下提示:
“這是一段在樹莓派 AI HAT上運行的姿態估計代碼。修改這段代碼,創建并控制一個水果忍者游戲。將左右手腕設為刀刃。讓水果跳躍并落下,包含一個生命系統,游戲結束后在5秒倒計時后開始新游戲。請讓刀刃留下軌跡。隨著時間推移,通過增加水果生成速度使游戲逐漸變得更難。為了實現這一點,請使用Pygame,并且不需要額外的庫或資源——我應該只需要粘貼你生成的代碼并運行它。”
雖然需要幾次嘗試才能生成正確的代碼,并在之后進行了一些微調,但讓LLM執行這樣的高級任務并利用這種姿態估計代碼是完全可能的,這可能超出了你的技能范圍(即使我對pygame也不是很精通)。此外,如果你希望學習這段代碼,LLM也非常擅長分解和解釋它生成的代碼——只需詢問它即可!
我們不會詳細解釋這段代碼的工作原理,因為它相當復雜,我們也不期望任何人都能理解。我們只是將其作為一個很酷的示例包含在內,展示了可以實現的功能、這段代碼的用途,以及它如何與LLM結合使用——這是2020年代“創客”的一項非常“酷”的技能。
接下來做什么?
現在,我們已經設置好了樹莓派和AI HAT,并運行了幾個示例代碼,為你提供了一些關于如何在項目中應用姿態估計的想法。現在唯一需要做的就是弄清楚如何利用它。我們有一些關于Pi的通用指南可以幫助你入門,例如,如何控制直流電機和步進電機、舵機,甚至通過繼電器控制電磁閥(你可以使用繼電器控制幾乎任何東西)。
-
嵌入式
+關注
關注
5154文章
19708瀏覽量
318146 -
目標檢測
+關注
關注
0文章
228瀏覽量
16040 -
樹莓派
+關注
關注
121文章
2024瀏覽量
107579
發布評論請先 登錄
DIY一個樹莓派擴展板
完整指南:如何使用樹莓派5、Hailo AI Hat、YOLO、Docker進行自定義數據集訓練?

樹莓派5,Raspberry Pi 5 評測
MCC推出用于樹莓派的MCC 118電壓測量HAT模塊
MCC基于樹莓派的HAT模塊
樹莓派MCC118
【POE HAT擴展板試用連載】樹莓派3B+電路板POE供電應用
樹莓派ReSpeaker 2 Mics Pi HAT電路原理圖免費下載
樹莓派ReSpeaker 2 Mics Pi HAT的PCB圖免費下載

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

評論