iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0

Chatbot integration- 看圖學英文

大致上的概念是要利用 Line 把圖片傳給 chatbot ,再把圖片傳到 Azure 認知服務,分別執行 object detection 和 image captioning,匡出圖片中的物體,並且描述圖片內容。

這邊要注意的是,有時候新手會直接把密碼、金鑰和端點直接放進去程式碼中,然後直接推到 Azure Web App,甚至直接推到 githib ,我想這不是一個好的習慣。雖然 Azure Web App 的使用者是自己,不是公開的情況,但如果是放到 github 的話,那自己的密碼、金鑰和端點就全曝光了。所以,最好還是養成好習慣,不要直接將自己的金鑰和密碼以明碼的方式放在自己的程式碼中上傳。

這裡介紹一個簡單的做法:另外開一個 json 檔案config.json,把一些比較敏感的變數放進此檔案中,在上傳到 Azure Web App。

上傳 config

  • 準備config.json

{
    "line": {
        "line_secret": "your line secret",
        "line_token": "your line token",
    },
    "azure": {
        "cv_key": "your subscription key of computer vision",
        "cv_end": "your endpoint of computer vision",
        "blob_connect": "your connect string",
        "blob_container": "your blob container name"
        }
}
  • 建立連結 Web App 的 tunnel。
az webapp create-remote-connection \
-n linecv --resource-group Tibame &
  • 使用scp上傳config.json,這邊要注意只能上傳到/home,這樣在 Web App 的 chatbot 才能讀到此檔案。
scp -P <port> config.json ben@127.0.0.1:/home/config.json

Python套件

requirements.txt

Flask==1.0.2
line-bot-sdk
azure-cognitiveservices-vision-computervision
azure-storage-blob
Pillow

示範程式

程式概念

  • 從 Line 的訊息取得圖片
  • 上傳圖片至 blob ,並取得圖片 URL
  • 以圖片 URL 作為輸入,執行物體偵測和影像描述
  • 將物體偵測的結果存成圖片上傳 blob,並取得連結
  • 將上述連結與影像描述結果,結合 flex message
  • 回傳 flex message 給使用者

application.py



import os
import json

from flask import Flask, request, abort
from azure.cognitiveservices.vision.computervision import ComputerVisionClient
from azure.storage.blob import BlobServiceClient

from msrest.authentication import CognitiveServicesCredentials
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (
    MessageEvent,
    FlexSendMessage,
    ImageMessage,
)

from PIL import Image, ImageDraw, ImageFont

app = Flask(__name__)


CONFIG = json.load(open("/home/config.json", "r"))

SUBSCRIPTION_KEY = CONFIG["azure"]["cv_key"]
ENDPOINT = CONFIG["azure"]["cv_end"]
CV_CLIENT = ComputerVisionClient(
    ENDPOINT, CognitiveServicesCredentials(SUBSCRIPTION_KEY)
)

CONNECT_STR = CONFIG["azure"]["blob_connect"]
CONTAINER = CONFIG["azure"]["blob_container"]
BLOB_SERVICE = BlobServiceClient.from_connection_string(CONNECT_STR)


LINE_SECRET = CONFIG["line"]["line_secret"]
LINE_TOKEN = CONFIG["line"]["line_token"]
LINE_BOT = LineBotApi(LINE_TOKEN)
HANDLER = WebhookHandler(LINE_SECRET)



# 特地留下 hello world,程式碼有問題時,我會從這邊開始檢查
@app.route("/")
def hello():
    "hello world"
    return "Hello World!!!!!"


# 為了上傳圖片到 Azure blob
def upload_blob(container, path):
    """
    Upload files to Azure blob
    """
    blob_client = BLOB_SERVICE.get_blob_client(container=container, blob=path)
    with open(path, "rb") as data:
        blob_client.upload_blob(data, overwrite=True)
    data.close()
    return blob_client.url

# 影像描述
def azure_describe(url):
    """
    Output azure image description result
    """
    description_results = CV_CLIENT.describe_image(url)
    output = ""
    for caption in description_results.captions:
        output += "'{}' with confidence {:.2f}% \n".format(
            caption.text, caption.confidence * 100
        )
    return output

# 物體偵測
def azure_object_detection(url, filename):
    """
    Azure object detection, and output images with bounding boxes
    """
    img = Image.open(filename)
    draw = ImageDraw.Draw(img)
    font_size = int(5e-2 * img.size[1])
    fnt = ImageFont.truetype("static/TaipeiSansTCBeta-Regular.ttf", size=font_size)
    object_detection = CV_CLIENT.detect_objects(url)
    if len(object_detection.objects) > 0:
        for obj in object_detection.objects:
            left = obj.rectangle.x
            top = obj.rectangle.y
            right = obj.rectangle.x + obj.rectangle.w
            bot = obj.rectangle.y + obj.rectangle.h
            name = obj.object_property
            confidence = obj.confidence
            print("{} at location {}, {}, {}, {}".format(name, left, right, top, bot))
            draw.rectangle([left, top, right, bot], outline=(255, 0, 0), width=3)
            draw.text(
                [left, top + font_size],
                "{} {}".format(name, confidence),
                fill=(255, 0, 0),
                font=fnt,
            )
    img.save(filename)
    # 把畫完方框的圖片傳至 blob
    link = upload_blob(CONTAINER, filename)
    # 為了避免一堆圖檔塞爆 Web App,用完就刪掉
    os.remove(filename)
    return link

# Callback for Line chatbot
@app.route("/callback", methods=["POST"])
def callback():
    """
    LINE bot webhook callback
    """
    # get X-Line-Signature header value
    signature = request.headers["X-Line-Signature"]
    print(signature)
    body = request.get_data(as_text=True)
    print(body)
    try:
        HANDLER.handle(body, signature)
    except InvalidSignatureError:
        print(
            "Invalid signature. Please check your channel access token/channel secret."
        )
        abort(400)
    return "OK"

# 處理影像訊息
@HANDLER.add(MessageEvent, message=ImageMessage)
def handle_content_message(event):
    """
    Reply Image message with results of image description and objection detection
    """
    # event 是使用者與 Line 之間的互動事件,可以印出 event 的物件,觀察 event。
    # message ID 會作為後續圖片存檔的名稱, user ID 則是作為之後人臉登入的依據。
    print(event.message)
    print(event.source.user_id)
    print(event.message.id)

    # 讀取為了產生 flex message 的 json 檔
    with open("templates/detect_result.json", "r") as f_h:
        bubble = json.load(f_h)
    f_h.close()
    
    # 以 Line message ID 作為檔案名稱
    filename = "{}.jpg".format(event.message.id)
    
    # 圖片訊息以 binary 的形式傳輸,取得之後先存成圖檔
    message_content = LINE_BOT.get_message_content(event.message.id)
    with open(filename, "wb") as f_h:
        for chunk in message_content.iter_content():
            f_h.write(chunk)
    f_h.close()
    
    # 開啟檔案,為了取得影像指寸
    img = Image.open(filename)
    
    # 上傳圖片到 Blob
    link = upload_blob(CONTAINER, filename)
    
    # 執行物體偵測和影像描述
    link_ob = azure_object_detection(link, filename)
    output = azure_describe(link)
    link = link_ob

    # 以 flex message 輸出結果
    bubble["body"]["contents"][0]["text"] = output
    bubble["header"]["contents"][0]["url"] = link
    # 以影像原尺寸,作為 Flex Message 輸出的依據
    bubble["header"]["contents"][0]["aspectRatio"] = "{}:{}".format(
        img.size[0], img.size[1]
    )

    LINE_BOT.reply_message(
        event.reply_token, [FlexSendMessage(alt_text="Report", contents=bubble)]
    )


將上述的Python程式,連同相對應的requirements.txt,透過git push部署到 Azure Web App , chatbot 就能依照圖片的內容,以通順的英文句子產生說明囉~~


接下來,讓 chatbot 也可以認出圖片中的文字!


上一篇
Day 10 Azure cognitive service: image description- 看圖說故事
下一篇
Day 12 Azure cognitive service: OCR- 光學字元辨識
系列文
我不太懂 AI,可是我會一點 Python 和 Azure30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言