Atom's tech blog

Raspberry Piで顔検出した顔画像をAWS経由でLINE通知してみた!

f:id:iAtom:20210108203721j:plain


本記事は、遠隔からRaspberryPiのカメラを起動し、顔検出した画像をLINEに通知します。

キーワードは「AWS IoT Device Shadow、IoT Rule」、「AWS Lambda」、「LINE Nortify」です。

Device Shadowは別記事の「AWS IoT Device Shadowを使ってみた!(Part1〜Part4)」を使用します。

尚、AWS IoT Device Shadowを使ってみた!(Part3) - Atom's tech blog のRasberry Piのソースコード部分は本記事をご参照ください。

顔検出とMQTTメッセージ送信(画像ファイル)する処理を追加しているため。

構成図

f:id:iAtom:20210108134538j:plain

LINE Notifyを登録とアクセストークン発行

LINE Notifyへアクセスします。

notify-bot.line.me


ログインボタンをクリックします。


f:id:iAtom:20210108135559j:plain


LINEに登録しているメールアドレスと、パスワードを入力します。


f:id:iAtom:20210108135758j:plain


Webブラウザで表示された暗証番号をスマートフォンに入力し、「 本人確認」ボタンをタップします。


f:id:iAtom:20210108141349j:plain


f:id:iAtom:20210108142337j:plain


LINE NotifyのWeb画面でログインを確認します。


f:id:iAtom:20210108142515j:plain


マイページ 」を開き、「 トークンを発行する」ボタンをクリックします。


f:id:iAtom:20210108143032j:plain


トークン名 」、「 トークンルーム(今回はお試しなので自分自身 [1:1で....]にします)」を選択し、「 発行する」ボタンをクリックします。

f:id:iAtom:20210108143900j:plain


完了するとトークン番号がダイアログ表示されますので、この「 トークン番号 」をメモしておいてください。

AWS Lambda関数でPush通知する時に使用します。


f:id:iAtom:20210108144925j:plain

AWS Lambda関数の作成と登録

AWS Lambdaコンソールにアクセスします。


f:id:iAtom:20210108145249j:plain


LINE Notifyへ通知するLambda関数を作成します。

関数の作成 」ボタンをクリックします。


f:id:iAtom:20210108145658j:plain


関数の作成画面で「 一から作成 」を選択、「関数名」を入力、「ランタイム」でPython3.7を選択し、「 関数の作成」ボタンをクリックします。


f:id:iAtom:20210108150305j:plain


関数の作成が完了すると以下の画面に遷移します。


f:id:iAtom:20210108151157j:plain


ブラウザを下スクロールすると「関数コード」がありますので、そちらに下記ソースコードをコピペします。

ソースコード説明

メモしたLINEのアクセストークンはここのLambda関数で設定します。

  1. Raspberry Piから受信したMQTTメッセージのイメージデータとファイル名(13行目と14行目)を取得します。

  2. Pasberry Piから送信されるイメージデータがエンコードしているので、15行目でデーコードします。

  3. 16行目で受信したファイル名でLambdaのtmpフォルダにバイナリ形式で保存します。 (保存しなくても実現できると思いますが、わからなかったので一旦保存します。) AWS S3を使う方法もありますが、ここはあえてS3を使わないやり方でトライしています。

  4. 保存したファイルをバイナリ形式で開き、27行目でメッセージと画像ファイルを添付しHTTPS送信します。

  5. 最後に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アクセストーク」を入力した後、「 ファイルを保存 」し、「 デプロイ 」ボタンをクリックします。


f:id:iAtom:20210108153004j:plain

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コンソールの左メニューから「 レイヤー 」を選択し、「 レイヤーの作成 」ボタンをクリックします。


f:id:iAtom:20210108155724j:plain


名前」、「 .zipファイルをアップロード 」を選択し、アップロードボタンをクリックし作成した「 layer.zip」ファイルを選択しアップロードします。

ランタイム 」は「 Python3.6、3.7、3.8 」を選択し、「 作成 」ボタンをクリックします。


f:id:iAtom:20210108160759j:plain


Layers 」を選択し、「 レイヤーの追加 」ボタンをクリックします。


f:id:iAtom:20210108155132j:plain


レイヤーを選択します。「カスタムレイヤー 」を選択、カスタムレイヤーのプルダウンをクリックし、登録した「 requests_layer」、バージョンを「 1 」を選択し、最後に「 追加 」ボタンをクリックし追加します。


f:id:iAtom:20210108161816j:plain


Layers 」をクリックすると、レイヤーに「 requests_layer」が追加できていることを確認できます。

f:id:iAtom:20210108162231j:plain

AWS IoT Rule登録

AWS Iot Coreコンソールにアクセスします。


f:id:iAtom:20210108162633j:plain


AWS IoTコンソール画面左メニューから「 ACT -> ルール 」を選択し、「 作成 」ボタンをクリックします。


f:id:iAtom:20210108164030j:plain


名前 」を入力、ルールクエリステートメントでルール対象となるtopicを変更( topic/faceLINE )します。

イメージはRaspberry Piから送信されたtopicの名称が「 topic/faceLINE」のMQTTメッセージのみにルールを適用することを意味します。


f:id:iAtom:20210108170625j:plain


次に「 topic/faceLINE」を受信したときのアクションを設定します。

アクションの追加 」ボタンをクリックします。


f:id:iAtom:20210108165232j:plain


LINEへ通知するLambda関数を起動したいので、「 メッセージデータを渡すLambda関数を読み出す 」を選択し、「 アクションの設定 」ボタンをクリックします。


f:id:iAtom:20210108165622j:plain


f:id:iAtom:20210108165641j:plain


作成、登録済みのLambda関数を選択し、「 アクションの追加 」ボタンをクリックします。


f:id:iAtom:20210108170016j:plain


最後に「 ルールの作成 」ボタンをクリックし、ルール作成します。


f:id:iAtom:20210108170250j:plain


ルールの登録が完了すると、LambdaコンソールからLambda関数に「AWS IoT」がトリガーとして追加されていることが確認できます。


f:id:iAtom:20210108171412j:plain

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)状態が確認できます。


f:id:iAtom:20210108193941j:plain

iPhoneアプリからONボタンタップ時の状態とLog表示


iphoneよりONボタンをタップすると、desired=1(カメラON指示)、reported(カメラON指示検出)が確認できます。


RaspberryPi Log表示(抜粋)
  ++++++++DELTA++++++++++
  camera: 1
  version: 482
  +++++++++++++++++++++++

  updating...
AWS IoT Core Shadow状態

f:id:iAtom:20210108195346j:plain

LINE通知確認

記事ではRaspberry PiカメラにiPhone画面上に画像ファイルを表示して顔検出させています。

iPhone画面表示イメージ


f:id:iAtom:20210108201606j:plain

LINE通知受信

RaspberryPiでカメラピクセル数を640/480でキャプチャーしているため、解像度は落ちていますが顔検出した画像データ、「faceDetect」メッセージもLINE通知できています。


f:id:iAtom:20210108202217j:plain

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を勉強しよう! 他モジュールのクラスをインポート

f:id:iAtom:20201119115540j:plain

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 」には様々な関数が用意されていますので、下記を見てください。

docs.python.org

Pythonを勉強しよう! switch文はあるの?

f:id:iAtom:20201116152248j:plain

Python初心者の備忘録.....

今回はPythonC言語のswitch文のような記述についてです。

残念ながらPythonにはC言語でいうswtich文のという条件文はありません。

C言語のswitch文の記述

C言語では「 switch 」文に式を記述し、「 case 」文に「 switch 」文の式の結果の値を記述します。

その後に値が一致した時の処理をします。

switch(式)
 {
    case1:
          値1が一致した時の処理
        break;
    case2:
          値2一致時した時の処理
        break;
    default:
         どの値とも一致しない時の処理
}

Pythonのswitch文相当の記述

Pythonには「 if 」、「 elf 」、「 els 」で記述します。

if1:
    式1が一致した時の処理
elif2:
   式2が一致した時の処理
else:
   どの値とも一致しない時の処理

Pythonを勉強しよう! 更新不可のデータ(タプル)の管理

f:id:iAtom:20201116145730j:plain

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について

f:id:iAtom:20201116135319j:plain

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を勉強しよう! データを管理するリストについて

f:id:iAtom:20201115173722j:plain

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パッケージ管理について

f:id:iAtom:20201113144827j:plain

Python初心者の備忘録.....

今回はPythonのパッケージ管理についてです。

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