• Добро пожаловать в сообщество My.Net.Ru

    Мы очень рады, что Вы посетили наш форум WAP|WEB мастеров! Мы сможем помочь Вам с решением ваших вопросов. Пожалуйста, пройдите регистрацию, она занимает не более одной минуты и у вас появится возможность:
      Просматривать документации и техническую информацию
      Скачивать шаблоны и скрипты
      Задавать вопросы и получать на них ответы
      Предоставлять услуги или искать исполнителя
      Отключить назойливую рекламу

telegram Распознавание изображений через бота в Telegram. Проект на Go с использованием TensorFlow

anonymous

Создатель
Команда форума
администратор
Сообщения
983
Реакции
291
Баллы
83
В этой статье мы рассмотрим проект по распознаванию изображений с помощью Go. Мы также создадим Telegram-бота, с помощью которого сможем отправлять изображения для распознавания.

Первое, что нам нужно, — это уже обученная модель. Да, мы не будем обучать и создавать собственную модель, а возьмём уже готовый docker-образ ctava/tfcgo.

Для запуска нашего проекта нам понадобится одновременно 4 терминала:

  • В первом мы запустим сервер распознавания изображений.
  • Во втором мы запустим бота.
  • В третьем мы создадим туннель до нашего локального хоста из публичного адреса.
  • В четвёртом мы выполним команду на регистрацию нашего бота.
Запуск сервера распознавания изображений
Чтобы запустить сервер распознавания, создайте файл Dockerfile:
Код:
FROM ctava/tfcgo

RUN mkdir -p /model && \
  curl -o /model/inception5h.zip -s "http://download.tensorflow.org/models/inception5h.zip" && \
  unzip /model/inception5h.zip -d /model

WORKDIR /go/src/imgrecognize
COPY src/ .
RUN go build
ENTRYPOINT [ "/go/src/imgrecognize/imgrecognize" ]
EXPOSE 8080

Так мы запустим сервер распознавания. Внутри будет наш сервер: src/imgrecognize. Кроме того, мы распакуем модель в каталоге: /model.


Приступим к созданию сервера. Первое, что нам нужно — это установить значение константы:
Код:
os.Setenv("TF_CPP_MIN_LOG_LEVEL", "2").
Это необходимо, чтобы не получить ошибку:
Код:
I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA unable to make a tensor from image: Expected image (JPEG, PNG, or GIF), got empty file
Мы не будем оптимизировать наш сервер, а просто запустим его через ListenAndServe на порту 8080. Перед запуском сервера нам понадобится граф как основа для TensorFlow. Грубо говоря, граф можно рассматривать как контейнер для операций и переменных. Его мы сможем загрузить из файла в формате protobuf: /model/tensorflow_inception_graph. pb. Наполним его позже через сессии.
Код:
func loadModel() (*tensorflow.Graph, []string, error) {
    // Load inception model
    model, err := ioutil.ReadFile(graphFile)
    if err != nil {
        return nil, nil, err
    }
    graph := tensorflow.NewGraph()
    if err := graph.Import(model, ""); err != nil {
        return nil, nil, err
    }

    // Load labels
    labelsFile, err := os.Open(labelsFile)
    if err != nil {
        return nil, nil, err
    }
    defer labelsFile.Close()
    scanner := bufio.NewScanner(labelsFile)
    var labels []string
    for scanner.Scan() {
        labels = append(labels, scanner.Text())
    }

    return graph, labels, scanner.Err()
}

В modelGraph мы сохраняем структуру нашей модели и ключевые инструменты для работы с ней. Labels содержат словарь для работы с нашей моделью. Важной частью по работе с моделью является нормализация. Мы нормализуем изображение внутри обработчика HTTP-запросов. В реальном проекте обязательно нужно выделить модуль по работе с распознаванием и нормализацией от HTTP-хендлера. Но в учебных целях мы оставим их вместе.

Чтобы нормализовать входные данные, мы преобразуем наше изображение из значения Go в тензор:
Код:
tensor, err := tensorflow.NewTensor(buf.String()).
После этого мы получаем три переменные:
Код:
graph, input, output, err := getNormalizedGraph().

Graph нам нужен, чтобы декодировать, изменять размер и нормализовать изображение. Input вместе с тензором будет входной точкой для связи между нашим приложением и TensorFlow. Output будет использоваться в качестве канала получения данных.

Через graph мы также откроем сессию, чтобы начать нормализацию.
Код:
session, err := tensorflow.NewSession(graph, nil)
Код нормализации:
Код:
func normalizeImage(imgBody io.ReadCloser) (*tensorflow.Tensor, error) {
    var buf bytes.Buffer
    _, err := io.Copy(&buf, imgBody)
    if err != nil {
        return nil, err
    }

    tensor, err := tensorflow.NewTensor(buf.String())
    if err != nil {
        return nil, err
    }

    graph, input, output, err := getNormalizedGraph()
    if err != nil {
        return nil, err
    }

    session, err := tensorflow.NewSession(graph, nil)
    if err != nil {
        return nil, err
    }

    normalized, err := session.Run(
        map[tensorflow.Output]*tensorflow.Tensor{
            input: tensor,
        },
        []tensorflow.Output{
            output,
        },
        nil)
    if err != nil {
        return nil, err
    }

    return normalized[0], nil
}
После нормализации изображения мы создаём сессию для работы с нашим графом:
Код:
session, err := tensorflow.NewSession(modelGraph, nil)
С помощью этой сессии мы начнём само распознавание. На вход подадим наше нормализованное изображение:
Код:
modelGraph.Operation("input").Output(0): normalizedImg,
Результат вычисления (распознавания) будет сохранён в переменной outputRecognize. Из полученных данных мы получаем последние 3 результата (ResultCount = 3):
Код:
res := getTopFiveLabels(labels, outputRecognize[0].Value().([][]float32)[0])
func getTopFiveLabels(labels []string, probabilities []float32) []Label {
    var resultLabels []Label
    for i, p := range probabilities {
        if i >= len(labels) {
            break
        }
        resultLabels = append(resultLabels, Label{Label: labels[i], Probability: p})
    }
    sort.Sort(Labels(resultLabels))

    return resultLabels[:ResultCount]
}
А для HTTP-ответа мы дадим только один наиболее вероятный результат:
Код:
msg := fmt.Sprintf("This is: %s (%.2f%%)", res[0].Label, res[0].Probability*100)
_, err = w.Write([]byte(msg))
Весь код нашего сервера для распознавания:
Код:
package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "sort"

    tensorflow "github.com/tensorflow/tensorflow/tensorflow
    /go"
    "github.com/tensorflow/tensorflow/tensorflow/go/op"
)

const (
    ResultCount = 3
)

var (
    graphFile  = "/model/tensorflow_inception_graph.pb"
    labelsFile = "/model/imagenet_comp_graph_label_strings
    .txt"
)

type Label struct {
    Label       string
    Probability float32
}

type Labels []Label

func (l Labels) Len() int {
    return len(l)
}
func (l Labels) Swap(i, j int) {
    l[i], l[j] = l[j], l[i]
}
func (l Labels) Less(i, j int) bool {
    return l[i].Probability > l[j].Probability
}

var (
    modelGraph *tensorflow.Graph
    labels     []string
)

func main() {
    // I tensorflow/core/platform/cpu_feature_guard.cc:140]
     Your CPU supports instructions that this TensorFlow
     binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2
     FMA
    // unable to make a tensor from image: Expected image
    (JPEG, PNG, or GIF), got empty file
    err := os.Setenv("TF_CPP_MIN_LOG_LEVEL", "2")
    if err != nil {
        log.Fatalln(err)
    }

    modelGraph, labels, err = loadModel()
    if err != nil {
        log.Fatalf("unable to load model: %v", err)
    }

    log.Println("Run RECOGNITION server ....")
    http.HandleFunc("/", mainHandler)
    err = http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatalln(err)
    }
}

func mainHandler(w http.ResponseWriter, r *http.Request) {
    normalizedImg, err := normalizeImage(r.Body)
    if err != nil {
        log.Fatalf("unable to make a normalizedImg from
                   image: %v", err)
    }

    // Create a session for inference over modelGraph
    session, err := tensorflow.NewSession(modelGraph, nil)
    if err != nil {
        log.Fatalf("could not init session: %v", err)
    }

    outputRecognize, err := session.Run(
        map[tensorflow.Output]*tensorflow.Tensor{
            modelGraph.Operation("input").Output(0):
            normalizedImg,
        },
        []tensorflow.Output{
            modelGraph.Operation("output").Output(0),
        },
        nil,
    )
    if err != nil {
        log.Fatalf("could not run inference: %v", err)
    }

    res := getTopFiveLabels(labels, outputRecognize[0].Value().([][]float32)[0])
    log.Println("--- recognition result:")
    for _, l := range res {
        fmt.Printf("label: %s, probability: %.2f%%\n", l.Label, l.Probability*100)
    }
    log.Println("---")

    msg := fmt.Sprintf("This is: %s (%.2f%%)", res[0].Label, res[0].Probability*100)
    _, err = w.Write([]byte(msg))
    if err != nil {
        log.Fatalf("could not write server response: %v", err)
    }
}

func loadModel() (*tensorflow.Graph, []string, error) {
    // Load inception model
    model, err := ioutil.ReadFile(graphFile)
    if err != nil {
        return nil, nil, err
    }
    graph := tensorflow.NewGraph()
    if err := graph.Import(model, ""); err != nil {
        return nil, nil, err
    }

    // Load labels
    labelsFile, err := os.Open(labelsFile)
    if err != nil {
        return nil, nil, err
    }
    defer labelsFile.Close()
    scanner := bufio.NewScanner(labelsFile)
    var labels []string
    for scanner.Scan() {
        labels = append(labels, scanner.Text())
    }

    return graph, labels, scanner.Err()
}

func getTopFiveLabels(labels []string, probabilities []float32) []Label {
    var resultLabels []Label
    for i, p := range probabilities {
        if i >= len(labels) {
            break
        }
        resultLabels = append(resultLabels, Label{Label: labels[i], Probability: p})
    }
    sort.Sort(Labels(resultLabels))

    return resultLabels[:ResultCount]
}

func normalizeImage(imgBody io.ReadCloser) (*tensorflow.Tensor, error) {
    var buf bytes.Buffer
    _, err := io.Copy(&buf, imgBody)
    if err != nil {
        return nil, err
    }

    tensor, err := tensorflow.NewTensor(buf.String())
    if err != nil {
        return nil, err
    }

    graph, input, output, err := getNormalizedGraph()
    if err != nil {
        return nil, err
    }

    session, err := tensorflow.NewSession(graph, nil)
    if err != nil {
        return nil, err
    }

    normalized, err := session.Run(
        map[tensorflow.Output]*tensorflow.Tensor{
            input: tensor,
        },
        []tensorflow.Output{
            output,
        },
        nil)
    if err != nil {
        return nil, err
    }

    return normalized[0], nil
}

// Creates a graph to decode, rezise and normalize an image
func getNormalizedGraph() (graph *tensorflow.Graph, input, output tensorflow.Output, err error) {
    s := op.NewScope()
    input = op.Placeholder(s, tensorflow.String)
    decode := op.DecodeJpeg(s, input, op.DecodeJpegChannels(3)) // 3 RGB

    output = op.Sub(s,
        op.ResizeBilinear(s,
            op.ExpandDims(s,
                op.Cast(s, decode, tensorflow.Float),
                op.Const(s.SubScope("make_batch"), int32(0))),
            op.Const(s.SubScope("size"), []int32{224, 224})),
        op.Const(s.SubScope("mean"), float32(117)))
    graph, err = s.Finalize()

    return graph, input, output, err
}
Теперь нам нужно построить этот образ (build it). Конечно, мы можем создать образ и запустить его в консоли с помощью соответствующих команд. Но удобнее создавать эти команды в файле Makefile. Итак, давайте создадим этот файл:
Код:
recognition_build:
    docker build -t imgrecognition .

recognition_run:
    docker run -it -p 8080:8080 imgrecognition
После этого откройте терминал и выполните команду:
Код:
make recognition_build && make recognition_run

Теперь в первом терминале у нас есть локальный HTTP-сервер, который может принимать изображения. В ответ он отправляет текстовое сообщение, содержащее информацию о том, что было распознано на изображении.

Это, так сказать, ядро нашего проекта.
 

anonymous

Создатель
Команда форума
администратор
Сообщения
983
Реакции
291
Баллы
83
Создание бота Telegram
Теперь нам нужно построить бота. Для этого необходимо написать второй HTTP-сервер. Первый сервер распознает наши изображения и использует порт 8080. Второй станет сервером бота и будет использовать порт 3000.

Для начала нужно создать бота через вашу учетную запись в Telegram через BotFather. После этой регистрации вы получите имя бота и его токен. Никому не говорите об этом токене.

Поместим токен в константу BotToken. Вы должны получить:
Код:
const BotToken = "1695571234:AAEbodyrfOjto2xNE5yjpQpW2Gyq0Ob5X24D5"
Обработчик нашего бота расшифрует тело ответа JSON.
Код:
json.NewDecoder(r.Body).Decode(webhookBody)
Нас интересует фотография в отправленном сообщении webhookBody.Message.Photo. По уникальному идентификатору изображения photoSize.FileID соберём ссылку на само изображение: fmt.Sprintf(GetFileUrl, BotToken, photoSize.FileID)

И загрузим его:
Код:
downloadResponse, err = http.Get(downloadFileUrl).
Мы отправим изображение обработчику нашего первого сервера:
Код:
msg := recognitionClient.Recognize(downloadResponse)

В ответ мы получаем определённое сообщение — текстовую строку. После этого мы просто отправляем эту строку пользователю как есть в боте Telegram.

Весь код бота:

Код:
package main

import (
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"

    "github.com/romanitalian/recognition/src/bot/recognition"
)

// Register Bot: curl -F "url=https://9068b6869da7.ngrok.io "  https://api.telegram.org/bot1695571234:AAEbodyrfOjto2xNE5yjpQpW2Gyq0Ob5X24D5/setWebhook
const (
    BotToken = "1695571234:AAEbodyrfOjto2xNE5yjpQpW2Gyq0Ob5X24D5"

    GetFileUrl       = "https://api.telegram.org/bot%s/getFile?file_id=%s"
    DownloadFileUrl  = "https://api.telegram.org/file/bot%s/%s"
    SendMsgToUserUrl = "https://api.telegram.org/bot%s/sendMessage"
)

type webhookReqBody struct {
    Message Msg
}

type Msg struct {
    MessageId int    `json:"message_id"`
    Text      string `json:"text"`
    From      struct {
        ID        int64  `json:"id"`
        FirstName string `json:"first_name"`
        Username  string `json:"username"`
    } `json:"from"`
    Photo *[]PhotoSize `json:"photo"`
    Chat  struct {
        ID        int64  `json:"id"`
        FirstName string `json:"first_name"`
        Username  string `json:"username"`
    } `json:"chat"`
    Date  int `json:"date"`
    Voice struct {
        Duration int64  `json:"duration"`
        MimeType string `json:"mime_type"`
        FileId   string `json:"file_id"`
        FileSize int64  `json:"file_size"`
    } `json:"voice"`
}

type PhotoSize struct {
    FileID   string `json:"file_id"`
    Width    int    `json:"width"`
    Height   int64  `json:"height"`
    FileSize int64  `json:"file_size"`
}
type ImgFileInfo struct {
    Ok     bool `json:"ok"`
    Result struct {
        FileId       string `json:"file_id"`
        FileUniqueId string `json:"file_unique_id"`
        FileSize     int    `json:"file_size"`
        FilePath     string `json:"file_path"`
    } `json:"result"`
}

func main() {
    log.Println("Run BOT server ....")
    err := http.ListenAndServe(":3000", http.HandlerFunc(Handler))
    if err != nil {
        log.Fatalln(err)
    }
}

// This handler is called everytime telegram sends us a webhook event
func Handler(w http.ResponseWriter, r *http.Request) {
    // First, decode the JSON response body
    webhookBody := &webhookReqBody{}
    err := json.NewDecoder(r.Body).Decode(webhookBody)
    if err != nil {
        log.Println("could not decode request body", err)
        return
    }

    // ------------------------- Download last img

    var downloadResponse *http.Response

    if webhookBody.Message.Photo == nil {
        log.Println("no photo in webhook body. webhookBody: ", webhookBody)
        return
    }
    for _, photoSize := range *webhookBody.Message.Photo {
        // GET JSON ABOUT OUR IMG (ORDER TO GET FILE_PATH)
        imgFileInfoUrl := fmt.Sprintf(GetFileUrl, BotToken, photoSize.FileID)
        rr, err := http.Get(imgFileInfoUrl)
        if err != nil {
            log.Println("unable retrieve img by FileID", err)
            return
        }
        defer rr.Body.Close()
        // READ JSON
        fileInfoJson, err := ioutil.ReadAll(rr.Body)
        if err != nil {
            log.Println("unable read img by FileID", err)
            return
        }
        // UNMARSHAL JSON
        imgInfo := &ImgFileInfo{}
        err = json.Unmarshal(fileInfoJson, imgInfo)
        if err != nil {
            log.Println("unable unmarshal file description from api.telegram by url: "+imgFileInfoUrl, err)
        }
        // GET FILE_PATH

        downloadFileUrl := fmt.Sprintf(DownloadFileUrl, BotToken, imgInfo.Result.FilePath)
        downloadResponse, err = http.Get(downloadFileUrl)
        if err != nil {
            log.Println("unable download file by file_path: "+downloadFileUrl, err)
            return
        }
        defer downloadResponse.Body.Close()
    }

    // --------------------------- Send img to server recognition.
    recognitionClient := recognition.New()
    msg := recognitionClient.Recognize(downloadResponse)

    if err := sendResponseToUser(webhookBody.Message.Chat.ID, msg); err != nil {
        log.Println("error in sending reply: ", err)
        return
    }
}

// The below code deals with the process of sending a response message
// to the user

// Create a struct to conform to the JSON body
// of the send message request
// https://core.telegram.org/bots/api#sendmessage
type sendMessageReqBody struct {
    ChatID int64  `json:"chat_id"`
    Text   string `json:"text"`
}

// sendResponseToUser notify user - what found on image.
func sendResponseToUser(chatID int64, msg string) error {
    // Create the request body struct
    msgBody := &sendMessageReqBody{
        ChatID: chatID,
        Text:   msg,
    }

    // Create the JSON body from the struct
    msgBytes, err := json.Marshal(msgBody)
    if err != nil {
        return err
    }

    // Send a post request with your token
    res, err := http.Post(fmt.Sprintf(SendMsgToUserUrl, BotToken), "application/json", bytes.NewBuffer(msgBytes))
    if err != nil {
        return err
    }
    if res.StatusCode != http.StatusOK {
        buf := new(bytes.Buffer)
        _, err := buf.ReadFrom(res.Body)
        if err != nil {
            return err
        }
        return errors.New("unexpected status: " + res.Status)
    }

    return nil
}
Клиент, который отправляет изображение от бота на сервер распознавания:
Код:
package recognition

import (
    "io/ioutil"
    "log"
    "net/http"
)

const imgRecognitionAddress = "http://localhost:8080/"

type Client struct {
    httpClient *http.Client
}

func New() *Client {
    return &Client{
        httpClient: &http.Client{},
    }
}

func (c *Client) Recognize(downloadResponse *http.Response) string {
    var msg string
    method := "POST"

    req, err := http.NewRequest(method, imgRecognitionAddress, downloadResponse.Body)
    if err != nil {
        log.Println("error from server recognition", err)
        return msg
    }
    req.Header.Add("Content-Type", "image/png")

    // do request to server recognition.
    recognitionResponse, err := c.httpClient.Do(req)
    if err != nil {
        log.Println(err)
        return msg
    }
    defer func() {
        er := recognitionResponse.Body.Close()
        if er != nil {
            log.Println(er)
        }
    }()

    recognitionResponseBody, err := ioutil.ReadAll(recognitionResponse.Body)
    if err != nil {
        log.Println("error on read response from server recognition", err)
        return msg
    }
    msg = string(recognitionResponseBody)

    return msg
}
Теперь нам нужно получить публичный HTTPS-адрес для нашего бота, который сейчас работает на localhost. В этом нам поможет ngrok:
Код:
ngrok http 3000

Сразу после выполнения этой команды вы увидите список общедоступных адресов. Последним будет адрес с HTTPS. Например, это может быть https://9068b6869da7.ngrok.io.

Теперь зарегистрируем нашего бота — пробросим наш адрес в Telegram API, куда отправлять веб-хуки:
Код:
curl -F "url=https://9068b6869da7.ngrok.io" https://api.telegram.org/bot1695571234:AAEbodyrfOjto2xNE5yjpQpW2Gyq0Ob5X24D5/setWebhook
Поздравляю, теперь мы можем отправить файл с фотографией своему боту и в ответ получим информацию о том, что на нём изображено.
 

Тему смотрели (Всего: 0)

Тема долгое время не просматривалась.

bodr.pp.ua Бодрый топ рейтинг мобильных WAP сайтов KatStat.ru - Топ рейтинг сайтов
Топ рейтинг сайтов! Статистика посещаемости сайтов Stata.Me onstat.top stats24.ru-лучший топ рейтинг мобильных сайтов Top.Mail.Ru GiStaT.RU - Рейтинг мобильных сайтов Mobiseo.RU - Рейтинг мобильных сайтов Добавить сайт в интернете для рекламы
Верх