5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

VRSNSで撮った写真を自動でいい感じに選択して定期的にTwitterに投稿できるようにしてみた

Last updated at Posted at 2022-10-01

こんにちは、とうとうです。この記事はXR好き開発合宿ブログリレー2日目の記事です。2泊3日の合宿中に開発した内容を紹介していきます。

概要

VRSNSで撮った大量の写真の中からいい感じに写真を選んでTwitterに投稿するのって大変ですよね...それを自動で(ランダムに選ぶよりかは)いい感じに選んで投稿するシステムを作ってみました!

スクリーンショット 2022-09-24 23.50.17.png

仕組みとしては、写真を保存したフォルダを読み込む、できるだけ最近撮影した画像を選ぶ、dHashで画像距離計算をして似ている画像は選ばない、API経由でTwitterに投稿する、定期実行にはWindows標準アプリのタスクスケジューラーを利用するといったものです。

VRChatで毎日増える大量の写真の中からいい感じに写真を選んで毎日投稿するのは手間なんですが、このシステムを使えば楽ができます。
実は、とうとうさんの「最近の!」ツイートは2021年4月頃から自動化されていて、最近の写真をランダムに選ぶという手法をとっていたのですが、それだと同時に似た画像を選んでしまいbot感が出てしまっていたので、今回の合宿でdHashを用いた画像距離計算をし、似た画像を選ばないアルゴリズムを追加したという経緯です。

また、GitHub Actionsを利用してリリースページに成果物を置けるようにしました。テストを書いていなかったり、まだまだ機能追加できる部分があったりするのですが、少しづつ良くしていきたいです。リポジトリはこちらになります。

紹介スライド

誰向け

  • PickPicTweetがどんな実装か知りたい
    • フォルダやファイル移動周りの処理について
    • dHashについて
    • Twitter APIの簡単な使い方について
  • VRSNSで撮った写真を自動で投稿できるようにしたい
    • 今回の記事の場合、自力でTwitter開発者申請してRead/Write用のトークンを取得できること
  • GitHub Actionsを利用して、.NETプロジェクトをWindows環境でビルドし、リリースページのアセットに特定のファイルを置くyamlファイルを参考にしたい
  • Windows標準アプリのタスクスケジューラーの使い方を知りたい

機能

  • 写真フォルダ配下を月別・日付別に割り振り直す
    • デフォルトでは機能OFF
    • 写真フォルダの構成を変えてしまうので利用は慎重に
  • いい感じに写真を選ぶ
    • できるだけ最近撮影した写真を選びます
    • dHashを用いた画像類似度を計算し似ている写真は投稿写真から除かれます
    • 過去(100枚)に投稿した写真は取り除かれます
    • secretフォルダ直下の写真は選ばれない
      • 手動でsecretフォルダを作成し、その直下に選びたくない写真を予め入れておく必要があります
  • 画像付きツイート

実行時オプション

  • --full-path "画像が含まれるフォルダのフルパス"
    • デフォルトはVRChatの画像フォルダへのパス
  • --every-dir n
    • 0:デフォルト値、何もしない
    • 1:画像を月別のフォルダに振り分ける。一度実行するともとに戻せないので注意が必要。
    • 2:画像を月別フォルダ配下の日付別フォルダに振り分ける。一度実行するともとに戻せないので注意が必要。
  • --pic-pool-count n
    • n枚の写真から4枚が選ばれる。デフォルトは100
  • --no-tweet n
    • 0:ツイートする、デフォルト値
    • 1:ツイートしない

使い方

アプリケーションを使う場合は自己責任でお願いします。

リリースページから実行ファイルをダウンロード

Visual Studio2022で実行ファイルをビルド

実装について

以下のような実装となっています。

  • VRChatの画像フォルダ配下を日別フォルダに振り分ける
  • いい感じに写真選ぶアルゴリズム
    1. 直近100枚の画像を時系列降順に並び替えたリストを作成
    2. リストから過去に投稿した画像を取り除く
    3. リストの前半から1枚(pic1)をランダムに選び、リストから除く
    4. pic1との画像距離が一定以下(画像類似度が一定以上)の画像をリストから除く
    5. リストの前半からランダムに1枚(pic2)を選ぶ
    6. 2-4を繰り返し同様にpic3,pic4を選ぶ。リストの要素数が0になるかpic4を選ぶかで終了
    7. 最大4枚の選ばれた画像パスをローカルテキストに保存
  • TwitterAPIを利用して選ばれた画像をツイート
  • 定期実行はWindows標準アプリのタスクスケジューラーで投稿間隔と実行ファイルを登録

画像を日別フォルダに振り分ける

画像作成時刻で月別または月別/日付別にフォルダを作成しそのフォルダ配下に画像を移動します。実装は以下です。

いい感じに写真を選ぶアルゴリズム

(ランダムに選ぶよりかは)いい感じに写真を選ぶアルゴリズムですが、以下のような感じになっています。

  1. 直近100枚ぐらいの画像のリスト_orderedSetを時系列降順に並び替え
  2. _orderedSetの前半から1枚の画像(pic1)をランダムに選び、_orderedSetから除く
  3. pic1との画像距離が一定以下(画像類似度が一定より大きい)の画像を_orderdSetから除く
  4. _orderedSetの前半からランダムに1枚(pic2)を選ぶ
  5. 3-4を繰り返し同様にpic3,pic4を選ぶ。_orderedSetの要素数が0になるかpic4を選ぶかで終了

実装は以下になっています。

dHashを用いた画像距離計算について

dHashを用いた画像距離計算は以下のようなシンプルなアルゴリズムとなっています。

スクリーンショット 2022-09-25 2.53.22.png

ハッシュ値の比較計算

dHashの良さは画像を64bitのハッシュ値に変換することができ、2つの画像を表すハッシュ値の比較がXORPOPCNTといった(おそらく)ハードウェア命令のレベルで実装されている関数を利用して計算するので高速なはずです。(そもそも高速にする必要があるのかみたいな話がありますが、それは置いてもらって...)

そして、XORPOPCNTを用いたハッシュ値の比較計算は以下のようになります。

image.png

実際の実装は以下の実装でシンプルにかけて嬉しい気持ちになれます。

PicSimilality.cs
/// <summary>
/// ulong(uint64)のハミング距離[0,64]を計算
/// 二つの画像のハッシュ(uint64)をXORしてPopCount(いくつのビットが1かをカウントする)
/// XORとPopCountはどちらもプリミティブな命令なので高速に計算できる
/// ref:https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%9F%E3%83%B3%E3%82%B0%E8%B7%9D%E9%9B%A2
/// </summary>
/// <param name="img1"></param>
/// <param name="img2"></param>
/// <returns></returns>
public int ComputeHammingDistance(Bitmap img1,Bitmap img2)
{
    // 画像サイズを小さくして、グレースケールに変換しハッシュ化
    var hash1 = ComputeAdjacentPixelDiff(ConvertGray(ReduceSize(img1)));
    var hash2 = ComputeAdjacentPixelDiff(ConvertGray(ReduceSize(img2)));
    // ハミング距離の計算
    return BitOperations.PopCount(hash1^hash2);
}

TwitterAPIを使用して画像ツイート

Twitter開発者プラットフォームで開発者用トークンを申請して取得

こちらの記事【2022年度最新版】Twitter API 申請方法(Twitter v1.1 v2対応)などを参考にしながら開発者用トークンを申請して取得してください。Read/Write権限が必要なのでご注意ください。

トークンをappsettings.jsonに保存

取得したトークンをプロジェクト内のPickPicTweet/appsettings.jsonに設定します。

appsettings.json
{
  "ConsumerKey": "",
  "ConsumerKeySecret": "",
  "AccessToken": "",
  "AccessTokenSecret": ""
}

PickPickTweet.csprojファイルにappsettings.jsonがBuildに含まれるように下記を追加

PickPicTweet/PickPickTweet.csprojファイルにappsettings.jsonがBuildに含まれるように下記を追加

PickPicTweet.csproj
<ItemGroup>
    <None Update="appsettings.json"> 
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
</ItemGroup>

画像付きツイート

画像をアップロードしてメディアidを取得し、画像付きツイートをします。

Twitter.cs
/// <summary>
/// 画像をアップロードしてメディアidを取得
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private long UploadImage(string path)
{
    MediaUploadResult result = Token.Media.Upload(media: new FileInfo(path));
    return result.MediaId;
}

/// <summary>
/// 最大4枚の画像付きツイート
/// 画像がない場合はテキストのみツイート
/// </summary>
/// <param name="text"></param>
/// <param name="paths"></param>
public void ImageTweet(string text, List<string> paths)
{
    // 画像をアップロードして返ってきたmedia_idを追加する
    List<long> mediaIds = paths.Select(UploadImage).ToList();
    Token.Statuses.Update(new
    {
        status = text,
        media_ids = mediaIds
    });
}


タスクスケジューラで定期実行

実行ファイルの生成

Visual Studio 2022Riderでプロジェクトのビルドを行い、実行ファイルを生成します。ビルド環境が.NET6.0なので注意が必要です。

リポジトリからcloneまたはダウンロードしてきたら、Visual Studio 2022でそのディレクトリを選んで開きます。ソリューションとして認識されない場合は、フォルダービューからPickPicTweet.slnをクリックするとソリューションとして認識されるはずです。そのあと、appsettings.jsonにトークンを設定し、下図のようにビルドすれば実行ファイルを生成できます。
image.png

または、Releases · tou-tou/PickPicTweetから拾ってくる方法もありますが、この場合はTwitter用のトークンを設定したappsettings.jsonと実行ファイルを同じディレクトリに配置してください。

タスクスケジューラーに実行ファイルを登録

Windowsの検索窓から「タスクスケジューラ」で検索し起動

基本タスクの作成します。
image.png

適当な名前をつけます。
image.png

トリガーを毎週で指定し開始時期や実行したい日付を選びます。
image.png

実行ファイルを登録し(必要であれば)引数を指定します。下図では--every-dir 2を指定しますが、このオプションは画像フォルダ配下を月別/日付別フォルダに変えてしまい、一度実行すると元に戻せないので注意が必要です。
image.png

下図は月別/日付別にファイルが割り振られた様子
image.png

GihHub Actionsでのリリース

mainブランチにpushしたら、.NETプロジェクトをWindows環境上でビルドして、実行ファイルや設定ファイルのテンプレートをリリースページのAssetとしておくようにしました。

yamlファイルはこんな感じです。

今後の課題とか

  • 単体テストを書く
  • VRSNSのログとかを使ってアバターやワールドの種類を画像選択に組み込む
  • 撮影した写真の中で投稿したくない画像(ネタバレ系とか取り扱い注意な画像)を投稿しないようにする
  • Twitter連携できるようにして誰でも使えるようにする

まとめ

正直なところ、テストを書いていなかったり、Twtter開発者申請が必要だったり、明らかに撮影ミスした写真が選ばれたりするなど、使い勝手がよくない状態でリリースしたり記事を書いたりするのには抵抗感があったのですが、いつまでたっても公開しないのもよくないので、とりあえず公開してみることにしました。この記事がだれかのお役にたてば幸いです。

謝辞

また、最後になりますが、今回の開発合宿では多くの支援者の皆様にサポートしていただきました1。楽しく開発できたり、メンバーとの交流ができたりでとても楽しかったです。本当にありがとうございました。

  1. 【支援求む】XR好き学生14名に2泊3日温泉開発合宿をプレゼントしたい (支援者30名・募集終了)|イワケン|note

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?