- Сообщения
 - 1 975
 
- Реакции
 - 408
 
- Баллы
 - 103
 
В этой статье мы рассмотрим проект по распознаванию изображений с помощью Go. Мы также создадим Telegram-бота, с помощью которого сможем отправлять изображения для распознавания.
Первое, что нам нужно, — это уже обученная модель. Да, мы не будем обучать и создавать собственную модель, а возьмём уже готовый docker-образ ctava/tfcgo.
Для запуска нашего проекта нам понадобится одновременно 4 терминала:
Чтобы запустить сервер распознавания, создайте файл Dockerfile:
	
	
	
		
Так мы запустим сервер распознавания. Внутри будет наш сервер: src/imgrecognize. Кроме того, мы распакуем модель в каталоге: /model.
Приступим к созданию сервера. Первое, что нам нужно — это установить значение константы:
	
	
	
		
Это необходимо, чтобы не получить ошибку: 
	
	
	
		
Мы не будем оптимизировать наш сервер, а просто запустим его через ListenAndServe на порту 8080. Перед запуском сервера нам понадобится граф как основа для TensorFlow. Грубо говоря, граф можно рассматривать как контейнер для операций и переменных. Его мы сможем загрузить из файла в формате protobuf: /model/tensorflow_inception_graph. pb. Наполним его позже через сессии. 
	
	
	
		
В modelGraph мы сохраняем структуру нашей модели и ключевые инструменты для работы с ней. Labels содержат словарь для работы с нашей моделью. Важной частью по работе с моделью является нормализация. Мы нормализуем изображение внутри обработчика HTTP-запросов. В реальном проекте обязательно нужно выделить модуль по работе с распознаванием и нормализацией от HTTP-хендлера. Но в учебных целях мы оставим их вместе.
Чтобы нормализовать входные данные, мы преобразуем наше изображение из значения Go в тензор:
	
	
	
		
После этого мы получаем три переменные: 
	
	
	
		
Graph нам нужен, чтобы декодировать, изменять размер и нормализовать изображение. Input вместе с тензором будет входной точкой для связи между нашим приложением и TensorFlow. Output будет использоваться в качестве канала получения данных.
Через graph мы также откроем сессию, чтобы начать нормализацию.
	
	
	
		
Код нормализации: 
	
	
	
		
После нормализации изображения мы создаём сессию для работы с нашим графом: 
	
	
	
		
С помощью этой сессии мы начнём само распознавание. На вход подадим наше нормализованное изображение: 
	
	
	
		
Результат вычисления (распознавания) будет сохранён в переменной outputRecognize. Из полученных данных мы получаем последние 3 результата (ResultCount = 3): 
	
	
	
		
А для HTTP-ответа мы дадим только один наиболее вероятный результат: 
	
	
	
		
Весь код нашего сервера для распознавания: 
	
	
	
		
Теперь нам нужно построить этот образ (build it). Конечно, мы можем создать образ и запустить его в консоли с помощью соответствующих команд. Но удобнее создавать эти команды в файле Makefile. Итак, давайте создадим этот файл: 
	
	
	
		
После этого откройте терминал и выполните команду: 
	
	
	
		
Теперь в первом терминале у нас есть локальный HTTP-сервер, который может принимать изображения. В ответ он отправляет текстовое сообщение, содержащее информацию о том, что было распознано на изображении.
Это, так сказать, ядро нашего проекта.
	
		
			
		
		
	
								Первое, что нам нужно, — это уже обученная модель. Да, мы не будем обучать и создавать собственную модель, а возьмём уже готовый 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
	
		Код:
	
	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,
	
		Код:
	
	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]
}
	
		Код:
	
	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
}
	
		Код:
	
	recognition_build:
    docker build -t imgrecognition .
recognition_run:
    docker run -it -p 8080:8080 imgrecognition
	
		Код:
	
	make recognition_build && make recognition_run
	Теперь в первом терминале у нас есть локальный HTTP-сервер, который может принимать изображения. В ответ он отправляет текстовое сообщение, содержащее информацию о том, что было распознано на изображении.
Это, так сказать, ядро нашего проекта.