質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.63%

APIリクエスト自体は成功している(※画像をURLで渡した場合)が、画像をbase64でエンコードした途端にAPIリクエストが送信されなくなる。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,259

learning_ci

score 22

【ゴール】

Alamofireを用いてGoogle Cloud Vision APIに画像をPOSTし、画像から検出されて返される文字列を取得すること。

ただし、画像はiOSアプリから起動したiPhoneカメラで撮ったものを、base64でSrting型に変換したものを渡すこととする。

【できたこと①】

・以下のように"source"に画像URLを渡すと、APIリクエストが成功し、画像に含まれる文字列を認識してちゃんと返してくれました。

let parameters: Parameters = [
            "requests": [
                "image": [
                    "source": ["https://1.bp.blogspot.com/-DS_mPoCrYrQ/XVDZ7TPubmI/AAAAAAAASyI/Z7Dl29IkmQwtoiQc5g8a_eqILvpihY9RQCLcBGAs/s1600/fullsizeoutput_5bf.jpeg"
                    ]
                ],
                "features": [
                    "type": "TEXT_DETECTION",
                    "maxResults": 1
                ]
            ]
        ]

【できたこと②】

・iPhoneカメラで撮った画像をbase64でString型に変換することも、できていました。

// 画像をbae64でエンコードする
  private func base64EncodeImage(_ image: UIImage) -> String? {
        return image.pngData()?.base64EncodedString(options: .endLineWithCarriageReturn)
    }
// 画像に含まれるテキストを検出する 
func detect(from image: UIImage, completion: @escaping (OCRResult?) -> Void) {

        guard let base64Image = base64EncodeImage(image) else {
            completion(nil)
            return
        }

// Google Vision APIに、base64でエンコード済みのデータを渡す
        callGoogleVisionAPI(with: base64Image, completion: completion)

        print ("The type of base64Image is \(type(of: base64Image))")

    }


↓コンソール 

The type of base64Image is String // 画像がString型になっている

【できなかったこと】

iOSアプリから起動したiPhoneカメラで撮ったものを、base64でSrting型に変換したものをAlamofireを使ってAPIリクエストした。

実行前・実行中(runtime)エラーのいずれもなく(エラーが何も出てこない)、Build Succeedとなり、正常にアプリが起動する。

しかし全くの無反応で、403や404などのエラーも返ってきません。

let parameters: Parameters = [
            "requests": [
                "image": ["content": base64EncodedImage],
                "features": [
                    "type": "TEXT_DETECTION",
                    "maxResults": 1]
            ]
        ]

Google Cloud Platformコンソール画面に行ってAPIリクエストが届いているか確認しても、APIリクエストの数が増えていないので、リクエスト自体が、届いていないと思われます。

【その他のコード】

カメラを起動し、写真を撮るコード

//
//
// CameraViewController.swift
//
//

// MARK: AVCapturePhotoCaptureDelegateデリゲートメソッド
extension CameraViewController: AVCapturePhotoCaptureDelegate{
    // 撮影した画像データが生成されたときに呼び出されるデリゲートメソッド
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        if let imageData = photo.fileDataRepresentation() {
            // Data型をUIImageオブジェクトに変換
            let uiImage = UIImage(data: imageData)
            // 写真ライブラリに画像を保存
            UIImageWriteToSavedPhotosAlbum(uiImage!, nil,nil,nil) // 正常にライブラリに保存されることを確認済みです

            // こっから下はGoogle Cloud Vision APIのdetectBoundingBoxes()を呼び出している
            GoogleCloudOCR().detect(from: uiImage!) { ocrResult in

                // self.activityIndicator.stopAnimating() //読み込み中画面を使う場合はコメントアウトを消す
                guard let ocrResult = ocrResult else {
                    fatalError("Did not recognize any text in this image")
                }
                print(ocrResult)
            }
        }
    }
}

【Google CloudにAPIリクエストするコード】

//
//  GoogleCloudOCR.swift
// 
//

import Foundation
import Alamofire
import UIKit

class GoogleCloudOCR {

    // Google Cloud Vision API
    var apiKey = "<My API Key>"
    var apiURL: URL {
        return URL(string: "https://vision.googleapis.com/v1/images:annotate?key=\(apiKey)")!
    }

    func detect(from image: UIImage, completion: @escaping (OCRResult?) -> Void) {

        guard let base64Image = base64EncodeImage(image) else {
            completion(nil)
            return
        }
        callGoogleVisionAPI(with: base64Image, completion: completion)

        print ("The type of base64Image is \(type(of: base64Image))")
        print ("callGoogleVisionAPI(with: base64Image, completion: completion) has been called")
    }

    private func callGoogleVisionAPI(
        with base64EncodedImage: String, completion: @escaping (OCRResult?) -> Void) {
        print ("func callGoogleVisionAPI() has been called")

// APIリクエストが送れなかったparameters(【できなかったこと】)
        /*
        let parameters: Parameters = [
            "requests": [
                "image": ["content": base64EncodedImage],
                "features": [
                    "type": "TEXT_DETECTION",
                    "maxResults": 1]
            ]
        ]
        */

// 以下のparametersならAPIリクエストが成功し、画像に含まれる文字列が返された(【できたこと①】)
        let parameters: Parameters = [
            "requests": [
                "image": [
                    "source": ["imageUri": "https://1.bp.blogspot.com/-DS_mPoCrYrQ/XVDZ7TPubmI/AAAAAAAASyI/Z7Dl29IkmQwtoiQc5g8a_eqILvpihY9RQCLcBGAs/s1600/fullsizeoutput_5bf.jpeg"
                    ]
                ],
                "features": [
                    "type": "TEXT_DETECTION",
                    "maxResults": 1
                ]
            ]
        ]

        let headers: HTTPHeaders = [
            "Content-Type": "application/json",
            "X-Ios-Bundle-Identifier": Bundle.main.bundleIdentifier ?? ""]

        Alamofire.request(
            apiURL,
            method: .post,
            parameters: parameters,
            encoding: JSONEncoding.default,
            headers: headers)
            .responseJSON { response in
                if response.result.isFailure {
                    completion(nil)
                    return
                }
                print(response.result.debugDescription)
                debugPrint(response)
        }
    }

    private func base64EncodeImage(_ image: UIImage) -> String? {
        return image.pngData()?.base64EncodedString(options: .endLineWithCarriageReturn)
    }

    struct Vertex: Codable {
        let x: Int?
        let y: Int?
        enum CodingKeys: String, CodingKey {
            case x = "x", y = "y"
        }
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            x = try container.decodeIfPresent(Int.self, forKey: .x)
            y = try container.decodeIfPresent(Int.self, forKey: .y)
        }

        func toCGPoint() -> CGPoint {
            return CGPoint(x: x ?? 0, y: y ?? 0)
        }
    }

    struct BoundingBox: Codable {
        let vertices: [Vertex]
        enum CodingKeys: String, CodingKey {
            case vertices = "vertices"
        }
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            vertices = try container.decode([Vertex].self, forKey: .vertices)
        }
    }

    struct Annotation: Codable {
        let text: String
        let boundingBox: BoundingBox
        enum CodingKeys: String, CodingKey {
            case text = "description"
            case boundingBox = "boundingPoly"
        }
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            text = try container.decode(String.self, forKey: .text)
            boundingBox = try container.decode(BoundingBox.self, forKey: .boundingBox)
        }
    }

    struct OCRResult: Codable {
        let annotations: [Annotation]
        enum CodingKeys: String, CodingKey {
            case annotations = "textAnnotations"
        }
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            annotations = try container.decode([Annotation].self, forKey: .annotations)
        }
    }

    struct GoogleCloudOCRResponse: Codable {
        let responses: [OCRResult]
        enum CodingKeys: String, CodingKey {
            case responses = "responses"
        }
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            responses = try container.decode([OCRResult].self, forKey: .responses)
        }
    }
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • takabosoft

    2019/08/21 09:46 編集

    当てずっぽうですが、.endLineWithCarriageReturn無しでbase64エンコードしてみるとどうでしょうか?
    (改行が入ってJSONとしての構文エラーになったのかな?と。ただ、何にせよレスポンスが無いのは不思議ですね)

    キャンセル

  • learning_ci

    2019/08/21 12:55

    ありがとうございます!

    一旦、.endLineWithCarriageReturnを消して実行したところ、5分程度何も起こらず、その後、Code=-1001 "The request timed out."が現れました。

    ここで.endLineWithCarriageReturnを戻して再度実行したところ、やはり5分程度何も起こらず、その後、Code=-1001 "The request timed out."が現れました。

    したがって、真の原因はtime outだったと思われます(せっかちな性格のせいでエラーが返ってくるまで待てなかったせいで、お手数おかけし、すみません)。

    試しに
    print (base64EncodedImage) // bae64でエンコード後の文字列を出力

    してみると、数百行、いや1000行以上ありそうな文字列が出力され、ハイスペックなMacBookPro(↓)がフリーズしながらコンソールに5分以上出力を続けました。

    【利用MacBookPro】
    13-inch, 2019, Four Thunderbolt 3 ports
    プロセッサ 2.8 GHz Intel Core i7
    メモリ 16 GB 2133 MHz LPDDR3

    なのでデータが重過ぎたのが原因かと、素人ながらに思っています。
    画像を圧縮などして、time outにならない方向性を探ってみます(もし、この点に見当違いあればご指摘いただけると幸いです)。

    解決次第、追記したいと思います。

    回答がない中で、takabosoftさんのコメントが励みになりました!
    ありがとうございます(^^)

    キャンセル

  • takabosoft

    2019/08/21 13:34

    👍

    キャンセル

回答 1

check解決した方法

0

Stack Overflowのページに、画像の圧縮方法が載っていて、これを試すと約20秒で正常な結果が返ってきました♡

How do I resize the UIImage to reduce upload image size

https://stackoverflow.com/questions/29137488/how-do-i-resize-the-uiimage-to-reduce-upload-image-size

実際に追加したコードは、以下です。

extension UIImage {
    func resized(withPercentage percentage: CGFloat) -> UIImage? {
        let canvas = CGSize(width: size.width * percentage, height: size.height * percentage)
        return UIGraphicsImageRenderer(size: canvas, format: imageRendererFormat).image {
            _ in draw(in: CGRect(origin: .zero, size: canvas))
        }
    }
}

↓ 圧縮のために呼び出す

private func base64EncodeImage(_ image: UIImage) -> String? {
        // データを軽くするために画像を圧縮        
        let resizedImage = image.resized(withPercentage: 0.2)

        // base64でエンコード
        return resizedImage?.pngData()?.base64EncodedString()
    }

↓返ってきたレスポンス(抜粋)

description = "Boris Johnson is in Paris for Brexit\nwith the French president saying the\nrespected.\nBut he added that the Ireland-Northern\n\"indispensable\" to preserving political st\nThe backstop, opposed by Mr Johnson,\nthe island of Ireland after Brexit\nMr Johnson said that with \"energy and c\nforward\".\nOn Wednesday German Chancellor Ange\nthe UK to find a workable plan.\n";
locale = en;

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.63%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • APIに関する質問
  • APIリクエスト自体は成功している(※画像をURLで渡した場合)が、画像をbase64でエンコードした途端にAPIリクエストが送信されなくなる。