90度回転した画像でGoogle Cloud Vision API(TEXT_DETECTION)を試しました。それでも、認識されたテキストを正しく返すことができます。 (下の画像を参照)
つまり、画像が90度、180度、270度回転しても、エンジンはテキストを認識できます。
ただし、応答結果には正しい画像の向きの情報は含まれません。 (ドキュメント: EntityAnnotation )
とにかく、認識されたテキストを取得するだけでなく、orientationも取得する方法はありますか?
Googleは( FaceAnnotation :getRollAngle)と同様にサポートできますか?
Public Issue Tracker で説明されているように、エンジニアリングチームはこの機能リクエストを認識しており、現在、その実装に関するETAはありません。
注:画像のメタデータで方向情報がすでに利用できる場合があります。メタデータを抽出する方法の例は、この サードパーティライブラリ で確認できます。
広範な回避策は、返された "textAnnotations"について返された " boundingPoly " "vertices"を確認することです。検出された各Wordの長方形の幅と高さを計算することにより、長方形が「高さ」>「幅」(画像が横向き)の場合に、画像が表を上にしていないかどうかを判断できます。
Wordの文字シーケンスがわかっているという事実を利用して、次のようにWordの方向を推測できます(明らかに、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
次に、個々の単語の方向を使用して、ドキュメント全体の方向を推測できます。
ジャックファンの答えが私のために働いた。これは私の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;
}
}
}
メタデータから方向を取得できない場合があります。たとえば、ユーザーがモバイルデバイスのカメラを使用して間違った向きで写真を撮った場合。私の解決策は、Jack Fanの回答とgoogle-api-services-vision(Mavenで利用可能)に基づいています。
my TextUnit class
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;
}
私は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);
_