果物の収穫と選別の自動化:教育事例

話
導入
テクノロジー分野の中で最もホットなロボット工学は、産業界に革命を起こし、世界中でイノベーションを推進しています。急速に進化するこの分野における熟練した人材の需要の高まりに応えるため、 エレファント・ロボティクスは大学向けに画期的なロボット工学教育ソリューションを開発しました。この革新的なソリューションは、自動果物収穫機のシミュレーションと、果物の選別・配送を自動化する複合ロボットを組み合わせ、学生に最も人気があり、トレンドとなっているテクノロジー分野の一つにおける包括的な学習体験を提供します。
本稿では、果物の収穫・選別ロボットのシナリオについて詳しく解説します。まずキットとその使用シナリオを紹介し、次にロボットアームの機能実装について詳しく説明します。
フルーツピッカーキット

この写真はフルーツピッカーキットを示しており、次のコンポーネントで構成されています。

このキットがどのように動作するのか、興味をそそられるのではないでしょうか。キットの動作プロセスについてご説明いたします。まず、ご覧のとおり、2本のロボットアームがそれぞれ異なる機能を果たしています。果樹に最も近いロボットアームは果実摘みロボットで、以下R1と呼称します。中央に位置するアームは選別ロボットで、以下R2と略します。それぞれの名称からも、それぞれの役割が分かります。R1は果実を木から摘み取り、ベルトコンベアに載せる役割を担い、R2はベルトコンベアから不良品を選別します。
ステップ:
収穫:R1は深度カメラを用いて木の果実を識別し、位置を特定します。果実の座標は収穫のためにR1に送信されます。
輸送:R1による収穫後、果物はベルトコンベア上に載せられます。ベルトコンベアは果物をR2が認識できる範囲まで運び、そこで果物の品質を判定します。
選別:R2上部のカメラが視界内の果物を認識し、アルゴリズムを用いて品質を判定します。良品と判断された場合は、ベルトコンベアで果物集積エリアへ搬送されます。不良品と判断された場合は、不良品の座標がR2に送信され、R2は対象物を掴んで特定のエリアに配置します。
前述のプロセスは継続的にループされます。
収穫→輸送→選別→収穫→輸送。
製品
ロボットアーム - mechArm 270 M5Stack
中心対称構造(産業構造を模した)のコンパクトな6軸ロボットアームで、M5Stack-Basicをコアとして制御され、ESP32の支援を受けています。mechArm 270-M5は重量1kg、可搬重量250g、動作半径270mmです。持ち運びやすく、小型ながらも高機能で、操作が簡単で、人間と安全に連携して作業できるように設計されています。

コンベアベルト
コンベアベルトは、物品の輸送に使用される機械装置で、通常はベルト状の物体と1つまたは複数の回転軸で構成されています。荷物、箱、食品、鉱物、建築資材など、様々な物品を輸送できます。コンベアベルトの動作原理は、移動するベルト上に物品を置き、目的の場所まで移動させることです。コンベアベルトは通常、モーター、伝動システム、ベルト、支持構造で構成されています。モーターが動力を供給し、伝動システムがその動力をベルトに伝達することで、ベルトが移動します。

現在、市場では、コンベアベルトの長さ、幅、高さ、トラックの材質など、ユーザーのニーズに応じてカスタマイズできるさまざまなタイプのコンベアベルトがあります。
3D深度カメラ
使用シナリオの多様性により、一般的な2Dカメラでは要件を満たすことができません。このシナリオでは、深度カメラを使用します。深度カメラは、シーンの深度情報を取得できるカメラの一種です。シーンの色と明るさの情報を取得するだけでなく、物体間の距離と深度情報も認識します。深度カメラは通常、赤外線などの光源を用いて測定を行い、物体やシーンの深度情報を取得します。

深度カメラは、深度画像、カラー画像、赤外線画像、点群画像など、さまざまな種類の情報をキャプチャできます。

深度カメラの助けを借りて、木の上の果物の位置と色情報を正確に取得できます。
適応型グリッパー - ロボットアームのエンドエフェクタ
アダプティブグリッパーは、物体を掴んだり、掴んだり、保持したりするために用いられるエンドエフェクタです。2つの可動爪で構成されており、ロボットアームの制御システムによって開閉角度と速度を調整できます。
プロジェクト機能
まず、コンパイル環境を準備する必要があります。このシナリオはPython言語でコーディングされているため、使用および学習のために環境をインストールすることが不可欠です。
numpy == 1 . 24 . 3
opencv-contrib-python== 4.6.0.66
openni== 2 . 3 . 0
pymycobot== 3 . 1 . 2
PyQt 5 == 5 . 15 . 9
PyQt 5 -Qt 5 == 5 . 15 . 2
PyQt 5 -sip== 12 . 12 . 1
pyserial == 3.5
まず、マシンビジョン認識アルゴリズムの導入について詳しく見ていきましょう。このプロジェクトの機能は主に3つの部分に分けられます。
- 深度認識アルゴリズムを含む機械視覚認識アルゴリズム
- ロボットアームの制御と軌道計画
- 複数のマシン間の通信と論理処理
機械視覚認識アルゴリズム
深度カメラを使用する前にカメラのキャリブレーションが必要です。 。
カメラのキャリブレーション:
カメラキャリブレーションとは、一連の測定と計算を通じてカメラの内部パラメータと外部パラメータを決定するプロセスを指します。カメラの内部パラメータには焦点距離、主点位置、ピクセル間隔などがあり、カメラの外部パラメータにはワールド座標系における位置と向きなどが含まれます。カメラキャリブレーションの目的は、カメラがワールド座標系における物体の位置、サイズ、形状に関する情報を正確に取得し、記録できるようにすることです。
私たちの対象物は果物です。果物には、赤、オレンジ、黄色など、様々な色や形があります。果物を正確かつ安全に掴むためには、果物の幅、厚さ、その他の特性を含む包括的な情報を収集し、インテリジェントな掴みを行う必要があります。

ターゲットとなる果物を調べてみましょう。現在、果物間の最も顕著な違いは、それぞれ異なる色です。赤とオレンジの色相を持つターゲットを選択します。ターゲットの位置特定にはHSV色空間を使用します。以下のコードは、ターゲットとなる果物を検出するためのものです。
コード:
class Detector:
class FetchType(Enum):
FETCH = False
FETCH_ALL = True
"" "
Detection and identification class
" ""
HSV_DIST = {
# "redA" : (np.array([ 0 , 120 , 50 ]), np.array([ 3 , 255 , 255 ])),
# "redB" : (np.array([ 176 , 120 , 50 ]), np.array([ 179 , 255 , 255 ])),
"redA" : (np.array([ 0 , 120 , 50 ]), np.array([ 3 , 255 , 255 ])),
"redB" : (np.array([ 118 , 120 , 50 ]), np.array([ 179 , 255 , 255 ])),
# "オレンジ" : (np.array([ 10 , 120 , 120 ]), np.array([ 15 , 255 , 255 ])),
"オレンジ" : (np.array([ 8 , 150 , 150 ]), np.array([ 20 , 255 , 255 ])),
"黄色" : (np.array([ 28 , 100 , 150 ]), np.array([ 35 , 255 , 255 ])), # 古い
# "黄色" : (np.array([ 31 , 246 , 227 ]), np.array([ 35 , 255 , 255 ])), # 新規
}
デフォルト_hough_params = {
「方法」 : cv 2 .HOUGH_GRADIENT_ALT,
「dp」 : 1.5 ,
"最小距離" : 20 ,
"パラメータ2" : 0.6 ,
"最小半径" : 15 ,
"最大半径" : 40 ,
}
def __init__(self, target):
self.bucket = TargetBucket()
self.detect_target = ターゲット
get_target(self)を定義します。
self.detect_target を返す
def set_target(self, target):
self.detect_target == targetの場合:
戻る
self.detect_target = ターゲット
ターゲット == "apple"の場合:
self.bucket = TargetBucket(adj_tolerance= 25 , expire_time= 0 . 2 )
elif target == "orange" :
self.bucket = TargetBucket()
elif ターゲット == "pear" :
self.bucket = TargetBucket(adj_tolerance= 35 )
defdetect(self, rgb_data):
self.detect_target == "apple"の場合:
self.__detect_apple(rgb_data)
self.detect_target == "オレンジ"の場合:
self.__detect_orange(rgb_data)
self.detect_target == "pear"の場合:
self.__detect_pear(rgb_data)
定義__detect_apple(self, rgb_data):
maskA = color_detect(rgb_data, *self.HSV_DIST[ "redA" ])
maskB = color_detect(rgb_data, *self.HSV_DIST[ "redB" ])
マスク = マスクA + マスクB
カーネルA = cv 2 .getStructuringElement(cv 2 .MORPH_RECT, ( 8 , 8 ))
カーネルB = cv 2 .getStructuringElement(cv 2 .MORPH_RECT, ( 2 , 2 ))
マスク = cv 2 .erode(マスク, カーネルA)
マスク = cv 2 .dilate(マスク, カーネルA)
ターゲット = circle_detect(
マスク、{ "minDist" : 15 、 "param2" : 0.5 、
「最小半径」 : 10 、 「最大半径」 : 50 }
)
self.bucket.add_all(ターゲット)
自己.バケット.更新()
定義__detect_orange(self, rgb_data):
マスク = color_detect(rgb_data, *self.HSV_DIST[ "オレンジ" ])
ターゲット = circle_detect(
マスク、{ "minDist" : 15 、 "param2" : 0 . 1 、
「最小半径」 : 7 、 「最大半径」 : 30 }
)
self.bucket.add_all(ターゲット)
自己.バケット.更新()
def __detect_pear(self, rgb_data):
マスク = color_detect(rgb_data, *self.HSV_DIST[ "黄色" ])
カーネルA = cv 2 .getStructuringElement(cv 2 .MORPH_RECT, ( 25 , 25 ))
カーネルB = cv 2 .getStructuringElement(cv 2 .MORPH_RECT, ( 3 , 3 ))
マスク = cv 2 .erode(マスク, カーネルA)
マスク = cv 2 .dilate(マスク, カーネルA)
マスク = cv 2 .erode(マスク、カーネルB)
ターゲット = circle_detect(
マスク、{ "minDist" : 15 、 "param2" : 0 . 1 、
「最小半径」 : 15 、 「最大半径」 : 70 }
)
self.bucket.add_all(ターゲット)
自己.バケット.更新()
def fetch(self):
self.bucket.fetch() を返す
fetch_all(self)を定義します。
self.bucket.fetch_all() を返す
def debug_view(self, bgr_data, view_all=True):
view_allの場合:
ターゲット = self.bucket.fetch_all()
それ以外:
ターゲット = self.bucket.fetch()
ターゲットがNoneでない場合:
ターゲット = [ターゲット]
ターゲットがNoneでない場合:
ターゲット内のターゲットの場合:
x, y, 半径 = ターゲット["x"], ターゲット["y"], ターゲット["半径"]
# アウトラインを描く
cv2.circle(bgr_data, (x, y), 半径, BGR_GREEN, 2)
# 円の中心を描く
cv2.circle(bgr_data, (x, y), 1, BGR_RED, -1)

最初のステップは、対象となる果実を正確に検出し、その座標、深度、その他の関連情報を取得することです。収集すべき必要な情報を定義し、後ほど使用・検索できるように保存します。
コード:
class VideoCaptureThread(threading.Thread):
def __init__(self, detector, detect_type = Detector.FetchType.FETCH_ALL.value):
threading.Thread.__init__(self)
self.vp = VideoStreamPipe()
self.detector = detector
self.finished = True
self.camera_coord_list = []
自己.古い実座標リスト = []
自己.実座標リスト = []
self.new_color_frame =なし
自己.fruit_type = detector.detect_target
自己検出タイプ = 検出タイプ
self.rgb_show =なし
self.depth_show = なし
最終的な目標は、果物の世界座標を取得し、それをロボットアームに送信して把持動作を実行させることです。奥行き座標を世界座標に変換することで、既に目標の半分を達成しました。あとは、世界座標をロボットアームの座標系に変換し、対象の果物の把持座標を取得するだけです。
# get world coordinate
def convert_depth_to_world(self, x, y, z):
fx = 454.367
f y = 454.367
cx = 313.847
cy = 239.89
ratio = float (z / 1000 )
world_x = float ((x - cx) * ratio) / fx
ワールドx = ワールドx * 1000
world_y = float ((y - cy) * 比率) / fy
ワールドY座標 = ワールドY座標 * 1000
world_z = float (z)
world_x、world_y、world_zを返す
次に、対象物体の把持可能な座標を検出・取得し、ロボットアームに送信できるようになりました。次のステップは、ロボットアームの制御と軌道計画です。
ロボットアームの制御と軌道計画
ロボットアームの制御となると、思った通りの軌道を描くのが難しいと感じる方もいるかもしれません。しかし、ご心配なく。MechArm270ロボットアームには、比較的成熟したロボットアーム制御ライブラリであるpymycobotが搭載されています。わずか数行のコードを書くだけで、ロボットアームを制御できます。
(注: pymycobot はロボットアームの動きを制御するための Python ライブラリです。最新バージョンの pymycobot==3.1.2 を使用します。)
#Introduce two commonly used control methods
'''
Send the degrees of all joints to robot arm.
angle_list_degrees: a list of degree value(List [ float ] ), length 6
speed: ( int ) 0 ~ 100 ,robotic arm's speed
'''
send _angles([ angle_list_degrees ], speed )
send _angles([10,20,30,45,60,0],100)
'''
座標をロボットアームに送信する
coords:座標値のリスト(List [ float ] )、長さ6
speed: ( int ) 0 ~ 100 、ロボットアームの速度
'''
_coords(座標,速度)を送信します
_coords([125,45,78,-120,77,90],50)を送信します
実行のために、軌道を角度形式または座標形式でロボットアームに送信できます。
収穫ロボットの軌道計画
基本的な制御を実装した後、次のステップは、ロボットアームが果物を掴むための軌道計画を設計することです。認識アルゴリズムでは、果物の世界座標を取得しました。これらの座標を処理し、ロボットアームの座標系に変換して、目標の果物を掴みます。
#deal with fruit world coordinates
def target_coords( self ):
coord = self .ma.get_coords()
while len (coord) == 0 :
coord = self .ma.get_coords()
target = self .model_track()
coord[: 3 ] = target.copy()
self .end_coords = coord[: 3 ]
DEBUG == Trueの場合:
print ( "coord: " , coord)
印刷( "self.end_coords: " , self .end_coords)
自己.end_coords = 座標
戻り座標
目標座標がわかったので、ロボットアームの軌道を計画できます。ロボットアームの動作中は、他の構造物に衝突したり、果物を倒したりしないように注意する必要があります。
軌道を計画する際には、次の点を考慮することができます。
● 初期姿勢
● 望ましい把持姿勢
● 障害物回避姿勢
シーンの具体的な要件に応じて、さまざまな姿勢を設定する必要があります。
ソーティングロボットの軌道計画
前回は収穫ロボットについて説明しましたが、今回は選別ロボットの軌道計画について詳しく説明しましょう。実際には、これら2つのロボットの軌道計画は非常に似ています。選別の目的はコンベアベルト上にあるターゲットであり、これらのターゲットは、コンベアベルト上にある腐敗した果物の座標を捉える深度カメラを用いて選別されます。
収穫ロボットと選別ロボットの制御と軌道計画については既に説明しました。次に、このシステムの極めて重要な側面について考察します。2本のロボットアーム間の通信チャネルは極めて重要です。どのようにして効果的な通信を確保し、デッドロックを回避し、コンベアベルトとシームレスに連携して動作するのでしょうか?この点について、次に考察します。
複数のマシン間の通信と論理処理
包括的かつ一貫したロジックがなければ、この2本のロボットアームは既に衝突していたはずです。プログラム全体のフローチャートを見てみましょう。

R2が腐敗した果物を検知すると、R1のロボットアームは一時的に停止します。R2は選別作業を完了すると、R1に信号を送信し、収穫作業を再開できるようにします。
ソケット
通信は必須であるため、Socketライブラリは不可欠です。Socketは、ネットワークプログラミングで頻繁に使用される標準ライブラリです。ネットワーク通信用のAPIセットを提供し、異なるコンピュータ間でのデータ転送を容易にします。R1とR2間の通信問題を解決するために、サーバーとクライアントを確立するソリューションを考案しました。

以下は、サーバー確立の関連コードと初期化情報です。
コード:
class TcpServer(threading. Thread ):
def __init__( self , server_address) -> None :
スレッド。Thread .__init__( self )
self .s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self .s.bind(サーバーアドレス)
print( "サーバーのバインドが成功しました!" )
自分.s.listen( 1 )
self .connected_obj = なし
self.good_fruit_str = "リンゴ"
self .bad_fruit_str = "オレンジ"
self .invalid_fruit_str = "なし"
自己.target =自己.invalid_fruit_str
self.target_copy = self.target
self.start_move = False
こちらがクライアントです。
コード:
class TcpClient(threading.Thread):
def __init__( self , host, port , max_fruit = 8 , recv_interval = 0.1 , recv_timeout = 30 ):
threading.Thread.__init__( self )
self .good_fruit_str = "apple"
self .bad_fruit_str = "orange"
self .invalid_fruit_str = "none"
自己.ホスト = ホスト
自己.ポート=ポート
# TCPソケットオブジェクトの初期化
# IPv4アドレスファミリの使用を指定する
# TCPストリーム伝送プロトコルの使用を指定する
self.client_socket = socket.socket(socket.AF_INET、socket.SOCK_STREAM)
self.client_socket.connect((self.host, self.port))
自己.current_extracted = 0
自己.max_fruit = max_fruit
自己.recv_interval = 受信間隔
自己.recv_timeout = recv_timeout
self.response_copy = self.invalid_fruit_str
self.action_ready = True
サーバーとクライアントが確立されたので、テキスト メッセージと同じように R1 と R2 が通信できるようになります。
R1:「現在、果物を収穫中です。」
R2: 「メッセージ受信しました。終了しました。」
技術的なポイント
実際、このプロジェクト全体を通して、複数のロボットアーム間の効率的な通信と論理処理が最も重要です。2つのロボットアーム間の通信を確立する方法はいくつかあり、シリアルポートを介した物理的な通信、 LANを介したイーサネット通信、 Bluetooth通信などがあります。
私たちのプロジェクトでは、既存のTCP/IPプロトコルを用いたイーサネット通信を利用し、 Pythonのソケットライブラリを通して実装しています。ご存知の通り、家を建てる際には強固な基礎を築くことが不可欠です。同様に、プロジェクトを開始する際にはフレームワークの構築が不可欠です。さらに、ロボットアームの制御原理を理解することも不可欠です。対象物体をワールド座標系に変換し、それをロボットアームの参照フレームにおける目標座標系に変換する方法を学ぶ必要があります。
まとめ
これらの果物収穫・選別ロボットの応用は、学生が力学と電子制御技術の原理をより深く理解するのに役立つだけでなく、科学技術への興味と情熱を育むことにも役立ちます。実践的な訓練と競争的な思考の機会を提供します。
ロボットアームの学習には実践的な操作が不可欠です。このアプリケーションは、学生が実際の操作を通してロボットアームへの理解と知識を深めることができる実践的な機会を提供します。さらに、このアプリケーションは、ロボットアームの動作制御、視覚認識、物体把持といった技術を習得し、習得を支援することで、ロボットアームに関する知識とスキルをより深く理解するのに役立ちます。
さらに、このアプリケーションは、学生のチームワーク、革新的思考、競争的思考能力を育成し、将来のキャリア開発のための強固な基盤を築くのに役立ちます。ロボットコンテストや技術展示会などの活動に参加することで、学生は競争レベルと革新的思考能力を継続的に向上させ、将来の社会の発展や技術の変化に適応する能力を高めることができます。