Получите правильную ориентацию изображения с помощью API Google Cloud Vision (TEXT_DETECTION)

Я попробовал API Google Cloud Vision (TEXT_DETECTION) на изображении, повернутом на 90 градусов. Он по-прежнему может правильно возвращать распознанный текст. (см. изображение ниже)

Это означает, что движок может распознавать текст, даже если изображение повернуто на 90, 180, 270 градусов.

Однако результат ответа не содержит информации о правильной ориентации изображения. (документ: EntityAnnotation)

Есть ли способ не только получить распознанный текст, но и получить его ориентацию?
Может ли Google поддерживать его аналогично (FaceAnnotation: getRollAngle)

введите описание изображения здесь


person Jack Fan    schedule 22.12.2016    source источник
comment
Если вы хотите, чтобы эта функция существовала, рассмотрите возможность размещения запроса функции в системе отслеживания проблем google-cloud-platform по адресу code.google.com/p/google-cloud-platform/issues/list.   -  person Adam    schedule 07.01.2017
comment
Спасибо. Я не знал, что существует такой список проблем. Я только что разместил заявку. code.google.com/p/google-cloud- platform / issues / detail? id = 194   -  person Jack Fan    schedule 09.01.2017


Ответы (7)


Как описано в общедоступном средстве отслеживания проблем, наши Команда инженеров теперь осведомлена об этом запросе функции, и в настоящее время нет ETA для его реализации.

Обратите внимание: информация об ориентации может быть уже доступна в метаданных вашего изображения. Пример извлечения метаданных можно увидеть в этой сторонней библиотеке.

Широкий обходной путь - проверить возвращенный "boundingPoly "" вершины "для возвращенного" textAnnotations ". Вычисляя ширину и высоту прямоугольника каждого обнаруженного слова, вы можете определить, находится ли изображение не правой стороной вверх, если прямоугольник «высота»> «ширина» (иначе говоря, изображение находится сбоку).

person Jordan    schedule 09.01.2017
comment
Любопытно узнать, как облачное зрение Google может получить правильный текст из изображения, даже если изображение не выровнено по горизонтали и его нужно повернуть. Как API узнает, на сколько повернуть изображение? Если этой информации нет в метаданных изображения, как облачный API узнает об этом? - person Omnipresent; 18.05.2017
comment
Проблема, которая была решена на вашей стороне, как описано в облаке. google.com/vision/docs/reference/rest/v1p4beta1/ позволяет различать только повернутые на 0, 90, 180, 270 градусов (и только после некоторой математики). У вас уже есть вся информация, необходимая для выравнивания изображения, почему бы не вернуть ее? - person Emilia Apostolova; 17.06.2021

Вы можете использовать тот факт, что мы знаем последовательность символов в слове, чтобы вывести ориентацию слова следующим образом (очевидно, немного другая логика для языков, отличных от LTR):

for page in annotation:
    for block in page.blocks:
        for paragraph in block.paragraphs:
            for word in paragraph.words:
                if len(word.symbols) < MIN_WORD_LENGTH_FOR_ROTATION_INFERENCE:
                    continue
                first_char = word.symbols[0]
                last_char = word.symbols[-1]
                first_char_center = (np.mean([v.x for v in first_char.bounding_box.vertices]),np.mean([v.y for v in first_char.bounding_box.vertices]))
                last_char_center = (np.mean([v.x for v in last_char.bounding_box.vertices]),np.mean([v.y for v in last_char.bounding_box.vertices]))

                #upright or upside down
                if np.abs(first_char_center[1] - last_char_center[1]) < np.abs(top_right.y - bottom_right.y): 
                    if first_char_center[0] <= last_char_center[0]: #upright
                        print 0
                    else: #updside down
                        print 180
                else: #sideways
                    if first_char_center[1] <= last_char_center[1]:
                        print 90
                    else:
                        print 270

Затем вы можете использовать ориентацию отдельных слов, чтобы сделать вывод об ориентации документа в целом.

person faaez    schedule 13.06.2018
comment
какое значение вы используете для MIN_WORD_LENGTH_FOR_ROTATION_INFERENCE? - person Sriram; 13.08.2018

Ответ Джека Фана сработал для меня. Это моя версия VanillaJS.

/**
 *
 * @param gOCR  The Google Vision response
 * @return orientation (0, 90, 180 or 270)
 */
function getOrientation(gOCR) {
    var vertexList = gOCR.responses[0].textAnnotations[1].boundingPoly.vertices;

    const ORIENTATION_NORMAL = 0;
    const ORIENTATION_270_DEGREE = 270;
    const ORIENTATION_90_DEGREE = 90;
    const ORIENTATION_180_DEGREE = 180;

    var centerX = 0, centerY = 0;
    for (var i = 0; i < 4; i++) {
        centerX += vertexList[i].x;
        centerY += vertexList[i].y;
    }
    centerX /= 4;
    centerY /= 4;

    var x0 = vertexList[0].x;
    var y0 = vertexList[0].y;

    if (x0 < centerX) {
        if (y0 < centerY) {

            return ORIENTATION_NORMAL;
        } else {
            return ORIENTATION_270_DEGREE;
        }
    } else {
        if (y0 < centerY) {
            return ORIENTATION_90_DEGREE;
        } else {
            return ORIENTATION_180_DEGREE;
        }
    }
}
person Alex Hernandez    schedule 05.07.2019

Я публикую свой обходной путь, который действительно работает для изображений, повернутых на 90, 180, 270 градусов. Пожалуйста, посмотрите код ниже.

GetExifOrientation(annotateImageResponse.getTextAnnotations().get(1));
/**
 *
 * @param ea  The input EntityAnnotation must be NOT from the first EntityAnnotation of
 *            annotateImageResponse.getTextAnnotations(), because it is not affected by
 *            image orientation.
 * @return Exif orientation (1 or 3 or 6 or 8)
 */
public static int GetExifOrientation(EntityAnnotation ea) {
    List<Vertex> vertexList = ea.getBoundingPoly().getVertices();
    // Calculate the center
    float centerX = 0, centerY = 0;
    for (int i = 0; i < 4; i++) {
        centerX += vertexList.get(i).getX();
        centerY += vertexList.get(i).getY();
    }
    centerX /= 4;
    centerY /= 4;

    int x0 = vertexList.get(0).getX();
    int y0 = vertexList.get(0).getY();

    if (x0 < centerX) {
        if (y0 < centerY) {
            //       0 -------- 1
            //       |          |
            //       3 -------- 2
            return EXIF_ORIENTATION_NORMAL; // 1
        } else {
            //       1 -------- 2
            //       |          |
            //       0 -------- 3
            return EXIF_ORIENTATION_270_DEGREE; // 6
        }
    } else {
        if (y0 < centerY) {
            //       3 -------- 0
            //       |          |
            //       2 -------- 1
            return EXIF_ORIENTATION_90_DEGREE; // 8
        } else {
            //       2 -------- 3
            //       |          |
            //       1 -------- 0
            return EXIF_ORIENTATION_180_DEGREE; // 3
        }
    }
}

Дополнительная информация
Я обнаружил, что мне нужно добавить языковую подсказку, чтобы annotateImageResponse.getTextAnnotations().get(1) всегда следовал правилу.

Пример кода для добавления подсказки по языку

ImageContext imageContext = new ImageContext();
String [] languages = { "zh-TW" };
imageContext.setLanguageHints(Arrays.asList(languages));
annotateImageRequest.setImageContext(imageContext);
person Jack Fan    schedule 13.01.2017

Иногда невозможно получить ориентировку по метаданным. Например, если пользователь сделал снимок с помощью камеры мобильного устройства с неправильной ориентацией. Мое решение основано на ответе Джека Фана и на google-api-services-vision (доступно через Maven).

мой класс TextUnit

  public class TextUnit {
        private String text;

        //    X of lowest left point
        private float llx;

        //    Y of lowest left point
        private float lly;

        //    X of upper right point
        private float urx;

        //    Y of upper right point
        private float ury;
    }

базовый метод:

 List<TextUnit> extractData(BatchAnnotateImagesResponse response) throws AnnotateImageResponseException {
            List<TextUnit> data = new ArrayList<>();

            for (AnnotateImageResponse res : response.getResponses()) {
                if (null != res.getError()) {
                    String errorMessage = res.getError().getMessage();
                    logger.log(Level.WARNING, "AnnotateImageResponse ERROR: " + errorMessage);
                    throw new AnnotateImageResponseException("AnnotateImageResponse ERROR: " + errorMessage);
                } else {
                    List<EntityAnnotation> texts = response.getResponses().get(0).getTextAnnotations();
                    if (texts.size() > 0) {

                        //get orientation
                        EntityAnnotation first_word = texts.get(1);
                        int orientation;
                        try {
                            orientation = getExifOrientation(first_word);
                        } catch (NullPointerException e) {
                            try {
                                orientation = getExifOrientation(texts.get(2));
                            } catch (NullPointerException e1) {
                                orientation = EXIF_ORIENTATION_NORMAL;
                            }
                        }
                        logger.log(Level.INFO, "orientation: " + orientation);

                        // Calculate the center
                        float centerX = 0, centerY = 0;
                        for (Vertex vertex : first_word.getBoundingPoly().getVertices()) {
                            if (vertex.getX() != null) {
                                centerX += vertex.getX();
                            }
                            if (vertex.getY() != null) {
                                centerY += vertex.getY();
                            }
                        }
                        centerX /= 4;
                        centerY /= 4;


                        for (int i = 1; i < texts.size(); i++) {//exclude first text - it contains all text of the page

                            String blockText = texts.get(i).getDescription();
                            BoundingPoly poly = texts.get(i).getBoundingPoly();

                            try {
                                float llx = 0;
                                float lly = 0;
                                float urx = 0;
                                float ury = 0;
                                if (orientation == EXIF_ORIENTATION_NORMAL) {
                                    poly = invertSymmetricallyBy0X(centerY, poly);
                                    llx = getLlx(poly);
                                    lly = getLly(poly);
                                    urx = getUrx(poly);
                                    ury = getUry(poly);
                                } else if (orientation == EXIF_ORIENTATION_90_DEGREE) {
                                    //invert by x
                                    poly = rotate(centerX, centerY, poly, Math.toRadians(-90));
                                    poly = invertSymmetricallyBy0Y(centerX, poly);
                                    llx = getLlx(poly);
                                    lly = getLly(poly);
                                    urx = getUrx(poly);
                                    ury = getUry(poly);
                                } else if (orientation == EXIF_ORIENTATION_180_DEGREE) {
                                    poly = rotate(centerX, centerY, poly, Math.toRadians(-180));
                                    poly = invertSymmetricallyBy0Y(centerX, poly);
                                    llx = getLlx(poly);
                                    lly = getLly(poly);
                                    urx = getUrx(poly);
                                    ury = getUry(poly);
                                }else if (orientation == EXIF_ORIENTATION_270_DEGREE){
                                    //invert by x
                                    poly = rotate(centerX, centerY, poly, Math.toRadians(-270));
                                    poly = invertSymmetricallyBy0Y(centerX, poly);
                                    llx = getLlx(poly);
                                    lly = getLly(poly);
                                    urx = getUrx(poly);
                                    ury = getUry(poly);
                                }


                                data.add(new TextUnit(blockText, llx, lly, urx, ury));
                            } catch (NullPointerException e) {
                                //ignore - some polys has not X or Y coordinate if text located closed to bounds.
                            }
                        }
                    }
                }
            }
            return data;
        }

вспомогательные методы:

private float getLlx(BoundingPoly poly) {
        try {
            List<Vertex> vertices = poly.getVertices();

            ArrayList<Float> xs = new ArrayList<>();
            for (Vertex v : vertices) {
                float x = 0;
                if (v.getX() != null) {
                    x = v.getX();
                }
                xs.add(x);
            }

            Collections.sort(xs);
            float llx = (xs.get(0) + xs.get(1)) / 2;
            return llx;
        } catch (Exception e) {
            return 0;
        }
    }

    private float getLly(BoundingPoly poly) {
        try {
            List<Vertex> vertices = poly.getVertices();

            ArrayList<Float> ys = new ArrayList<>();
            for (Vertex v : vertices) {
                float y = 0;
                if (v.getY() != null) {
                    y = v.getY();
                }
                ys.add(y);
            }

            Collections.sort(ys);
            float lly = (ys.get(0) + ys.get(1)) / 2;
            return lly;
        } catch (Exception e) {
            return 0;
        }
    }

    private float getUrx(BoundingPoly poly) {
        try {
            List<Vertex> vertices = poly.getVertices();

            ArrayList<Float> xs = new ArrayList<>();
            for (Vertex v : vertices) {
                float x = 0;
                if (v.getX() != null) {
                    x = v.getX();
                }
                xs.add(x);
            }

            Collections.sort(xs);
            float urx = (xs.get(xs.size()-1) + xs.get(xs.size()-2)) / 2;
            return urx;
        } catch (Exception e) {
            return 0;
        }
    }

    private float getUry(BoundingPoly poly) {
        try {
            List<Vertex> vertices = poly.getVertices();

            ArrayList<Float> ys = new ArrayList<>();
            for (Vertex v : vertices) {
                float y = 0;
                if (v.getY() != null) {
                    y = v.getY();
                }
                ys.add(y);
            }

            Collections.sort(ys);
            float ury = (ys.get(ys.size()-1) +ys.get(ys.size()-2)) / 2;
            return ury;
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * rotate rectangular clockwise
     *
     * @param poly
     * @param theta the angle of rotation in radians
     * @return
     */
    public BoundingPoly rotate(float centerX, float centerY, BoundingPoly poly, double theta) {

        List<Vertex> vertexList = poly.getVertices();

        //rotate all vertices in poly
        for (Vertex vertex : vertexList) {
            float tempX = vertex.getX() - centerX;
            float tempY = vertex.getY() - centerY;

            // now apply rotation
            float rotatedX = (float) (centerX - tempX * cos(theta) + tempY * sin(theta));
            float rotatedY = (float) (centerX - tempX * sin(theta) - tempY * cos(theta));

            vertex.setX((int) rotatedX);
            vertex.setY((int) rotatedY);
        }
        return poly;
    }

    /**
     * since Google Vision Api returns boundingPoly-s when Coordinates starts from top left corner,
     * but Itext uses coordinate system with bottom left start position -
     * we need invert the result for continue to work with itext.
     *
     * @return text units inverted symmetrically by 0X coordinates.
     */
    private BoundingPoly invertSymmetricallyBy0X(float centerY, BoundingPoly poly) {

        List<Vertex> vertices = poly.getVertices();
        for (Vertex v : vertices) {
            if (v.getY() != null) {
                v.setY((int) (centerY + (centerY - v.getY())));
            }
        }
        return poly;
    }

    /**
     *
     * @param centerX
     * @param poly
     * @return  text units inverted symmetrically by 0Y coordinates.
     */
    private BoundingPoly invertSymmetricallyBy0Y(float centerX, BoundingPoly poly) {
        List<Vertex> vertices = poly.getVertices();
        for (Vertex v : vertices) {
            if (v.getX() != null) {
                v.setX((int) (centerX + (centerX - v.getX())));
            }
        }
        return poly;
    }
person Yuliia Ashomok    schedule 18.10.2017

Обычно нам нужно знать реальный угол поворота текста на фотографии. Информация о координатах, представленная в API, является достаточно полной. Вам нужно только рассчитать угол между xy1 и xy0, чтобы получить угол поворота.

// reset
self.transform = CGAffineTransformIdentity;

CGFloat x_0 = viewData.bounds[0].x;
CGFloat y_0 = viewData.bounds[0].y;

CGFloat x_1 = viewData.bounds[1].x;
CGFloat y_1 = viewData.bounds[1].y;

CGFloat x_3 = viewData.bounds[3].x;
CGFloat y_3 = viewData.bounds[3].y;

// distance
CGFloat width = sqrt(pow(x_0 - x_1, 2) + pow(y_0 - y_1, 2));
CGFloat height = sqrt(pow(x_0 - x_3, 2) + pow(y_0 - y_3, 2));
self.size = CGSizeMake(width, height);

// angle
CGFloat angle = atan2((y_1 - y_0), (x_1 - x_0));
// rotation
self.transform = CGAffineTransformRotate(CGAffineTransformIdentity, angle);
person H. Misfortune    schedule 23.02.2021
comment
Привет, ожидается, что ответы будут на английском языке. Я перевел его для вас через онлайн-переводчик - пожалуйста, подтвердите, что это все еще имеет смысл. - person Ryan M; 23.02.2021

Конечная точка REST v1 уже содержит orientationDegrees в ответе:

https://cloud.google.com/vision/docs/reference/rest/v1/AnnotateImageResponse#Page

К сожалению, в google-cloud-vision 3.2.0 этого еще нет https://github.com/googleapis/python-vision/issues/156

person MuchHelping    schedule 08.06.2021