20211128のC#に関する記事は7件です。

C#のOpenCVSharpのデバッグを楽にする

前書き OpenCVSharpを用いてテンプレートマッチングを行っていた時に 前処理としていくつか画像処理を加えるのですが、その際のデバッグ方法として 毎度Cv2.Imshowを呼び出して確認してました。 これがあまりにもめんどくさかったので簡単に確認できるツールを作りました。 注意点 OpenCVのすべての機能を網羅していないです。 現在は色変換、二値化のみ対応しています。 更新履歴 更新日 Release名 Release内容 2021/11/28 OpenCV_GUI_PreRelese 色変換、二値化の機能追加 Releaseファイル 使い方 画像選択画面&機能選択画面 上部メニューの画像処理をクリックすることで、色変換と二値化の機能を選択できます。 画像ファイル選択ボタンを押すことで画像ファイルを選択できます。 画像ファイルを選択した後に画像表示ボタン押すことで画像を表示できます。 画像全体を表示したい場合は縮小サイズで調整してください。 今のところ画像を拡大する機能はありません。 色変換画面 変換したいカラーコード(初期状態ではグレースケール)を選択する。 画像によっては変換できないものもあります。 二値化画面 二値化の手法をThresholdTypeから選択する。(初期状態では大津の二値化) ThresholdTypeによっては閾値を選択する必要な場合がある。 次回の追加予定機能 テンプレートマッチング機能を追加予定です。 UIを整える予定です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクトを上下にゆらゆらさせる

ゆっくりその場から上下に動きます インスペクタのspeedで上下運動のスピードを変えられます using System.Collections; using System.Collections.Generic; using UnityEngine; public class UpDownLoop : MonoBehaviour { Vector3 startPos; public float speed = 1.0f; void Start() { startPos = transform.position; } void Update() { float sin = Mathf.Sin(Time.time * speed) +startPos.y; //ゲーム開始からの経過時間を1~-1に変換 transform.position = new Vector3(startPos.x,sin,startPos.z); } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

任意のオブジェクトをエモく回転&ふんわり上昇させる

MVなんかでよくある、フワ…とオブジェクトが上昇してく演出を再現しました。 回転もさせられます。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class RiseUpper : MonoBehaviour { Vector3 startPos; public float x = 0.5f; public float y = 0.5f; public float z = 0.5f; public float speed = 0.005f; //上昇スピード void Update() { transform.Rotate(x,y,z); transform.position = new Vector3(0, startPos.y, 0); startPos.y += speed; } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UniRxとは?(UniRx導入に悩んでいる人向け)

00. この記事の目的 この記事の目的はUniRxを導入または学習を検討している方に軽く概要を掴んで頂いて検討材料にしていただければなと思い書き始めました。そのため、細かなUniRxの書き方等はこの記事に含まれていません。 01. UniRxとは? 通常、UnityではReactive Extensions(以降Rx)は機能しません。そこで、Yoshifumi KawaiさんによりをUnity向けにRxの概念とさまざまなユーティリティを追加し、再実装しなおしたものがUniRxになります。それ故、Rxの概念を理解していることがUniRxを使用する上で非常に重要になってきます。 まとめ ・UniRx = unity + Reactive Extensions 02. Rx(Reactive Extensions)とは? ①Rxの定義 Rxとは監視可能なシーケンスを使用して「非同期」「イベント」ベースのプログラム制作を目的として制作されたライブラリーで、Observerパターンと言うデザインパターンによって構成されています。 ここでよくある間違いについてですが、 Rxと関数型リアクティブプログラミング(functional reactive programming)が混同されることがよくありますがこれらは異なるものであるということに注意してください。Rxの公式サイトでもこのふたつの違いについて、「関数型リアクティブプログラミングは時間の経過に伴い連続的に変化する値によって変化していくのに対して、Rxは時間の経過とともに放出される離散型の動きをする。」と説明されており、二つが別のものであることを理解できます。 まとめ ・Rxは複雑な非同期処理やイベント処理、時間が関係する処理を、 LINQ形式で簡単かつ宣言的に記述できる。 ・Reactive Extensions ≠ 関数型リアクティブプログラミング 03. Observerパターンとは? ObserverパターンはSubject(監視対象)とObserver(観測者)の2つの役割から成り、監視対象の状態が変化した際に観測者に通知されるデザインパターンのことを言い、状態変化に応じた処理を記述する際に好まれる書き方とされています。 Example: オークションを例に挙げると観測者が入札者となり、競売人が監視対象となります。下記の図のように初めに13番の札を持つ人が入札した際Subjectの入札金額に変更が起こります(監視対象の状態変化)。次に入札金額の変更を競売人がオークション対象者に通知します(観測者への通知)。 (https://sourcemaking.com/design_patterns/observer) ここまでで、ObserverパターンはObserverがSubjectを観察するものなんだと思った方もいると思うのですが、実際は 『Subjectが各Observerを管理する』というのが正しい認識になるので注意してください。 まとめ ・Observerパターンとは監視しているオブジェクトに発生するイベントについて複数のオブジェクトに通知するサブスクリプションメカニズムを定義できる動作設計パターン。 ・Observerは受け身でSubjectがObserverへの参照をもつ。 04. 結局UniRxって導入するべきなの? UniRx導入を検討している方が一番気になるところはここだと思います、私もUniRxを勉強するにあたり色々な記事等を読みましたが、基本的に書かれている内容はUniRxの良い点がほとんどした。逆に疑いたくなるレベルでした、しかし、不安とは裏腹に少なくとも私にとってはUniRxは非常に便利なものでした。 悪い点 悪い点① 学習コストが高い 私が読んだ記事にUniRxは学習コストが非常に高いと言った内容が書かれたものがありました。実際高いと言ったら高いのかも?抽象的な言い方だったのではっきり言うと、UniRx自体の学習コストは全く高くないです。しかし、一定数UniRxを勉強し始めた時に「難しくない?」「学習コスト高!!」と感じる人もいると思います。このように感じる人の原因は「C#のevent」「メッセージパッシング」「非同期処理」「unityのUpdate」「シーケンス(LINQ)」「Rx」について理解が足りていないことにあると思います。もし、どれかひとつでも当てはまるようでしたらページ最後におすすめのサイトのURLを添付してあるので確認してみてください。 悪い点② UniRxそのものが少し重いことによる弊害 UniRxは1フレームに一回しか呼ばれない処理や非同期処理(Ex.通信、IO)といった負荷の重くない処理を得意としています。一方、1フレームに何度もインスタンス(Observable)の生成が行われるといった負荷の重い処理での利用には向いていません。理由は単に処理が重くなり場合によっては動かなくなるからです。 悪い点②に関しては一応悪い点としてあげていますが、unityのUpdateでも同じことが言えるのでUniRxだけの問題ではないと思います。 良い点 良い点① UniRxのオペレータによる従来のUpdateをストリーム化できる UniRxにはさまざまなオペレーターが用意されているのですが、それらのオペレーターによりunity従来のUpdateをストリーム化しロジックを明確に処理することが可能になります。加えて、変数のスコープの明確化やロジック単位で分割し処理を行うことが可能になったり例外処理が統一されるといったメリットもあります。 良い点② 従来のイベントにはできなかったことが可能に 発生したイベントに関する値の変化を監視し対応することが容易(Observerパターン)になります。加えて、従来のイベントではdelegateの定義が必要とされていましたが、UniRxではそれが不要になります。もうひとつ、これをメリットと捉えるかは時と場合によるとは思うのですがUniRxは後発的な処理です。 良い点③ 時間処理が容易に 私自身特にUniRx便利だなと感じたのが時間に関する処理です。例えば、イベントの発火タイミングの処理や時間に伴い変化する値の監視や非同期処理です。非同期処理のおかげで実行スレッドを容易に変更したり、フレーム処理が本当に容易になります。 補足 UniRxではイベントの処理や時間処理の便利機能を総称として「Observable」と読んでいます。 注意事項(チームプロジェクト向け) UniRxを全員が理解していることを前提で話を進めますが、チームでUniRxを導入してプロジェクトを進める際、スパゲティプログラムには気をつけてください。もともとUniRx自体は可読性も高く、書き方が統一されるためテストが非常にしやすいといったメリットがあるのですが、UniRxの特徴でもあるStreamとSubscribeを走らせる場所にルールを設けないとチームプロジェクトでは著しく可読性が低下しテストもしにくくなることが予想できます。 05. まとめ 私自身UniRxを勉強し始めた時はC#のイベントの理解とObserverパターンの利点について理解が甘くUniRxを勉強する前に周りみちをしました。UniRxの機能自体は便利なものが多く、わざわざUniRxで書かなくても良いのでは?という処理もありますがそれ以上にイベントや時間周りの便利機能のことを考えると導入した方が良いのではないかなと思います。特にunityのプログラミングレベルを上げたいと考えている方にはunityとC#の理解が深まるので是非とも学習を始めてほしいです。 おすすめサイト UniRxチュートリアル: https://qiita.com/toRisouP/items/00b8a5bb8e7b68e0686c Observerパターン(日本語): https://blog.xin9le.net/entry/2011/12/10/153032 C#の機能について網羅しているサイト(イベント & 非同期処理 & シーケンス): https://ufcpp.net/study/csharp/ メッセージパッキング: http://itdoc.hitachi.co.jp/manuals/3000/30003D0820/GD080321.HTM#ID00763 Reactive Extensions: https://atmarkit.itmedia.co.jp/fdotnet/introrx/introrx_01/introrx_01_01.html Functional Reactive programmingとは?(英語QAですが内容的は具体的に書いてあるためおすすめです。): https://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming/1030631#1030631
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NotNullAttributeで設定したオブジェクトをHierarchy上で確認できる機能拡張の作成

「Applibot Advent Calendar 2021」2日目の記事になります。 前日は @Sigsiguma さんの 「Unity TextMeshProにおけるダイナミックフォントの扱い」という記事でした! はじめに Unity開発中にこういった経験をしたことがありますか? ・SerializeFieldで設定したオブジェクトが参照されておらずUnity再生中に進行不能になってしまう ・オブジェクトを消したことによって、知らぬ間に別のオブジェクトが影響を受け、Inspector上でMissing表示になっている こういったNull Reference Exceptionを見たくない人の悩みを減らすため オブジェクトが参照されているかどうかをHierarchy上で確認できるような 環境を作りたいと思います。 この記事で載せているスクリプトをコピペすることで誰でも使うことができますのでぜひ導入してください! 概要 NotNullで設定したものをInspectorとHierarchy上で確認できるEditor拡張を作成します。 作業環境 Unity2020.3.22f1 ※それ以前のバージョンでも使える場合はございます。 実装の流れ 1.NotNullの属性を作成 2.Inspector上で確認できる機能の作成 3.Hierarchy上で確認できる機能の作成 1.NotNullの属性を作成 以下のスクリプトを作成します。 NotNullAttribute.cs using System; using UnityEngine; [AttributeUsage(AttributeTargets.Field)] public class NotNullAttribute : PropertyAttribute { } これにより、以下のように属性を書くことができるようになります。 Test.cs using UnityEngine; public class Test : MonoBehaviour { [NotNull, SerializeField] private GameObject _gameObject; } 2.Inspector上で確認できる機能の作成 ここからはEditor拡張になりますので、Editorディレクトリを作成してそこに以下のスクリプトを追加してください。 NotNullAttributeDrawer.cs using UnityEditor; using UnityEngine; /// <summary> /// Inspector上でNotNullのAttributeつけたもの変数がnullの場合、注意文を表示する /// </summary> [CustomPropertyDrawer(typeof(NotNullAttribute))] public class NotNullAttributeDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { position.height = base.GetPropertyHeight(property, label); EditorGUI.PropertyField(position, property, label); position.y += position.height; if (IsNull(property)) { EditorGUI.HelpBox(position, "参照してるオブジェクトがありません", MessageType.Error); } } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return IsNull(property) ? base.GetPropertyHeight(property, label) * 2f : base.GetPropertyHeight(property, label); } /// <summary> /// 参照しているオブジェクトがnullかどうかを判定 /// </summary> private bool IsNull(SerializedProperty property) { if (property.isArray) { return property.arraySize == 0; } // オブジェクトを参照しないものは除く if (property.propertyType != SerializedPropertyType.ObjectReference) { return false; } return property.objectReferenceValue == null; } } 先程のTest.csをオブジェクトにAddComponentすると以下のように注意文言が表示されます。 注意文言があることで少し便利になりましたね! 3.Hierarchy上で確認できる機能の作成 これもEditor拡張になりますので、Editorディレクトリを作成してそこに以下のスクリプトを追加してください。 HierarchyExtension.cs using System.Reflection; using UnityEditor; using UnityEngine; public class HierarchyExtension : EditorWindow { [InitializeOnLoadMethod] private static void Initialize() { EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI; } // Hierarchy上表示する注意アイコンの大きさ private const int _ICON_SIZE = 16; private static void HierarchyWindowItemOnGUI(int instanceId, Rect selectionRect) { // GameObject が取得できない場合は SceneAsset GameObject obj = EditorUtility.InstanceIDToObject(instanceId) as GameObject; if (obj == null) { return; } // 所持しているコンポーネント一覧を取得 Component[] components = obj.GetComponents<Component>(); // コンポーネントが一つもなければ返す if (components.Length <= 0) { return; } // すべてのコンポーネントを見る foreach (Component component in components) { // オブジェクトにつけられるスクリプトでなければ返す if (component is MonoBehaviour == false) { continue; } MonoBehaviour monoBehaviour = (MonoBehaviour) component; // SerializeFieldで表示しているものをみる foreach (FieldInfo fieldInfo in monoBehaviour.GetType() .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { if (fieldInfo.GetCustomAttributes(typeof(NotNullAttribute), false).Length <= 0) { continue; } // 注意アイコンを表示 object field = fieldInfo.GetValue(monoBehaviour); if (field == null || field.Equals(null)) { selectionRect.width = _ICON_SIZE; GUI.DrawTexture(selectionRect, EditorGUIUtility.Load("console.erroricon") as Texture2D); break; } } } } } 先程のTest.csをAddComponentしたオブジェクトがHierarchy上で以下のように表示されます。 もちろんオブジェクトを参照すれば注意マークは消えるようになっています。 これでNull Reference Exceptionを未然に防げるようになりましたね! 備考 Hierarchy上の警告表示について、親オブジェクトで閉じちゃうと注意マークが見えなくなります。 親のオブジェクトに警告マークをつけるような実装を追加でやっていましたが、Editorが重くなり過ぎたので断念しました、、、。 おわりに 今回はEditor拡張の作成について紹介しました。 個人的にこの機能は色んな場面で役立った印象でしたので、是非皆さんも使ってみてください。 以上、 「Applibot Advent Calendar 2021」 2日目の記事でした! 参考ブログ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET6/C#10 で動的にJsonパースする標準クラス JsonNode

C#で動的にJsonパースしたい! C#でもクラスを準備することなくjsonを python や node.js のようにダックタイピングしたいことがあると思います。たとえば、 C#クラスを準備するのが面倒。ネストが多くてPOCOクラスだらけになる。 int なのに "NULL" や "-"が入ってくるWebAPIがいる。 APIが適当すぎてクラス定義できない・・・ .NET6/C#10 でライブラリに JsonNode クラスという動的にjsonを扱うクラスが追加されていたので、その使い方をベンチマーク交えて他の方法と比較します。 サンプルのjsonデータ HeartRails Express から 山手線の駅一覧 を使わせていただきました。このjsonから駅名一覧を抽出するサンプルを作っていきます。 jsonデータ例(2021年11月現在) { "response": { "station": [ { "name": "品川", "prefecture": "東京都", "line": "JR山手線", "x": 139.738999, "y": 35.62876, "postal": "1080075", "prev": null, "next": "大崎" }, { "name": "大崎", "prefecture": "東京都", "line": "JR山手線", "x": 139.728439, "y": 35.619772, "postal": "1410032", "prev": "品川", "next": "五反田" }, { "name": "五反田", "prefecture": "東京都", "line": "JR山手線", "x": 139.723822, "y": 35.625974, "postal": "1410022", "prev": "大崎", "next": "目黒" }, { "name": "目黒", "prefecture": "東京都", "line": "JR山手線", "x": 139.715775, "y": 35.633923, "postal": "1410021", "prev": "五反田", "next": "恵比寿" }, { "name": "恵比寿", "prefecture": "東京都", "line": "JR山手線", "x": 139.71007, "y": 35.646684, "postal": "1500013", "prev": "目黒", "next": "渋谷" }, { "name": "渋谷", "prefecture": "東京都", "line": "JR山手線", "x": 139.701238, "y": 35.658871, "postal": "1500002", "prev": "恵比寿", "next": "原宿" }, { "name": "原宿", "prefecture": "東京都", "line": "JR山手線", "x": 139.702592, "y": 35.670646, "postal": "1500001", "prev": "渋谷", "next": "代々木" }, { "name": "代々木", "prefecture": "東京都", "line": "JR山手線", "x": 139.702042, "y": 35.683061, "postal": "1510051", "prev": "原宿", "next": "新宿" }, { "name": "新宿", "prefecture": "東京都", "line": "JR山手線", "x": 139.700464, "y": 35.689729, "postal": "1600022", "prev": "代々木", "next": "新大久保" }, { "name": "新大久保", "prefecture": "東京都", "line": "JR山手線", "x": 139.700261, "y": 35.700875, "postal": "1690073", "prev": "新宿", "next": "高田馬場" }, { "name": "高田馬場", "prefecture": "東京都", "line": "JR山手線", "x": 139.703715, "y": 35.712677, "postal": "1690075", "prev": "新大久保", "next": "目白" }, { "name": "目白", "prefecture": "東京都", "line": "JR山手線", "x": 139.706228, "y": 35.720476, "postal": "1710031", "prev": "高田馬場", "next": "池袋" }, { "name": "池袋", "prefecture": "東京都", "line": "JR山手線", "x": 139.711085, "y": 35.730256, "postal": "1710021", "prev": "目白", "next": "大塚" }, { "name": "大塚", "prefecture": "東京都", "line": "JR山手線", "x": 139.728584, "y": 35.731412, "postal": "1700005", "prev": "池袋", "next": "巣鴨" }, { "name": "巣鴨", "prefecture": "東京都", "line": "JR山手線", "x": 139.739303, "y": 35.733445, "postal": "1700002", "prev": "大塚", "next": "駒込" }, { "name": "駒込", "prefecture": "東京都", "line": "JR山手線", "x": 139.748053, "y": 35.736825, "postal": "1700003", "prev": "巣鴨", "next": "田端" }, { "name": "田端", "prefecture": "東京都", "line": "JR山手線", "x": 139.761229, "y": 35.737781, "postal": "1140013", "prev": "駒込", "next": "西日暮里" }, { "name": "西日暮里", "prefecture": "東京都", "line": "JR山手線", "x": 139.766857, "y": 35.731954, "postal": "1160013", "prev": "田端", "next": "日暮里" }, { "name": "日暮里", "prefecture": "東京都", "line": "JR山手線", "x": 139.771287, "y": 35.727908, "postal": "1100001", "prev": "西日暮里", "next": "鶯谷" }, { "name": "鶯谷", "prefecture": "東京都", "line": "JR山手線", "x": 139.778015, "y": 35.721484, "postal": "1100003", "prev": "日暮里", "next": "上野" }, { "name": "上野", "prefecture": "東京都", "line": "JR山手線", "x": 139.777043, "y": 35.71379, "postal": "1100005", "prev": "鶯谷", "next": "御徒町" }, { "name": "御徒町", "prefecture": "東京都", "line": "JR山手線", "x": 139.774727, "y": 35.707282, "postal": "1100005", "prev": "上野", "next": "秋葉原" }, { "name": "秋葉原", "prefecture": "東京都", "line": "JR山手線", "x": 139.773288, "y": 35.698619, "postal": "1010028", "prev": "御徒町", "next": "神田" }, { "name": "神田", "prefecture": "東京都", "line": "JR山手線", "x": 139.770641, "y": 35.691173, "postal": "1010044", "prev": "秋葉原", "next": "東京" }, { "name": "東京", "prefecture": "東京都", "line": "JR山手線", "x": 139.766103, "y": 35.681391, "postal": "1000005", "prev": "神田", "next": "有楽町" }, { "name": "有楽町", "prefecture": "東京都", "line": "JR山手線", "x": 139.763806, "y": 35.675441, "postal": "1000006", "prev": "東京", "next": "新橋" }, { "name": "新橋", "prefecture": "東京都", "line": "JR山手線", "x": 139.758587, "y": 35.666195, "postal": "1050004", "prev": "有楽町", "next": "浜松町" }, { "name": "浜松町", "prefecture": "東京都", "line": "JR山手線", "x": 139.757135, "y": 35.655391, "postal": "1050022", "prev": "新橋", "next": "田町" }, { "name": "田町", "prefecture": "東京都", "line": "JR山手線", "x": 139.747575, "y": 35.645737, "postal": "1080023", "prev": "浜松町", "next": "高輪ゲートウェイ" }, { "name": "高輪ゲートウェイ", "prefecture": "東京都", "line": "JR山手線", "x": 139.740651, "y": 35.635476, "postal": "1080075", "prev": "田町", "next": null } ] } } 比較ライブラリ 以下のライブラリと比べていきたいと思います。Json.NETは外しました。 JsonNode (System.Text.Json.Node) System.Text.Json (通常の使い方) System.Text.Json 動的 DynaJson DynamicJson Utf8Json (dynamic) JmesPath.Net 利用例 JsonNode .NET6で標準ライブラリに入った JsonNode です。jsonを連想配列のように扱うことが可能です。例えば今回のjsonで"品川"を得るには node["response"]["station"][0]["name"] というコードになります。 駅一覧抽出コード例は以下の通り。foreachするために JsonArray にキャストしています。 using System.Text.Json.Nodes; public void SystemTextJsonNode() { var node = JsonNode.Parse(jsonString); var list = new List<string>(); foreach (var item in (JsonArray)node["response"]["station"]) { list.Add(item["name"].ToString()); } } System.Text.Json(静的) 標準ライブラリ。ベンチマーク基準のために入れておきます。 型を作ってデシリアライズし、駅名一覧を抽出。クラスを作るのが問題なければ順当な方法です。 using System.Text.Json; public void SystemTextJson() { var stationList = JsonSerializer.Deserialize<StationInfo>(jsonString); var list = stationList.response.station.Select(s => s.name); } クラス定義は json2charpで作りました。 public class Station { public string name { get; set; } public string prefecture { get; set; } public string line { get; set; } public double x { get; set; } public double y { get; set; } public string postal { get; set; } public string prev { get; set; } public string next { get; set; } } public class Response { public List<Station> station { get; set; } } public class StationInfo { public Response response { get; set; } } System.Text.Json(動的) System.Text.Jsonで動的に扱うには JsonElement を使ってプロパティにアクセスする形になります。クラス定義は不要になりましたが、プログラムの見通しが悪く、ぱっと見で理解しづらいかもしれません。ただしパフォーマンスは一番いいです(後述のベンチマーク参照)。 using System.Text.Json; public void SystemTextJsonDynamic() { var doc = JsonDocument.Parse(jsonString); var stationArray = doc.RootElement .GetProperty("response") .GetProperty("station") .EnumerateArray(); var list = new List<string>(); foreach (var station in stationArray) { var name = station.GetProperty("name"); list.Add(name.ToString()); } } DynaJson fujiedaさん作の高速な動的Jsonシリアライザ DynaJson ライブラリです。使い勝手は neue.ccさん作のJsonライブラリDynamicJson互換、、衝撃的なほど高速で低メモリー消費です。 public void Dynajson() { var dyna = DynaJson.JsonObject.Parse(jsonString); var list = new List<string>(); foreach (var station in dyna.response.station) { list.Add(station.name); } } DynamicJson neue.ccさん作のJsonライブラリ。サンプルコードはDynaJsonと同じなので省略します。 Utf8Json 最速とうたわれる neue.ccさんのライブラリ Utf8Json で動的に扱ってみました。 コードとしては dynamicへデコードしアクセスします。今回追加された JsonNode にそっくりなコードになりました。 public void Utf8JsonDynamic() { var res = Utf8Json.JsonSerializer.Deserialize<dynamic>(jsonString); var list = new List<string>(); foreach (var item in res["response"]["station"]) { list.Add(item["name"]); } } JmesPath.Net AWSやAzureを使っている方ならなじみ深いJsonクエリー言語 JMESPathも参考として使ってみました。駅名一覧はJMESPath記法で "response.station[].name" というクエリーになります。dotnet用のライブラリはこちら。 using DevLab.JmesPath; public void JmesPath() { var jmes = new JmesPath(); var result = jmes.Transform(jsonString, "response.station[].name"); // "[ "品川","大崎","五反田"... という「文字列」が返ってくる } JmesPath記法を知っていれば簡潔に記載できますが、戻り値は駅一覧のJson文字列です(C#のオブジェクトではない)。そのためベンチマークは参考までとします。 ベンチマーク結果 最速は 「System.Text.Jsonで動的に頑張る」方法ですがコードが面倒すぎます。 同等速度でコーディングが容易な DynaJson が優秀です。 標準ライブラリ縛りなら JsonNode もありでしょうか。 | Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Allocated | |-------------------------- |----------:|---------:|----------:|--------:|-------:|----------:| | Dynajson | 31.84 us | 0.613 us | 0.839 us | 9.5825 | 0.3052 | 39 KB | | System.Text.Json | 48.64 us | 0.957 us | 1.749 us | 2.3193 | 0.0610 | 10 KB | | (Dynamic)System.Text.Json | 31.82 us | 0.609 us | 0.792 us | 4.2725 | - | 18 KB | | JsonNode | 77.74 us | 1.504 us | 1.902 us | 11.5967 | 1.4648 | 48 KB | | (Dynamic)Utf8Json | 65.46 us | 1.302 us | 2.661 us | 12.2070 | 0.6104 | 50 KB | | DynamicJson | 189.08 us | 3.695 us | 6.070 us | 16.8457 | 2.1973 | 70 KB | | JmesPath.NET | 347.83 us | 6.893 us | 18.984 us | 65.4297 | 2.4414 | 268 KB |
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作液晶コントローラで動画再生(改善編)

はじめに こちらは鈴鹿高専 Advent Calendar 2021の5日目の記事です. 注意事項としては,ここで記載されているコードを他のプロダクトでそのまま使うのは禁止とします. 何かバグがあっても責任を取れないためです. また,実際に何かを制作する際には必ず一次情報(データシートなど)を参考にしてください. 作ったもの 3日目の記事で作ったものをテスト終わりのテンションで爆速で改善しました. ついでにPC上でシミュレーション動画を生成することもできるようにしました. 何がマズいのか 本来動画を再生させる用途の液晶でもなければそれに合わせたファームウェアでもありません. 何もチューニングしていない頭文字DのAE85状態です. 具体的には以下のような問題が生じました. チラつく 遅い 見にくい 今回はこれらの問題を全て解消していくまでの道のりについて書いていこうと思います. 画面のチラつき 動画で見るとそれほど感じないかもしれませんが,肉眼で見ると,ブラウン管テレビの走査線のような波が視認できます. 正直,遠目で見ればそれほど目立たないのですが,やっぱり気になったので修正することにします. なぜチラつくのか 原因は大きく2つ 変則シングルバッファリング 書き換えタイミングと表示タイミングの不一致 まず,前者についてはフレーム受信終了と同時に表示用バッファへmemcpyで転送していましたが,速度的な限界によりティアリングが発生していました. 画像が例です. 次に,書き換えタイミングと表示タイミングの不一致について. 表示に関してはタイマ+DMAによりほぼほぼ自動で書き込むようなコードを書いていたため今何ライン目なのかについて気にしていませんでした.そのため,上のmemcpyのタイミングと表示タイミングが被り,なんとも言えないチラつきを生んでいました. どうしたか とった対策は以下の2つです ダブルバッファリング化 垂直同期割り込みの追加 まず,memcpyで転送するのではなく2面のバッファを切り替えて使うようにしました. また,この切り替えタイミングも垂直同期割り込みに合わせることによりほぼほぼチラつきはなくなりました. これに合わせて描画割り込みの周期も動画のフレームレートに合わせるようにしました. 残像が残るという液晶そのものの問題はのこりますが,これはもう仕方ないでしょう. ハマった点 当初,memcpyが遅いならM2MのDMA転送をすればいいじゃないか!と思いましたが,実装してみるとなぜか動かない. 試しにDMAのチャネルを切り替えたらアッサリ動きました... CubeMXで自動的に割り当てられた際にはきちんとその組み合わせが動作するのか確認するようにしましょう. あと,どうやらDMA全転送終了割り込みは無いようなので使いにくそうですね...(半分終了時の割り込みはある) 遅い これは液晶ではなくPCのソフト側の問題です. とはいっても,階調表示編で作り直したCUI版ではかなり速度は改善されました. が,いよいよ開発機(Core-M 5Y31,4GB)のパソコンではしんどくなってきました. また,現行の転送方式ではフレームレートにも制限があります. 実効再生フレームレートは30fps弱で目標の30fpsには届いていません. これ以上速度を上げるには圧縮するしかありませんがいくつか障壁があります. 速度 容量 液晶の限界 無論伸長処理にもCPUパワーは必要です.4階調化,つまり2bppイメージ転送を行えば今より効率よく,また高フレームレートに対応することも可能ですがいよいよCPUに余裕がなくなってきたので難しいでしょう. また,伸長したデータを保管するためのRAMも必要です,単純計算で2KBx4面+受信データ8KBで16KB必要ですが,CCMRAM空間足して16KBしかRAMがないSTM32F303K8T6ではどう足掻いても無理でしょう. 最後に液晶の限界があります.3階調でも動きのある動画は厳しいのに4階調化すればもはや何も見えなくなるでしょう(笑).ロジックアナライザでの観測や,チューニングの結果4階調で30fpsを維持するのは不可能であると判断されたのも要因の一つです. よって,これ以上の速度改善はマイコン変えない限り不可能だと判断され妥協するしかありませんでした. 見にくい 肉眼だとそれほどでもないのですが,スマートフォンで撮影すると絶望的に見にくいです. グレア液晶のため反射しまくりで撮影難易度が鬼です.そのため反射防止フィルムを貼っています. また,3階調変換時のパラメタが適当すぎたというのも要因です. これを改善するために,パラメタを視覚的に確認できるソフトをでっちあげました. 以下に画像を示します. フォルダを選択するとパラメタを適用した状態のプレビューを表示できるというものです. より液晶っぽくするためにいろいろイジっていますが,これによりDynabookでは本当に動作不可なレベルまで重くなってしまいました... 連番画像の生成には拙作の"Sapphire CLI Commander(以下SCC)"を使用します. このデータをもとに,SCCで変換するというわけです. また,ここで制作したプレビュー機能をもとに,プレビュー動画の生成機能まで作ってしまいました. 以下のように,本物以上の見やすさで表示できる優れものです. 優れすぎているので封印しました. これにより,冒頭のような割と見やすいデータを生成できるようになりました. ハマった点 C#のBitmapはスレッドセーフではないので気をつけましょう. また,輝度だけでは真っ黒で何も見えなくなったりするので別の方法を考える必要があるかもしれない. 例外処理はきちんとしましょう.今回は自分しか使わないからいいやと思っていたら裏切られました,悲しい. 問題点 動画の変換処理が重すぎて大変. 画像のように100%に張り付いたまま数分間動けなくなってしまいます. おまけ タイトルだけは中央にだいたいあるので綺麗に映ります. CIRCUS系のスクロールを多用するタイプの動画であれば違和感なく再生できますが,ましろ色シンフォニーのように上下に情報があるタイプのものは見切れてしまってもはや何がなんだかわからなくなります. 4:1の変則アスペクト比なので再生できる動画を探すのもなかなか大変です. 感想 テスト終わりのテンションに任せて土曜の午前中に書き殴ったコードの割にはかなり良くできていると自分でも思います. とりあえず,次はマイコンを変えて実際に何か使えるものでも作ろうかなーと思っています.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む