AzureのComputer Vision APIのOCR機能を試してみた

Azure Cognitive ServicesにあるComputer Vision APIを使ってみました。

Computer Vision — 画像処理および画像分析 | Microsoft Azure

Computer Vision APIといってもいくつか機能があって、今回はそのうちOCR機能をJavaScript(というかTypeScript)で試してみました。

OCRを試す

APIの仕様はここにあります。

使い方は簡単でAPIのエンドポイントURLに、画像のURLをJSON形式でPOSTするか、画像のバイナリデータをPOSTするだけです。

TypeScriptでバイナリデータをPOSTする場合は次のような感じになるのかなと思います。

// 画像をBlobとして取得するとして
const blob: Blob = await getBlob();

// APIのURL(今回は東アジア)
const apiUrl = "https://eastasia.api.cognitive.microsoft.com/vision/v1.0/ocr";

// APIのキー
const apiKey = "内緒のキー";

// APIを呼び出す
const response = await fetch(apiUrl, {
    method: "POST",
    // リクエストのヘッダにメディアタイプとAPIのキーを指定する
    headers: {
        "Content-Type": "application/octet-stream", // ボディのメディアタイプ
        "Ocp-Apim-Subscription-Key": apiKey  // APIのキー
    },
    // リクエストのボディにBlobを設定する
    body: blob
});

// レスポンスはJSON
// OCRの結果を取得できる
const result: IResult = await response.json();

/*
// 適当に行(文字列)を抽出してみるならこんな感じ
result.regions
   .map(region => region.lines)
   .reduce((previous, current) => previous.concat(current))
   .map(line => line.words.map(word => word.text).join(" "))
   .forEach(line => console.log(line));
*/

レスポンスにはOCRの結果が入ります。フォーマットはJSONで、次のようなインターフェイスを持ったオブジェクトです。単にテキストだけでなく、その座標も取得できるようです。

// OCRの結果
interface IResult {
    language: string;
    textAngle: number;
    orientation: string,
    // 複数のリージョン
    regions: IRegion[];
}

// リージョン
interface IRegion {
    boundingBox: string;
    // 複数行を持つ
    lines: ILine[];
}

// 行
interface ILine {
    boundingBox: string;
    words: IWord[];
}

// 単語
interface IWord {
    boundingBox: string;
    text: string;
}

参考

JavaScriptチュートリアルとかはこのあたり。

ASP.NET Core MVC - ビューコンポーネントの呼び出し方色々

ASP.NET Core MVCのビューコンポーネントを試してみました。

ビュー コンポーネント | Microsoft Docs

ビューコンポーネントは従来のASP.NET MVCにあったチャイルドアクション(子アクション、ビューからRenderActionで呼び出していたあれ)の代わりになる機能ですね。呼び出し方が色々あるようなので、ちょっと書き残しておきます。

ビューコンポーネント名を指定する

上記ドキュメントにありますが、ビューコンポーネント名を指定して呼び出す方法があります。試してみましょう。

次のようなビューコンポーネントクラスを用意します。csファイルはプロジェクト直下に「ViewComponents」フォルダを作ってそこに入れるといいのかな?

// 適当なビューコンポーネント
public class Sample1ViewComponent : ViewComponent {
    public async Task<IViewComponentResult> InvokeAsync(int no, string name) {
        // 何か重たい処理があるとして
        await Task.Delay(0);

        // ViewBagも使える(それかViewModelを作るか)
        ViewBag.No = no;
        ViewBag.Name = name;

        return View();
    }
}

続いてビューコンポーネントの呼び出し側。Component.InvokeAsyncメソッドの1つ目の引数にビューコンポーネント名を、2つ目の引数にはSample1ViewComponent.InvokeAsyncメソッドの引数を匿名オブジェクトで指定します。

@await Component.InvokeAsync("Sample1", new { no = 1, name = "Sample1 string" })

型を指定する

typeof演算子で型を指定して呼び出す方法もあります。

@await Component.InvokeAsync(typeof(Sample1ViewComponent), new { no = 2, name = "Sample1 typeof" })

ジェネリック メソッドを使う

ジェネリックジェネリクス?)メソッドが用意されているので、次のような呼び出し方もできます。ただ丸括弧"()"を使う必要があってちょっとだけ残念。ちょいちょい括弧を忘れてコンパイラさんに怒られそう。

@(await Component.InvokeAsync<Sample1ViewComponent>(new { no = 3, name = "Sample1 generics" }))

引数にクラスを使う

ビューコンポーネントを呼び出す引数は、匿名オブジェクトではなく具体的なクラスでも問題なさそうです。

引数が多いという体で引数をクラスにまとめます。

// 適当なビューコンポーネントその2
public class Sample2ViewComponent : ViewComponent {
    // ビューコンポーネントの呼び出しパラメータクラス
    // インナークラスにする必要はないけど
    public class Param {
        public int No { get; set; }
        public string Name { get; set; }
    }

    // 引数にParamクラスを受け取る
    public async Task<IViewComponentResult> InvokeAsync(Param param) {
        // 何か重たい処理があるとして
        await Task.Delay(0);

        ViewBag.No = param.No;
        ViewBag.Name = param.Name;

        return View();
    }
}

呼び出し側もわかりやすいのでこの方法もありかなと思います。

@await Component.InvokeAsync(typeof(Sample2ViewComponent), new Sample2ViewComponent.Param { No = 4, Name = "Sample2" })

コントローラのアクションから呼び出す

ちょっと話は変わりますが、ビューコンポーネントはビューだけでなくコントローラのアクションメソッドでも呼び出せます。Ajaxでも使いたいときですかね。

// ビューコンポーネントを返すアクション
public IActionResult Sample1() {
    return ViewComponent(typeof(Sample1ViewComponent), new { no = 5, name = "Sample1 controller" });
}

こんなところかなと。

ASP.NET Core MVC - ビューの基底クラスを変更する

ASP.NET Core MVCのビュー全体で、プロジェクト独自のプロパティやメソッドを追加して利用したいことがあるかなと思います。

デフォルトでは、ビューはMicrosoft.AspNetCore.Mvc.Razor.RazorPage<TModel>を継承しています。このクラスを派生したクラスを用意することで実現できます。

詳しくはこのあたり。

ASP.NET Core の Razor 構文リファレンス | Microsoft Docs

上記ドキュメントそのままなんですが、覚え書きとしてメモしておきます。

まずRazorPage<TModel>を継承した独自のRazorページクラスを用意します。

// プロジェクト独自のRazorページクラス
public abstract class CustomRazorPage<TModel> : RazorPage<TModel> {
    // 独自のプロパティやメソッドを追加したい
    public int CustomValue { get; set; }
}

つぎに@inheritsディレクティブを使って、用意したRazorページクラスを継承するようにします。プロジェクト全体でビューの基底クラスを変えるには、Viewsフォルダ直下の_ViewImports.cshtmlで指定します。

@* _ViewImports.cshtml *@
@* ビューの基底クラスを変更する *@
@inherits CustomRazorPage<TModel>

すると、各cshtmlでカスタムRazorページのプロパティやメソッドが使えるようになります。

@* 各cshtml *@
@* 独自のプロパティが使えるようになる *@
@CustomValue

ASP.NET Core MVC - 生成されるURLを小文字にする

ASP.NET Core MVCを勉強し始めたので、その記録というかメモというか小ネタを残していきたいと思います。

まずはタグヘルパーやHTMLヘルパー(URLヘルパー)を使って生成するURLを小文字にする方法です。CoreじゃないASP.NET MVCでは、RouteCollection.LowercaseUrlsをtrueに設定していましたが、CoreではRouteOptions.LowercaseUrlsにtrueを設定するようです。

public class Startup {
    public void ConfigureServices(IServiceCollection services) {
        // MVCをサービスに追加
        services.AddMvc();

        services.Configure<RouteOptions>(options => {
            // URLを小文字にする
            options.LowercaseUrls = true;
        });
    }
}

RouteOptions.LowercaseUrls Property (Microsoft.AspNetCore.Routing) | Microsoft Docs

Dropbox.NETを試す

DropboxAPIを使ってみようとふと思いました。公式のドキュメントを見てみるとDropbox.NETというライブラリがあったので、チュートリアルを参考にしながら試してみました。

.NET - Developers - Dropbox

準備

コンソールアプリプロジェクトを作って、NuGetでDropbox.Apiをインストールします。

Install-Package Dropbox.Api

NuGet Gallery | Dropbox.Api 4.7.0

Dropbox APIを使うために、App Consoleでアプリを登録して、OAuth2のアクセストークンを生成しておきます。(手順は省略。公式ドキュメントを見てください。)

https://www.dropbox.com/developers/apps

基本的な操作を試す

フォルダの追加・削除やファイルのアップロード・ダウンロードなど基本的な操作を試していきますが、まずはエントリポイントです。

// Program.cs
class Program {
    static void Main(string[] args) {
        const string token = "ここにアクセストークン";

        // アクセストークンを使ってDropboxクライアントを生成
        using (var client = new DropboxClient(token)) {
            // Dropboxサンプルを実行
            new DropboxSample(client).RunAsync().Wait();
        }
    }
}

// DropboxSample.cs
// Dropboxサンプル
class DropboxSample {
    // Dropboxクライアント
    private readonly DropboxClient _client;

    public DropboxSample(DropboxClient client) {
        _client = client;
    }

    // サンプルを実行
    public async Task<bool> RunAsync() {
        // todo: ここに以下のコードがある体で
        return true;
    }
}

ここからのコードはDropboxSample.RunAsyncメソッドの中身です。コードの順番もそのままで実行できるはず。

フォルダを作成
// "/Temp/sample"フォルダを作成
await _client.Files.CreateFolderV2Async("/Temp/sample");
ファイルをアップロード

作成したフォルダにテキストファイルを2つアップロードします。streamはファイルの中身です。

// "text1.txt"
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("file1 content"))) {
    await _client.Files.UploadAsync("/Temp/sample/text1.txt", body: stream);
}

// "text2.txt"
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("file2 content"))) {
    await _client.Files.UploadAsync("/Temp/sample/text2.txt", body: stream);
}
ファイル一覧を取得

ListFolderAsyncメソッドを使ってファイル一覧を取得します。結果のListFolderResult.HasMoreプロパティがtrueの場合は、ListFolderContinueAsyncメソッドを使ってファイル一覧の続きを取得します。

// ファイルの一覧を取得
// ListFolderContinueAsyncを試したいのでlimitを1にする
var result = await _client.Files.ListFolderAsync("/Temp/sample", limit: 1);
foreach (var entry in result.Entries) {
    Console.WriteLine(entry.Name);
    // text1.txt
}

while (result.HasMore) {
    result = await _client.Files.ListFolderContinueAsync(result.Cursor);

    foreach (var entry in result.Entries) {
        Console.WriteLine(entry.Name);
        // text2.txt
    }
}
ファイルをダウンロード
// ファイルをダウンロード
// "text1.txt"
using (var response = await _client.Files.DownloadAsync("/Temp/sample/text1.txt")) {
    Console.WriteLine(await response.GetContentAsStringAsync());
    // file1 content
}

// "text2.txt"
using (var response = await _client.Files.DownloadAsync("/Temp/sample/text2.txt")) {
    Console.WriteLine(await response.GetContentAsStringAsync());
    // file2 content
}
ファイルを削除
// ファイルを削除
// "text1.txt"
await _client.Files.DeleteV2Async("/Temp/sample/text1.txt");

// "text2.txt"
await _client.Files.DeleteV2Async("/Temp/sample/text2.txt");
フォルダを削除
// "/Temp/sample"フォルダを削除
await _client.Files.DeleteV2Async("/Temp/sample");

こんなところかなと。

ソース全体はこちらに。

Dropbox.NETを試す · GitHub