Raspberry Piで顔検出した顔画像をAWS経由でLINE通知してみた!
本記事は、遠隔からRaspberryPiのカメラを起動し、顔検出した画像をLINEに通知します。
キーワードは「AWS IoT Device Shadow、IoT Rule」、「AWS Lambda」、「LINE Nortify」です。
- 構成図
- LINE Notifyを登録とアクセストークン発行
- AWS Lambda関数の作成と登録
- AWS IoT Rule登録
- AWS IoT モノ・Shadow登録、AWS API Gateway登録、iPhoneアプリ作成
- 検証
Device Shadowは別記事の「AWS IoT Device Shadowを使ってみた!(Part1〜Part4)」を使用します。
尚、AWS IoT Device Shadowを使ってみた!(Part3) - Atom's tech blog のRasberry Piのソースコード部分は本記事をご参照ください。
顔検出とMQTTメッセージ送信(画像ファイル)する処理を追加しているため。
構成図
LINE Notifyを登録とアクセストークン発行
LINE Notifyへアクセスします。
ログインボタンをクリックします。
LINEに登録しているメールアドレスと、パスワードを入力します。
Webブラウザで表示された暗証番号をスマートフォンに入力し、「 本人確認」ボタンをタップします。
LINE NotifyのWeb画面でログインを確認します。
「 マイページ 」を開き、「 トークンを発行する」ボタンをクリックします。
「 トークン名 」、「 トークンルーム(今回はお試しなので自分自身 [1:1で....]にします)」を選択し、「 発行する」ボタンをクリックします。
完了するとトークン番号がダイアログ表示されますので、この「 トークン番号 」をメモしておいてください。
AWS Lambda関数でPush通知する時に使用します。
AWS Lambda関数の作成と登録
AWS Lambdaコンソールにアクセスします。
LINE Notifyへ通知するLambda関数を作成します。
「 関数の作成 」ボタンをクリックします。
関数の作成画面で「 一から作成 」を選択、「関数名」を入力、「ランタイム」でPython3.7を選択し、「 関数の作成」ボタンをクリックします。
関数の作成が完了すると以下の画面に遷移します。
ブラウザを下スクロールすると「関数コード」がありますので、そちらに下記ソースコードをコピペします。
ソースコード説明
メモしたLINEのアクセストークンはここのLambda関数で設定します。
Raspberry Piから受信したMQTTメッセージのイメージデータとファイル名(13行目と14行目)を取得します。
Pasberry Piから送信されるイメージデータがエンコードしているので、15行目でデーコードします。
16行目で受信したファイル名でLambdaのtmpフォルダにバイナリ形式で保存します。 (保存しなくても実現できると思いますが、わからなかったので一旦保存します。) AWS S3を使う方法もありますが、ここはあえてS3を使わないやり方でトライしています。
保存したファイルをバイナリ形式で開き、27行目でメッセージと画像ファイルを添付しHTTPS送信します。
最後にLambdaのtmpに保存した画像ファイルを削除します。
ソースコード
import json import boto3 from base64 import b64decode import requests import os # Line Notify ACCESS_TOKEN = "LINE Notifyのアクセストークンを設定してください" HEADERS = {"Authorization": "Bearer %s" % ACCESS_TOKEN} URL = "https://notify-api.line.me/api/notify" def lambda_handler(event, context): imageData = event['img'] # イメージデータ filename = event['key'] # 画像ファイル名 imgeDataBinaly = b64decode(imageData) # ファイル作成 fw = open('/tmp/' + filename,'wb') # ファイル書き込み fw.write(imgeDataBinaly) fw.close() # ファイル読み込み(requests.postに渡すため) files = {'imageFile': open('/tmp/'+ filename, 'rb')} # メッセージ data = {'message': "faceDetect"} #lineに通知 requests.post(URL, headers=HEADERS, data=data, files=files) # ファイル削除 os.remove('/tmp/' + filename)
「 コピペ 」と「 LINEアクセストークン」を入力した後、「 ファイルを保存 」し、「 デプロイ 」ボタンをクリックします。
Layersの登録
ソースコードで「 imoort requests 」していますが、Lambdaはデフォルトでインストールされていないため、Layersを使ってrequestsライブラリを使用できるようにします。
本来はAWS EC2でrequestsライブラリを作成するべきなのですが、EC2は立ち上げていない(使用していない)ので、MACで作成します。
$ mkdir -p layer/python $ cd layler/python $ pip3 install requests -t . $ cd .. $ zip -r ../layer.zip * $ cd .. $ ls -l layer.zip -rw-r--r-- 1 999125 1 8 15:45 layer.zip
AWS Lambdaコンソールから作成した「layer.zip」ファイルをアップロードします。
Lambdaコンソールの左メニューから「 レイヤー 」を選択し、「 レイヤーの作成 」ボタンをクリックします。
「名前」、「 .zipファイルをアップロード 」を選択し、アップロードボタンをクリックし作成した「 layer.zip」ファイルを選択しアップロードします。
「ランタイム 」は「 Python3.6、3.7、3.8 」を選択し、「 作成 」ボタンをクリックします。
「 Layers 」を選択し、「 レイヤーの追加 」ボタンをクリックします。
レイヤーを選択します。「カスタムレイヤー 」を選択、カスタムレイヤーのプルダウンをクリックし、登録した「 requests_layer」、バージョンを「 1 」を選択し、最後に「 追加 」ボタンをクリックし追加します。
「 Layers 」をクリックすると、レイヤーに「 requests_layer」が追加できていることを確認できます。
AWS IoT Rule登録
AWS Iot Coreコンソールにアクセスします。
AWS IoTコンソール画面左メニューから「 ACT -> ルール 」を選択し、「 作成 」ボタンをクリックします。
「 名前 」を入力、ルールクエリステートメントでルール対象となるtopicを変更( topic/faceLINE )します。
イメージはRaspberry Piから送信されたtopicの名称が「 topic/faceLINE」のMQTTメッセージのみにルールを適用することを意味します。
次に「 topic/faceLINE」を受信したときのアクションを設定します。
「 アクションの追加 」ボタンをクリックします。
LINEへ通知するLambda関数を起動したいので、「 メッセージデータを渡すLambda関数を読み出す 」を選択し、「 アクションの設定 」ボタンをクリックします。
作成、登録済みのLambda関数を選択し、「 アクションの追加 」ボタンをクリックします。
最後に「 ルールの作成 」ボタンをクリックし、ルール作成します。
ルールの登録が完了すると、LambdaコンソールからLambda関数に「AWS IoT」がトリガーとして追加されていることが確認できます。
AWS IoT モノ・Shadow登録、AWS API Gateway登録、iPhoneアプリ作成
下記記事のリンクをご参照ください。
AWS IoT Device Shadowを使ってみた!(Part1) - Atom's tech blog
AWS IoT Device Shadowを使ってみた!(Part2) - Atom's tech blog
AWS IoT Device Shadowを使ってみた!(Part3) - Atom's tech blog
AWS IoT Device Shadowを使ってみた!(Part4) - Atom's tech blog
Part3のRaspberryPiのソースコードは、下記の(shadowFaceDetect.py)を使用してください。
ソースコード(shadowFaceDetect.py)
ソースコード内の下記変数は、各自のAWS環境に合わせてコード記入してください。
- host(endpoint)/ rootCAPath / certificatePath / privateKeyPath
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient import logging import time import json import cv2 import datetime import base64 # HaarCascade cascade_fn = "./haarcascade_frontalface_default.xml" cascade = cv2.CascadeClassifier(cascade_fn) # VideoCapture用インスタンス生成 cap = cv2.VideoCapture(0) # バッファサイズ1に変更 cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # カメラフレームサイズをVGAに変更する cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # カメラ画像の横幅を640に設定 cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # カメラ画像の縦幅を480に設定 # カメラ状態保存処理 def cameraStatusSet(onoff): global cameraState # カメラ起動指示有無確認 if onoff == "1" : cameraState = 'on' else: cameraState = 'off' # IoT Device Shadow "reported" UpDate処理 def updateState(newState): JSONPayload = '{"state":{"reported":{"camera":' + str(newState) +'}}}' print("updating...\n") print(JSONPayload) deviceShadowHandler.shadowUpdate(JSONPayload, None, 5) cameraStatusSet(newState) # Custom Shadow callback def customShadowCallback_Delta(payload, responseStatus, token): print(responseStatus) payloadDict = json.loads(payload) print("++++++++DELTA++++++++++") print("camera: " + str(payloadDict["state"]["camera"])) print("version: " + str(payloadDict["version"])) print("+++++++++++++++++++++++\n\n") # reported Update newState = json.dumps(payloadDict["state"]["camera"]) updateState(newState) host = "自分の環境に合わせてください" port = 8883 clientId = "RaspberryPi" thingName = "RaspberryPi_Camera" rootCAPath = "自分の環境に合わせてください" certificatePath = "自分の環境に合わせてください" privateKeyPath = "自分の環境に合わせてください" # Configure logging logger = logging.getLogger("AWSIoTPythonSDK.core") logger.setLevel(logging.DEBUG) streamHandler = logging.StreamHandler() formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') streamHandler.setFormatter(formatter) logger.addHandler(streamHandler) ################## ### MQTT shadow ### ################## # Init AWSIoTMQTTShadowClient myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient(clientId) myAWSIoTMQTTShadowClient.configureEndpoint(host, port) myAWSIoTMQTTShadowClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath) # AWSIoTMQTTShadowClient configuration myAWSIoTMQTTShadowClient.configureAutoReconnectBackoffTime(1, 32, 20) myAWSIoTMQTTShadowClient.configureConnectDisconnectTimeout(10) # 10 sec myAWSIoTMQTTShadowClient.configureMQTTOperationTimeout(5) # 5 sec # Connect to AWS IoT myAWSIoTMQTTShadowClient.connect() # Create a deviceShadow with persistent subscription deviceShadowHandler = myAWSIoTMQTTShadowClient.createShadowHandlerWithName(thingName, True) # Listen on deltas deviceShadowHandler.shadowRegisterDeltaCallback(customShadowCallback_Delta) ################### ### MQTT Message ### ################### topic = "topic/faceLINE" bucket = "binary-upload-20210106" clientIdPubSub = "RaspberryPiPubSub" # Init AWSIoTMQTTClient myAWSIoTMQTTClient = None myAWSIoTMQTTClient = AWSIoTMQTTClient(clientIdPubSub) myAWSIoTMQTTClient.configureEndpoint(host, 8883) myAWSIoTMQTTClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath) # AWSIoTMQTTClient connection configuration myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 32, 20) myAWSIoTMQTTClient.configureOfflinePublishQueueing(-1) # Infinite offline Publish queueing myAWSIoTMQTTClient.configureDrainingFrequency(2) # Draining: 2 Hz myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10) # 10 sec myAWSIoTMQTTClient.configureMQTTOperationTimeout(5) # 5 sec # Connect and subscribe to AWS IoT myAWSIoTMQTTClient.connect() time.sleep(2) # カメラON/OFF状態を初期化 cameraState = 'off' # Loop forever while True: # カメラOPEN状態 while(cap.isOpened()): # カメラ起動状態指示 ? if cameraState == 'on': # カメラで顔データを取得する ret, img = cap.read() # 画像のグレースケールと平滑化 gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray_image = cv2.equalizeHist(gray_image) # OpenCV顔検出 dets = cascade.detectMultiScale(gray_image, scaleFactor=1.3, minNeighbors=3, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE) for(x,y,w,h) in dets: # 顔部分を抽出 face_image = img[y:y+h, x:x+w] # 日時取得 dt_now = datetime.datetime.now() dateStr = dt_now.strftime('%Y%m%d_%H%M%S_%f') # 画像ファイル名作成 filename = 'image_'+ dateStr + '.jpg' filenamePath = './image/'+filename # 画像ファイル保存 cv2.imwrite(filenamePath,face_image) # 送信する画像ファイルを読出し sendImg = open(filenamePath, 'rb').read() # binary -> base64変換 base64Img = base64.b64encode(sendImg).decode("utf-8") # メッセージデータ作成 payloadMsg=json.dumps({"img": base64Img,"bucket": bucket,"key": filename}) #send Command print("FaceDetect Send") myAWSIoTMQTTClient.publish(topic, payloadMsg, 1) time.sleep(1) time.sleep(10)
検証
Raspberry Pi Pythonスクリプト起動
最初にRaspberry Pi Pythonスクリプトを起動しておきます。
$ python3 shadowFaceDetect.py
iPhoneアプリからONボタンタップ前のShadow状態
Shadow状態はRaspberry Piのカメラ状態(reported)、iPhoneから指定する状態(desired)共に0(カメラOFF)状態が確認できます。
iPhoneアプリからONボタンタップ時の状態とLog表示
iphoneよりONボタンをタップすると、desired=1(カメラON指示)、reported(カメラON指示検出)が確認できます。
RaspberryPi Log表示(抜粋)
++++++++DELTA++++++++++ camera: 1 version: 482 +++++++++++++++++++++++ updating...
AWS IoT Core Shadow状態
LINE通知確認
記事ではRaspberry PiカメラにiPhone画面上に画像ファイルを表示して顔検出させています。
iPhone画面表示イメージ
LINE通知受信
RaspberryPiでカメラピクセル数を640/480でキャプチャーしているため、解像度は落ちていますが顔検出した画像データ、「faceDetect」メッセージもLINE通知できています。
RaspberryPi Log表示(抜粋)
「FaceDetect Send」の文字列表示できており、RaspberryPiでも正常に顔検出&MQTTメッセージ送信しています。
2021-01-08 19:51:01,602 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync publish... 2021-01-08 19:51:01,603 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [puback] event 2021-01-08 19:51:01,604 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [puback] event 2021-01-08 19:51:01,654 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [message] event 2021-01-08 19:51:01,655 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [message] event 2021-01-08 19:51:01,656 - AWSIoTPythonSDK.core.shadow.deviceShadow - DEBUG - shadow message clientToken: 2021-01-08 19:51:01,656 - AWSIoTPythonSDK.core.shadow.deviceShadow - DEBUG - Token is in the pool. Type: accepted 2021-01-08 19:51:01,657 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Invoking custom event callback... 2021-01-08 19:51:02,670 - AWSIoTPythonSDK.core.protocol.connection.cores - DEBUG - stableConnection: Resetting the backoff time to: 1 sec. FaceDetect Send
うまくいきました。
最初Raspberry Pi 3 model b+にMTCNNをインストールして使用しましたが、動作がもっさりしてしまうので、検出率は落ちますが検出が早い「HaarCascade」を使用しました。
Pythonを勉強しよう! 他モジュールのクラスをインポート
Python初心者の備忘録.....
今回は他のPythonファイル(モジュール)をインポートする方法です。
インストールしたパッケージや自分で作成したPythonファイルを利用したいときがあります。 そのときは「 import 」文を用いて、そのモジュールを使うことを宣言する必要があります。
またファイルの先頭に静的に「 import 」宣言ではなく、ある条件下のときに インポートすることも可能です。 そのときは「 importlib 」パッケージを使用します。
モジュールとは、Pythonのソースファイル 。
パッケージとは、複数のモジュールで構成されたものを一つのディレクトリに入れて整理されたもの
基本的なimport宣言
「 import <モジュール名> 」、「 import <パッケージ名> 」を宣言することで、記述したモジュール、パッケージを取り込みし、使用することができます。
import後に使用するときは 「 <モジュール名 or パッケージ名>.<メソット名> 」、「 <モジュール名 or パッケージ名>.<変数名> 」で宣言し、メソッドや変数にアクセスします。
import os os.system("ls")
別名を任意につけたimport宣言
「 import <モジュール名> [as 別名] 」、または「 import <パッケージ名> [as 別名] 」を宣言することで、モジュール名、パッケージ名ではなく、任意につけた別名で記述した名称でpythonファイルを取り込みし、使用することができます。
import後に使用するときは 「 <別名>.<メソット名> 」、「 <別名>.<変数名> 」で宣言し、メソッドや変数にアクセスします。
import numpy as np P = np.array([0, 1, 2, 3, 4, 5])
複数のimport宣言
「 import <モジュール名> , <モジュール名> 」で複数のモジュールをインポートできますが、推奨されていないようです。基本的には一行ずつインポートで。
import os import numpy as np
モジュール内の特定のメソッドのimport宣言
「 from <モジュール名> import <メソッド名 or クラス名 or 変数名> [as 別名] 」を宣言することで使用することができます。
下記サンプルは「 mathモジュール 」の「 floor 」メソッドを名称を「 max 」として使用しています。
from math import floor as max print(max(4.5)) # 4
動的にインポート
Pythonの標準パッケージとして組み込みされている「 importlib 」パッケージを使用します。
ここではimport_module関数を使用して「 os 」をインポートしています。
from importlib import import_module if input() == "1": # OSモジュールのインポート os = import_module("os") os.system("pwd")
「 importlib 」には様々な関数が用意されていますので、下記を見てください。
Pythonを勉強しよう! switch文はあるの?
Python初心者の備忘録.....
今回はPythonでC言語のswitch文のような記述についてです。
残念ながらPythonにはC言語でいうswtich文のという条件文はありません。
C言語のswitch文の記述
C言語では「 switch 」文に式を記述し、「 case 」文に「 switch 」文の式の結果の値を記述します。
その後に値が一致した時の処理をします。
switch(式) { case 値1: 値1が一致した時の処理 break; case 値2: 値2一致時した時の処理 break; default: どの値とも一致しない時の処理 }
Pythonのswitch文相当の記述
Pythonには「 if 」、「 elf 」、「 els 」で記述します。
if 式1: 式1が一致した時の処理 elif 式2: 式2が一致した時の処理 else: どの値とも一致しない時の処理
Pythonを勉強しよう! 更新不可のデータ(タプル)の管理
Python初心者の備忘録.....
今回はリスト型に似ているタブルについてです。
タプルは1回設定した要素は書き換えることができないリストのことをいいます。
リストはミュータブルで、タプルはイミュータブルです。
記述もリストは「 [ ] 」で作成するのに対して、タプルは「 ( )」で作成します。似ていますが若干異なります。
タプル作成
タプル名 = ( 要素1 , 要素2 , 要素3 ....... )
タプル変数を更新した場合
fruits = ( 'apple' , 'orange' , 'banana' ) # 要素を変更 fruits[0] = 'lemon' print(fruits) # 出力結果 # 'tuple' object does not support item assignment
上記サンプル、3行目で変更したがイミュータブルのため、エラーとなります。
ご参考
リストとタプルの使い分けですが、基本的には初期値があって変更がるようなデータとして扱う場合かと。
タプルはappend()はできないので、動的に要素を追加することもできません。
ただし、パフォーマス:処理速度は速いので、使い分けが必要なときもありそうです。
タプルで使用可能なメソッド
- count
- index
リストで使用可能なメソッド
- ppend
- clear
- copy
- count
- extend
- index
- insert
- pop
- remove
- reverse
- sort
Pythonを勉強しよう! and、or、notについて
Python初心者の備忘録.....
今回は条件文「 if 」、「 elif 」などでよく使われる論理演算子「 and 、or 、not 」についてです。
この演算子はどのプログラミング言語でも使われます。しかし言語で記述が違うので、一覧にC言語と比較してみます。
演算子比較
種別 | Python | C言語 | 意味 | |
---|---|---|---|---|
1 | 論理積 | 'a and b' | 'a && b' | 左辺と右辺が共に真:Trueの場合「真:True」 |
2 | 論理和 | 'a or b' | 'a | | b' |左辺と右辺のどちらかが真:Trueの場合「真:True」 |
3 | 否定 | not a | !a | aが偽:Falseの場合、「真:True」、aがTrueの場合「偽:False」 |
and(論理積)
「and」は二つの値の論理積を返却します。
a = 10 b = 20 # aが10 かつ bが20か確認 if a==10 and b==20 : print( "aとbが共にTrue") else: print( "aとbのどちらかがFalse、または両方False") # 出力結果 # aとbが共にTrue
上記サンプル、5行目の左辺と右辺が共に「True」となり、Trueルートへ遷移します。
a = 11 b = 20 # aが10 かつ bが20か確認 if a==10 and b==20 : print( "aとbが共にTrue") else: print( "aとbのどちらかがFalse、または両方False") # 出力結果 # aとbのどちらかがFalse、または両方False
上記サンプル、5行目の左辺が条件合わずFalseとなるため、elseルートへ遷移します。
or(論理積)
「or」は二つの値の論理和を返却します。
a = 11 b = 20 # aが10 または bが20を確認 if a==10 or b==20 : print( "a、またはbのどちらかがTrue、または両方True") else: print( "aとbのどちらもFalse") # 出力結果 # a、またはbのどちらかがTrue、または両方True
上記サンプル、5行目の右辺(b)がTrueとなるため、Trueルートへ遷移します。
a = 11 b = 21 # aが10 または bが20を確認 if a==10 or b==20 : print( "a、またはbのどちらかがTrue、または両方True") else: print( "aとbのどちらもFalse") # 出力結果 # aとbのどちらもFalse
上記サンプル、5行目の両方がFalseとなるため、elseルートへ遷移します。
not (否定)
a = 11 # aが10以外か確認 if not a == 10: print("aが10以外のときTrue") else: print("aが10のときFalse") # 出力結果 # aが10以外のときTrue
上記サンプル、3行目でaが10以外のため、Trueルートへ遷移します。
a = 10 # aが10以外か確認 if not a == 10: print("aが10以外のときTrue") else: print("aが10のときFalse") # 出力結果 # aが10のときFalse
上記サンプル、3行目でaが10のため、Falseルートへ遷移します。
Pythonを勉強しよう! データを管理するリストについて
Python初心者の備忘録.....
今回はよく使われるリスト型についてです。
Pythonではデータ型として「 リスト型 」と「 タプル型 」があり、どちらも一つの変数に複数の要素を格納できることが特徴です。C言語では配列が近いです。
リストの生成
リストを生成するには、以下のように記述します。
要素が整数の場合
num = [ 0 , 1 , 2 , 3 , 4 , 5 ]
要素が文字列の場合
fruits = [ 'apple' , 'orange' , 'banana' ]
空のリスト(スクエアブラケット)
fruits = [ ]
空のリスト(list()コンストラクタ)
fruits = list()
リスト要素の追加
リストに要素を追加するときは、「 append() 」、「 insert() 」メソッドを使用し、 リストに別のリストを追加するときは「 extend() 」を使用します。
また新しい要素を配列指定「 += 」して追加もできます。
append()メソッド
「 append() 」メソッドを使用することで、末尾に追加していきます。
# 空のリストを作成 fruits = list() # リストに追加 fruits.append('apple') print(fruits) # リスト末尾に追加 fruits.append('orange') print(fruits) # 出力結果 # ['apple'] # ['apple', 'orange']
出力結果より順番に追加されていることがわかります。
新しい要素を配列指定
「 += ['banana'] 」で末尾に追加していきます。
fruits = [ 'apple' , 'orange' ] # リスト最後に追加 fruits += ['banana'] print(fruits) # 出力結果 # ['apple', 'orange', 'banana']
insert()メソッド
「 insert() 」メソッドを使用することで、リストの途中に追加することができます。
そのためにはリストの途中のインデックスを知る必要があります。
リスト.insert(インデックス, 値)
fruits = [ 'apple' , 'orange' , 'banana' ] # リスト先頭に追加 fruits.insert(0, 'strawberry') print(fruits) # リスト末尾に追加 fruits.insert(-1 , 'melon') print(fruits) # 出力結果 # ['strawberry', 'apple', 'orange', 'banana'] # ['strawberry', 'apple', 'orange', 'melon', 'banana']
インデックスに「 0 」を指定した場合は、リストの先頭へ挿入。「 -1 」を指定した場合は、リストの最後の要素の一つ前に挿入となります。
extend()メソッド
「 extend()メソッド 」を使用することで、別のリストを一つのリストに追加することができます。
リスト.extend(リスト)
fruits1 = [ 'apple' , 'orange' , 'banana' ] fruits2 = [ 'strawberry' , 'melon'] # リストに追加 fruits1.extend(fruits2) print(fruits1) # 出力結果 # ['apple', 'orange', 'banana', 'strawberry', 'melon']
この他に「 extend() 」メソッドを使用せず、「 += 」でも同等の結果が得られます。
fruits1 = [ 'apple' , 'orange' , 'banana' ] fruits2 = [ 'strawberry' , 'melon'] # リストに追加 fruits1 += fruits2 print(fruits1) # 出力結果 # ['apple', 'orange', 'banana', 'strawberry', 'melon']
リスト要素の更新
リストは「ミュータブル」で要素の変更ができます。要素を変更したい場合、[ ](スクエアブラケット)にインデックス値をつけて、直接要素にアクセスします。
fruits = [ 'apple' , 'orange' , 'banana' ] # リスト最後を変更する fruits[2] = 'melon' print(fruits) # 出力結果 # ['apple', 'orange', 'melon']
リスト要素の削除
リストに要素を削除するときは、「 pop() 」、「 del 」、「 remove() 」、「 clear() 」を使用します。
pop()メソッド
リストのインデックスを指定して、リストから削除します。削除した要素は戻り値で返却されます。
インデックスで「 -1 」を指定したとき、パラメータ指定しない場合は、末尾として削除されます。
・インデックスを指定した場合
fruits = [ 'apple' , 'orange' , 'banana' ] # リストの2番目'orange'を削除する delValue = fruits.pop(1) print(fruits) # 出力結果 # ['apple', 'banana']
・インデックスを指定しない場合
fruits = [ 'apple' , 'orange' , 'banana' ] # リスト最後の'banana'を削除する delValue = fruits.pop() print(fruits) # 出力結果 # ['apple', 'orange']
del演算子
del演算子は、要素を削除したい場合、[ ](スクエアブラケット)にインデックス値をつけて、直接要素にアクセスします。
del [ 先頭のインデックス : 最後尾のインデックス ]
削除する先頭インデックスのみ指定した場合、そのインデックスのみ削除されます。
・先頭インデックスのみ指定した場合
fruits = [ 'apple' , 'orange' , 'banana' ] # リストの'orange'を削除する del fruits[1] print(fruits) # 出力結果 # ['apple', 'banana']
・両方を指定した場合
fruits = [ 'apple' , 'orange' , 'banana' ] # リストの'orange, banana'を削除する del fruits[1:3] print(fruits) # 出力結果 # ['apple']
remove()メソッド
リストから削除する場合、インデックスがわかるケースであれば、pop()やdel演算子が使用できますが、インデックス値がわからないケースもあります。そのときは「 remove() 」メソッドで要素の値を指定して削除します。
尚、リスト内に同じ要素値があった場合は、インデックス値の小さい方が削除され、インデックス値が大きい方は削除されません。
fruits = [ 'apple' , 'orange' , 'banana' ] # リストの'orange'を削除する fruits.remove('orange') print(fruits) # 出力結果 # ['apple', 'banana']
clear()メソッド
本メソッドは、リスト内の要素を全て削除する時に使用します。
fruits = [ 'apple' , 'orange' , 'banana' ] # リストを全削除 fruits.clear() print(fruits) # 出力結果 # []
スライスを用いた全削除
fruits = [ 'apple' , 'orange' , 'banana' ] # リストを全削除 fruits[:] = [] print(fruits) # 出力結果 # []
リスト要素を取り出す(スライス)
スライスとは、リストから新しいリストを取り出して、その取り出したリストを返すことを「 スライス 」と呼びます。
開始インデックスと終了インデックスを指定することで、リスト内のインデックス範囲を取得することができます。
終了インデックスがマイナスの場合は、リストの最後位置からの数を意味します。
・リストのスライス指定
リスト[開始インデックス:終了インデックス:ステップ数]
・サンプルコード
fruits = [ 'apple' , 'orange' , 'banana' , 'lemon' , 'strawberry' , 'melon' ] # 2番目と3番目を取得 print(fruits[1:3]) # 開始インデックスが無い場合は先頭(1番目) # このスライスは1番目〜3番目を示している print(fruits[:3]) # 終了インデックスが無い場合は最後 # このスライスは5番目〜6番目を示している print(fruits[4:]) # 終了インデックスがマイナスの場合は、最後からの数 # このスライスは先頭〜後ろから3番目までを示している print(fruits[0:-2]) # 出力結果 # ['orange', 'banana'] # ['apple', 'orange', 'banana'] # ['strawberry', 'melon'] # ['apple', 'orange', 'banana', 'lemon']
リスト要素数取得
リスト要素の数は「 len() 」やNumPyライブラリの「 Numpy配列(ndarray型) 変換 」で取得することができます。
「 len() 」は二次元配列などでも可能ですが、二次元配列はリスト内のリスト数変えるため、注意が必要です。
一次元リスト
len()関数の場合
fruits = [ 'apple' , 'orange' , 'banana' ] # list内の要素数を取得 lenCnt = len(fruits) print(lenCnt) # 出力結果 # 3
Numpy配列(ndarray型) 変換の場合
list型をNumpy配列(ndarray型) に変換し、sizeで要素数を取得できます。
import numpy as np fruits = ['apple' , 'orange' , 'banana'] # Numpy配列(ndarray型)に変換 ndarrayList = np.array(fruits) print(ndarrayList.size) # 出力結果 # 3
二次元リスト
len()関数の場合
二次元リストで「 len() 」を使用すると、リスト内のリスト数が取得できます。意図的にリスト数を取得する目的であれば「 len() 」使用は問題ありません。
リスト内の要素数を取得はできないため、取得したいときはループ文と「 sum() 」活用する必要があります。
fruits = [['apple' , 'orange' , 'banana'], ['lemon' , 'strawberry' , 'melon']] # list内のlist数を取得 lenCnt = len(fruits) print(lenCnt) # list内の要素数を取得 lenCnt = sum([len(item) for item in fruits]) print(lenCnt) # 出力結果 # 2 # 6
Numpy配列(ndarray型) 変換の場合
list型をNumpy配列(ndarray型) に変換し、sizeで要素数を取得できます。こちらは、多次元でも同様です。
import numpy as np fruits = [['apple' , 'orange' , 'banana'], ['lemon' , 'strawberry' , 'melon']] # Numpy配列(ndarray型)に変換 ndarrayList = np.array(fruits) print(ndarrayList.size) # 出力結果 # 6
リストのコピー
リスト変数をたの別リスト変数に代入すると、メモリアドレスも代入します。
そのため、コピー後に別リストへアクセスすると、最初のコピー元のリストまで変更されてしまいます。
そのときは、「 copy() 」メソッドを使用してコピーしましょう。このメソッドを使用することで、独立した変数をして扱うことができます。
別変数に代入した場合
fruits1 = [ 'apple' , 'orange' , 'banana' , 'lemon' , 'strawberry' , 'melon' ] # fruts1リストをfruits2へ代入 fruits2 = fruits1 # fruits2の要素を変更 fruits2[0] = 'pineapple' print(fruits1) # 出力結果 # ['pineapple', 'orange', 'banana', 'lemon', 'strawberry', 'melon']
上記サンプル、リストをコピーしても別変数へコピーしたものは独立した変数とはならない。
fruits1 = [ 'apple' , 'orange' , 'banana' , 'lemon' , 'strawberry' , 'melon' ] # fruts1リストをfruits2へ代入 fruits2 = fruits1.copy() # fruits2の要素を変更 fruits2[0] = 'pineapple' print(fruits1) print(fruits2) # 出力結果 # ['apple', 'orange', 'banana', 'lemon', 'strawberry', 'melon'] #['pineapple', 'orange', 'banana', 'lemon', 'strawberry', 'melon']
上記サンプル。リストを「 copy() 」メソッドを使用することで、別変数は独立した変数として扱うことができる。
他にもリストの並べ替えに「 reverse() 」(リストの反転)、「 sort() 」(リストの並び順をソート)がありますが、ここでは説明は割愛します。
Pythonを勉強しよう! Pythonパッケージ管理について
Python初心者の備忘録.....
今回はPythonのパッケージ管理についてです。
- pip インストール
- パッケージインストールコマンド関連
- インストールリスト表示
- 最新版がインストールされていないパッケージを表示
- 最新版がインストールされているパッケージを表示
- 依存されていないパッケージのみを表示
Pythonのパッケージ管理には「 pip 」パッケージ(ライブラリ)があり、いろいろなパッケージをインストールやアンインストール、またインストールした詳細情報など確認することに使います。
pip インストール
公式サイトにインストール手順があるので、そちらをご参照ください。 pip.pypa.io
MACでは「 ターミナル 」を起動します。
「 LaunchPad 」→「 その他 」→「ターミナル」です。
まずは「 ターミナル 」から下記コマンドでインストール用のpythonファイルをダウンロードします。
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
次にダンロードしたPythonファイルを実行しインストールをします。
$ python3 get-pip.py
パッケージインストールコマンド関連
項目 | コマンド | |
---|---|---|
1 | 単独インストール | pip install パッケージ名 |
2 | 複数インストール | pip install パッケージ名1 パッケージ名2 .... |
3 | バージョンを指定したインストール | pip install パッケージ名 == バージョン |
4 | 一括インストール | pip install -r requirements.txt |
5 | アップグレード | pip install --upgrade パッケージ名 または pip install -U パッケージ名 |
6 | 単独アンインストール | pip uninstall パッケージ名 |
7 | 複数アンインストール | pip uninstall パッケージ名1 パッケージ名2 .... |
一括インストールの「 requirements.txt 」のファイル名は、なんでもよいです。 そのファイルは、コマンドを実行するディレクトリに配置するか、パス情報もコマンド入力することもできるようです。 例:「 pip install -r ./pipinst/requirements.txt 」など。
requirements.txtの書き方例
一番良い方法は、下記「 pip freeze 」コマンドを用いてリダイレクトしてファイルに保存し、そのファイルを別の環境へインストールが良いです。
$ pip freeze > requirements.txt
下記は公式サイトです。手動で記述するときは下記例をご参考に。
自分自身はトライしたことはありません。
####### example-requirements.txt ####### # ###### Requirements without Version Specifiers ###### nose nose-cov beautifulsoup4 # ###### Requirements with Version Specifiers ###### # See https://www.python.org/dev/peps/pep-0440/#version-specifiers docopt == 0.6.1 # Version Matching. Must be version 0.6.1 keyring >= 4.1.1 # Minimum version 4.1.1 coverage != 3.5 # Version Exclusion. Anything except version 3.5 Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.*
インストールリスト表示
コマンドには「 list 」と「 freeze 」があります。どちらもインストールリストを表示しますが、表示形式が異なります。
実施の表示イメージをみましょう。
$ pip freeze absl-py==0.9.0 alabaster==0.7.12 anaconda-client==1.7.2
$ pip list Package Version ---------------------------------- ------------------- absl-py 0.9.0 alabaster 0.7.12 anaconda-client 1.7.2
最新版がインストールされていないパッケージを表示
このコマンドはよく使いますね。最新化する必要があるか判断するために必要です。
オプションは「 -o 」または「 --outdated 」
$ pip list -o Package Version Latest Type ----------------------------- ------------------- --------- ----- absl-py 0.9.0 0.11.0 wheel appscript 1.0.1 1.1.1 wheel asn1crypto 1.3.0 1.4.0 wheel
最新版がインストールされているパッケージを表示
オプションは「 -u 」または「 --uptodate 」。
$ pip list -u Package Version ---------------------------------- ----------- alabaster 0.7.12 applaunchservices 0.2.1 appnope 0.1.0
依存されていないパッケージのみを表示
オプションは「 --not-required 」。このオプションで実行することで、他のパッケージに依存するか確認ができます。
もし単独で使用しないが、他のパッケージで使用している可能性もるため、アンインストール実施の確認にも使用します。
$pip list --not-required Package Version ---------------------------------- ----------------- anaconda-project 0.8.3 argh 0.26.2 asn1crypto 1.3.0