20220110のC#に関する記事は11件です。

DictationRecognizerを使ってみる

この記事について Unityの標準で用意されているDictationRecognizerを利用してみます。 動作環境 Windows10(Windows10でしか動かないそうです) 注意:設定 > プライバシー > 音声認識がオンになっていないと動きません。 Unity 2021.3.24f1 やりたいこと 今回やりたいことはゲーム内で音声を受け取ってその結果を取得してゲームに反映させるということを使用と思います。 例:メニューと発言するとUIが表示される DictationRecognizer DictationRecognizerはフレーズを受け取った際にその内容を返す音声認識ライブラリです。 認識するキーワードを用意するわけではないので、歪みのある表記は苦手だと思います。(一と書いて「いち」と読ませるか「はじめ」と読ませるかなど) 帰ってくる値は「仮説(Hypotheses)」と「認識(Recognition)」の2種類の文字列があります。 HypothesesとRecognition Hypothesesは認識が甘めの文字列で、発言の途中であっても文字列を返します。 Recognitionは発言終了後に精度が高い文字列を返します。 個人的にはKeywordRecognizerとDictationRecognizerの中間的なライブラリが欲しい...... 使ってみた とりあえずドラグスレイブを打ちたくなるのは世代の性。 上のテキストがHypothesesです。 文章のとぎれとぎれで値が帰ってきているのがわかります。 下のテキストがDictationRecognizerです。 自分が読み終わった後に1秒程度間があって表示されました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#で全進数を相互に変換するプログラムを作成してみた

初めに ・専門学校に通う1年生がC#の勉強をするために作ったプログラムです。誤字、脱字や『もっとこうしたほうがいいよ!』などあればご指導いただけるとありがたいです。 ・下記のようにConvertクラスのToInt32メソッドとToStringメソッドを使えば一発で変換できますが、今回はあくまで勉強として作成しているので使用しておりません。 例えば 16進数のffffを10進数表すと65535に変換できます。 ToInt32メソッドを使用して、メッセージで表示するプログラムです。 test string n="ffff"; int num16 = Convert.ToInt32(n, 16); MessageBox.Show(num16.ToString()); 逆に10進数の65535を16進数のffffに変換したい場合、ToStringメソッドを使用してメッセージで表示します。 test int num = 65535; string str = Convert.ToString(num,16); MessageBox.Show(str); ・最後にTwitterの方も最近、作ったのでフォローお願いします! 作ったきっかけ きっかけは2つあります。 ・1つ目 担任の先生にアルゴリズムを理解したければ、プログラムを作り続け、どうなってるのかを理解するしかないと言われたため。 ・2つ目 C#を担当している先生が進数を変換するプログラムが欲しいと言っていたため。 (そんなようなこと言ってた気がする) 進数とは まず、進数とは、あらかじめ定められたN 種類の記号(数字)を列べることによって数を表す方法です。 Wikipedia参照 難しいので自分なりにまとめました。 まずN進数のNとはN種類の英数字を使用すると思ってください。 ・2進数の場合は0と1の2種類 ・8進数の場合は0~7の8種類 ・10進数の場合は0~9の10種類 ・16進数の場合は0~9の数字とAからFの英字で16種類 を使用します。 変換 私が作ったプログラムでは、主に2種類の変換方法を使用しています。 2種類とも、人間が得意とする10進数に関わりがあります。 ・1種類目 初めに2進数、8進数、16進数を10進数に変換する方法です。 ・2種類目 次に逆のパターンです。 10進数を2進数、8進数、16進数と、それぞれの進数に変換する方法です。 ☆この2つの変換方法を使えば、それぞれの進数がスタートでもゴールでも変換することができます。 例えば 2進数から8進数にしたい場合、実際には変換する方法はあります。 この画像のように3桁ごとに区切り、重みを位ごとに足していくことによって表現できますが、今回作ったプログラムでは、一度10進数を経由して表現しています。 ・2進数から10進数にした後に、10進数から8進数の順で変換しています。 では、実際のコードを紹介します。 実際のコード ・全体的なコード main using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace sinsuuhenkan { public partial class Form1 : Form { string deta = "";//入力される進数 string a = "0123456789abcdef";//16進数用の変換配列 //10進数に変換するメソッド public string henkan(string deta, int basu) { //変数型定義 int ans = 0; int i = 1, j, len; len = deta.Length;//lenに文字数を代入 //lenが0より大きい時 while (len > 0) { //10進数に変換 j = a.IndexOf(deta[len - 1]);//16進数用に文字変換 ans += j * i; i = i * basu; len = len - 1; } this.deta = ans.ToString();//int型をstring型に変換 return this.deta;//返却値 } //10進数から変換するメソッド public string reversehenkan(string deta, int basu) { //変数型定義 int redeta, j,len; string ans="",answer=""; redeta = int.Parse(deta);//detaをint型に変換 //redetaが0じゃない時 while (redeta != 0) { //10進数から変換 j = redeta % basu; ans += a.Substring(j,1); redeta = redeta / basu; } len = ans.Length;//lenに文字数を代入 //文字列を出力を逆にする //iの初期値をlen-1にiが0以上の時 for (int i = len-1; i >=0; i--) { answer += ans.Substring(i,1); } this.deta = answer.ToString();//int型をstring型に変換 return this.deta;//返却値 } } 説明 見にくいので細かく分解して説明していきましょう。 ・最初に From1 string deta = "";//入力される進数 string a = "0123456789abcdef";//16進数用の変換配列 が自分で作ったメソッドの外(Form1)で定義していることを、頭の片隅に置いといてください。 配列aは16進数の英字を数字に変えるために使います。 次にそれぞれの2種類のメソッドの説明をしていきます。 まずhenkanというメソッドについてです。 henkan //10進数に変換するメソッド public string henkan(string deta, int basu) { //変数型定義 int ans = 0; int i = 1, j, len; len = deta.Length;//lenに文字数を代入 //lenが0より大きい時 while (len > 0) { //10進数に変換 j = a.IndexOf(deta[len - 1]);//16進数用に文字変換 ans += j * i; i = i * basu; len = len - 1; } this.deta = ans.ToString();//int型をstring型に変換 return this.deta;//返却値 } これはそれぞれの進数を10進数に変換させるメソッドです。 例えばdetaに2進数の1011を持ってるとき、メソッドを使うときにhenkan(deta,2)というように引数を渡します。すると結果は10進数の11になります。 この時やりたい事はおおよそ4つです。 1.まず16進数があるならAを10、Bを11のように英字を数字にしたい。   配列aとIndexOfを使って要素数を取得しint型に変換した後、jに代入します。   (16進数じゃない時も同じ数字が返ってくるので使用可能です。)   ※IndexOfの個人的にわかりやすいと思うサイトをリンクさせました。 2.まず一の位から処理していきます。取得したjとiをかけて以前のansに足してansに代入するようにします。   ※ans+=〇とはans=ans+〇と同じ意味です。 3.この時のiとは第2引数で受け取った数字のべき乗です。   つまり2進数の時で、一の位なら2の0乗、十の位なら2の1乗です。 4.そしてlenから1引いて次の位に移ります。 解説 もう少し深く見ていきましょう、実際に値を入れていきます。 先生も実際にやってみた方が分かりやすいと言っていました。笑 先ほどの2進数の1011で検証してみましょう。 この時のメソッドは、henkan(1011,2);と書きます。 ・まずメソッドに入った時にlenは4になります。 ・条件が真なのでループに入ってjの代入処理をします。 ①len-1からint型に変換します。  -1をしている理由は配列の始まりが0からだからです。  ☆例:jには一の位の1が代入されます。 ②次にans+=j*iをします。  これはjに2進数のべき乗していった値をansに代入しています。  ☆例:ansには1×1で1が代入されます。 ③iはべき乗をかけるので次のi=i+baseの処理をしていきます。  besuには第2引数が与えられます。  ここの値はn進数を10進数に変えるときの、nを第2引数として書きます。  ☆例:iには1×2で2が代入されます。 ④最後に十の位に移りたいためlen=len-1をします。  ☆例:lenは4-1で3が代入されます。 ・条件が真のとき①に戻り、繰り返し処理していきます。  ☆例:      ・最後のansが11なので2進数の1011を10進数にすると11になるということがわかります。 次にreversehenkanのメソッドを見ていきます。 reversehenkan //10進数から変換するメソッド public string reversehenkan(string deta, int basu) { //変数型定義 int redeta, j,len; string ans="",answer=""; redeta = int.Parse(deta);//detaをint型に変換 //redetaが0じゃない時 while (redeta != 0) { //10進数から変換 j = redeta % basu; ans += a.Substring(j,1); redeta = redeta / basu; } len = ans.Length;//lenに文字数を代入 //文字列を出力を逆にする //iの初期値をlen-1にiが0以上の時 for (int i = len-1; i >=0; i--) { answer += ans.Substring(i,1); } this.deta = answer.ToString();//int型をstring型に変換 return this.deta;//返却値 } これは10進数を全進数にするためのメソッドです。 例えばdetaに10進数の11を持ってるとき、メソッドを使うときにreversehenkan(deta,2)というように引数を渡します。すると結果は2進数の1011になります。 説明していきます。 1.まずdetaをint型に変換します。   数値にしたredetaをbasu(変換先の進数)で割り、余りをjに代入します。 2.このままでは16進数にするとき、数字の10や11が英字になりません。   そこでaの配列とSubstringを使って、文字に変換しつつansに代入します。   ※Substringは簡単に言うと、配列の中身を取り出すメソッドです。    わかりやすいと思うサイトをリンクさせました。 3.最後にredetaをbasuで割りredetaに代入します。 4.このループだと出力したときに文字列が逆になります。   なので、次のループで配置を左右反転していきます。 解説 実際に値を入れて見てみましょう。 今回、10進数の11を2進数の1011に変換していきます。 メソッドはreversehenkan(11,2)と書きます。 ・11をint型に変換します。 ※見た目ではほとんど変わりないですが、パソコンでは文字の1と数字の1はASCIIコードといい全くの別物です。 ・まず上の画像のように処理したいというイメージを持っといてください。 ①j=redeta%basuとは、まず%で余りを取り出せます。  ☆例:つまり、上の画像の通り11÷2=5余り1で1をjに代入するということになります。 ②これだけでは16進数が来た時に数字を英字に変換できません。  なのでSubstringを使います。  ※今回16進数はないですが、ここでint型からstring型に変換しているので覚えといてください。 ③このままだとループしてもredetaがそのままです。  ですので最後にredetaをbasuで割ります。  ☆例:basuは第二引数のことなので11÷2=5余り1でredetaは5になります。 ・条件が真のとき①に戻り、繰り返し処理していきます。  ☆例:      ・redetaが0になった時点でループから抜けますが、今回はこれで終わりではありません。よく見てみると、出力したい値は1011ですが、ここのansの値は1101です。なので次のループで並び替えをする必要があります。 ④ansの後ろの値から順にanswerに格納して例のように並べ替えして、  ☆例:ans   1101 ↓     answer  1011  となります。  ・これで10進数の11が2進数の1011になりました。 #結論 ・これで全進数を相互変換できるプログラムができました。 ・デザインは、、、 こんな感じにしてみました。 後から気づいたんですが、ラジオボタンなどを使えばもっとかっこよく、効率よくできたのかなと思います。これも経験ですかね、笑 まとめと今後 ・今回はC#で全進数を相互に変換するプログラムを作ってみました。今は誰もが作れるようなプログラムを作ったりし、応用的なものは作れていませんが、今後はこれを応用したプログラムを作ろうと考えています。 ・例えば変換過程をtextで表示し、初心者にわかりやすいように改良したり、小数点も変換できるようにしようと考えています。 ・ここまで見ていただきありがとうございました。初めてQiitaを書き、文章を分かりやすく書くのは難しいなと思いました。4年間で上手く書けるようになれればなと思います。笑
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

整数の除算と剰余の種類

はじめに 整数同士の除算/剰余演算を行う場合、端数処理の方法によって解がことなる. ここではそれぞれの場合についてどのような結果となるのかをまとめる. なお、例として挙げるコードは C# で書く. 剰余の計算方法 <商> = <被除数> / <除数> とするとき、 <剰余> = <被除数> - (<商> * <除数>) で表せる. 例として、 <被除数> = 42 <除数> = 7 とすれば、 <商> = 42 / 7 = 6 <剰余> = 42 - (6 * 7) = 0 となる. 商が整数とならない場合における、剰余のバリエーションがこの記事の主眼である. 以下、特に注意がない場合 q := <商> r := <剰余> x := <被除数> y := <除数> であるものとする. まとめの表 最後まで読むのは面倒かと思われるため先に一覧表を提示する. 端数処理 剰余の範囲 剰余の符号 補足等 C# における関数 Google sheets における関数 正の無限大への丸め (-abs(y) .. abs(y)) 除数と異なる符号 普通使わない System.Math.Ceiling(double) CEILING 負の無限大への丸め (-abs(y) .. abs(y)) 除数と同じ符号 これを使いたい時があるかも System.Math.Floor(double) FLOOR 0への丸め (-abs(y) .. abs(y)) 被除数と同じ符号 C# における整数の除算はこれ System.Math.Truncate(double) TRUNCATE/ROUNDDOWN 0から離れる丸め (-abs(y) .. abs(y)) 被除数と異なる符号 普通使わない (実装なし?) ROUNDUP 四捨五入 (-abs(y)/2 .. abs(y)/2] x, y の符号によらない 絶対最小剰余 System.Math.Round(double, MidpointRounding.AwayFromZero) ROUND この記事では扱わないが、四捨五入のような最近接丸めにも、X.5 の扱いについてバリエーションが存在する. 詳細は英語版 Wikipedia の各 Round half ~ を参照されたし. C# における System.Math.Round(double) は銀行家の丸め(MidpointRounding.ToEven)を行う. C# の場合、標準では剰余が被除数と同じ符号となるが、 プログラム上では剰余が常に非負であることを期待する計算も少なくない. 加えて、被除数を正負の不明な引数として受け取り、定数で割ること場合が多い. 除数が正であると分かっている場合には、負の無限大への丸めを利用した剰余を使うと都合がよい. 被除数、除数ともに符号が不明な場合、次で最小非負剰余が得られる. (どうしても if 文を使いたくない場合) static int NonNegativeMod(int x, int y) { // FloorMod := 負の無限大への丸めによる剰余 // Abs := System.Math.Abs return (FloorMod(x, y) + Abs(y)) % Abs(y); } 各方法の詳細 正の無限大への丸め(Ceil/Rounding toward positive infinity) 一般に天井関数(Ceiling function)とよばれるものを使って端数を処理する. これは小数としての商をそれ以上で、最小の整数に丸める方法である. e.g. 5.7 -> 6, -5.7 -> 5 C# では System.Math.Ceiling、Google sheets では CEILING として提供されている. 整数の剰余演算に用いると、常に除数と異なる符号となる. あえて使うことはないだろう. この端数処理に従う除算/剰余演算プログラム例 static (int q, int r) CeilDivRem(int x, int y) { int q = x / y; // C# の標準では 0への丸め が行われるため、 // x, y の符号が同じで、かつ剰余が 0 でない場合は商をインクリメントする if((x ^ y) > 0 && (q * y != x)) { ++q; } // 得られた商で剰余を計算する int r = x - q * y; return (q, r); } 負の無限大への丸め(Floor/Rounding toward negative infinity) 一般に床関数(Floor function)とよばれるものを使って端数を処理する. これは小数としての商をそれ以下で、最大の整数に丸める方法である. e.g. 5.7 -> 5, -5.7 -> -6 C# では System.Math.Floor、Google sheets では FLOOR として提供されている. この端数処理に従う除算/剰余演算プログラム例 static (int q, int r) FloorDivRem(int x, int y) { int q = x / y; // C# の標準では 0への丸め が行われるため、 // x, y の符号が異なり、かつ剰余が 0 でない場合は商をインクリメントする if((x ^ y) < 0 && (q * y != x)) { --q; } // 得られた商で剰余を計算する int r = x - q * y; return (q, r); } 0への丸め(Truncate/Round down/Rounding toward zero) 日本語ではおそらく切り捨て関数と呼ぶことが多いと思われるものを使って端数を処理する. (英語では Truncate で共通認識が作られているように感じる) これは小数としての商を、最も0方向で最も近い整数に丸める方法である. e.g. 5.7 -> 5, -5.7 -> -5 C# では System.Math.Truncate、Google sheets では TRUNC/ROUNDDOWN として提供されている. この端数処理に従う除算/剰余演算プログラム例 static (int q, int r) TruncDivRem(int x, int y) { int q = x / y; // C# の標準では 0への丸め が行われるため、調整は不要 // 得られた商で剰余を計算する int r = x - q * y; return (q, r); // なお、自力で実装するまでもなく標準で実装されている: // int q = System.Math.DivRem(x, y, out int r); // return (q, r); // 当然、関数を使わずに演算子だけで計算してもよい(商と剰余両方が必要な場合は DivRem を使う方が速いかもしれないが) // int q = x / y; // int r = x % y; // return (q, r); } 0から離れる丸め(Round up/Rounding away from zero) 日本語では何と呼ぶべきか不明瞭だが(切り上げ関数?)、英語版 Wikipediaには Rounding away from zero とある方法. これは小数としての商を、最も0と反対方向で最も近い整数に丸める方法である. e.g. 5.7 -> 6, -5.7 -> -6 C# では System.Math 内に該当する処理がないものの、Google sheets では ROUNDUP として提供されている. この端数処理に従う除算/剰余演算プログラム例 static (int q, int r) RoundupDivRem(int x, int y) { int q = x / y; int r = x - q * y; // 割り切れたならばこれで計算終了 if(r == 0) return (q, 0); // 除数, 被除数の符号が異なればデクリメント、同じならばインクリメント if((x ^ y) < 0) --q; else ++q; // 剰余を計算しなおす r = x - q * y; return (q, r); // 剰余を計算しなおさず、次のように書いてもよい // if((x ^ y) < 0) return (q - 1, r + y); // else return (q + 1, r - y); } 四捨五入(Commercial rounding/Round half away from zero) 一般に四捨五入と呼ばれる方法. 英語でも単に Round といった場合はこれを指すものと思われる. C# では System.Math.Round(double, MidpointRounding.AwayFromZero)、Google sheets では ROUND として提供されている. e.g. 5.5 -> 6, 5.4 -> 5, -5.4 -> -5, -5.5 -> -6 この端数処理に従う除算/剰余演算プログラム例 static (int q, int r) RoundDivRem(int x, int y) { int q; // 符号が異なれば(=>小数としての商が負であれば)除数の半分を引いてから割る. さもなくば、足してから割る. if((x ^ y) < 0) q = ((x - y / 2) / y); else q = ((x + y / 2) / y); // 商さえ得られれば、剰余は定義通りに計算するだけ. int r = x - q * y; return (q, r); // 常に q = ((x - y / 2) / y) とすれば、Round half toward negative infinity // 常に q = ((x + y / 2) / y) とすれば、Round half toward positive infinity // if 文の条件を逆転させれば、Round half toward zero }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebView2ランタイム exists

WebView2ランタイムの存在確認 レジストリキーを確認するタイプは端末で動きが読めず。 バージョン見るタイプ(下)はインストール・アンインストールに反応。 Browser.cs class Browser:Form { double dpr; public Browser() { dpr = DeviceDpi/96.0; ClientSize = GetSize(640,360); //https://github.com/MicrosoftEdge/WebView2Feedback/issues/421 var error = ""; var version = ""; try{ version = CoreWebView2Environment.GetAvailableBrowserVersionString(); }catch(WebView2RuntimeNotFoundException e) { error = e.Message; } if(version=="") { NotFound(error); }else{ var view = new WebView2(){Size=ClientSize}; view.Source = new Uri("https://qiita.com/"); SizeChanged += (s,e)=>view.Size=ClientSize; Controls.Add(view); } } public void NotFound(string error) { var label = new Label() { Text = "", Location = GetPoint(20,80), Size = GetSize(600,80), }; var button = new Button() { Text = "WebView2ランタイム ダウンロードページ", Location = GetPoint(20,20), Size = GetSize(240,45), }; button.Click+=(s,e) => { OpenBrowser("https://developer.microsoft.com/microsoft-edge/webview2/"); }; label.Text += "上記ページから「WebView2ランタイム」をインストールしてください。\n"; label.Text += "WebView2 ランタイムをダウンロード > エバーグリーン ブートストラップ > 「ダウンロード」\n"; label.Text += error; Controls.Add(button); Controls.Add(label); } public Size GetSize(int w,int h) { return new Size((int)(w*dpr),(int)(h*dpr)); } public Point GetPoint(int x,int y) { return new Point((int)(x*dpr),(int)(y*dpr)); } public void OpenBrowser(string s) { Process.Start(new ProcessStartInfo() {FileName = s,UseShellExecute = true}); } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Zoomから自動的に退出する!

はじめに コロナ禍の大学の授業はZoomによるものが多く,自宅で受講しているとどうしてもお布団の引力に負けてしまいます。開始時に自動的に参加するのはURLと時刻をあらかじめ取得しておけば簡単に実装できますが,いい感じのタイミングで抜けるのはそうはいきません。そこで,それっぽいタイミングでZoomから自動的に退出してくれるアプリを作成しました。 これは「技術的には可能」であることを紹介する記事であって,オンライン授業で教授の呪文に負けて寝落ちしてしまっても教授と二人きりにならないようにするなどの悪用を想定したものではありません。 環境 Windows 10のAPIを多用しているのでWin10(>=10.0.10240.0)限定です。Win11は知りません。 諸事情により.NET Framework 4.7.2,C# 9で作成しました。 実装 Zoomの画面をキャプチャして,参加者数を読み取って減ってきた頃(最大時の人数×一定の割合以下まで減った時)に退出します。 準備 NuGetパッケージのMicrosoft.Windows.SDK.Contractsを追加します。 パッケージの管理形式がPackages.configになっている場合は予めPackageReferenceに変更しておきます。 PresentationCoreとWindowsBaseへの参照を追加します。 .NET 5であればプロジェクトファイルに<UseWPF>true</UseWPF>を追加します(詳細はこのあたりを参照)。 IntPtrと何かしらのデータをセットで持っておきたいことがあるのでクラスを用意しておきます。 using System; namespace Qiita { internal class MeetingData<T> { internal IntPtr HWnd { get; } internal T Data { get; } internal MeetingData(IntPtr hWnd, T window) { this.HWnd = hWnd; this.Data = window; } } } 画面キャプチャ using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Linq; using System.Runtime.InteropServices; using Point = System.Drawing.Point; namespace Qiita { internal static class CaptureUtil { #region Win32 [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; // x position of upper-left corner public int Top; // y position of upper-left corner public int Right; // x position of lower-right corner public int Bottom; // y position of lower-right corner } [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect); [DllImport("User32.dll")] internal extern static bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags); [DllImport("gdi32.dll")] internal static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2); [DllImport("user32.dll")] internal static extern int GetWindowRgn(IntPtr hWnd, IntPtr hRgn); #endregion Win32 /// <summary> /// アプリケーション名からウィンドウハンドルを取得 /// </summary> /// <param name="name">アプリケーション名</param> /// <returns>ウィンドウハンドルたち</returns> /// <remarks>2回目以降の呼び出し時にその前の画面キャプチャの結果から必要と思われるハンドルを優先的に返すなどの改善もできるが長くなるので省略</remarks> private static IEnumerable<IntPtr> GetHWnds(string name) => Process.GetProcessesByName(name).Select(proc => proc.MainWindowHandle); /// <summary> /// 指定したアプリケーション名に対応するウィンドウをキャプチャする /// </summary> /// <param name="name">アプリケーション名</param> /// <param name="scaling">画像の拡大倍率</param> /// <returns>ウィンドウのハンドルとそのキャプチャした画像</returns> /// <remarks>画面が小さいと文字が潰れてうまく読めないことがあるので適当な<paramref name="scaling"/>を指定して拡大する</remarks> internal static IEnumerable<MeetingData<Bitmap>> GetWindows(string name, float scaling = 1) { foreach (var hWnd in GetHWnds(name)) yield return GetWindow(hWnd, scaling); } /// <summary> /// 指定されたハンドルのウィンドウをキャプチャする /// </summary> /// <param name="hWnd">ウィンドウハンドル</param> /// <param name="scaling">画像の拡大倍率</param> /// <returns>ウィンドウのハンドルとそのキャプチャした画像</returns> internal static MeetingData<Bitmap> GetWindow(IntPtr hWnd, float scaling = 1) { // https://stackoverflow.com/questions/37931433/capture-screen-of-window-by-handle if (hWnd == IntPtr.Zero) return null; if (!GetWindowRect(new(null, hWnd), out var rect)) return null; var region = new Rectangle() { X = rect.Left, Y = rect.Top, Width = rect.Right - rect.Left, Height = rect.Bottom - rect.Top, }; if (region.Width * region.Height == 0) return null; var bitmap = new Bitmap(region.Width, region.Height, PixelFormat.Format32bppArgb); using var graphics = Graphics.FromImage(bitmap); IntPtr hdcBitmap; try { hdcBitmap = graphics.GetHdc(); } catch { return null; } var succeeded = PrintWindow(hWnd, hdcBitmap, 0); graphics.ReleaseHdc(hdcBitmap); if (!succeeded) { graphics.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bitmap.Size)); } var hRgn = CreateRectRgn(0, 0, 0, 0); var reg = Region.FromHrgn(hRgn); if (!reg.IsEmpty(graphics)) { graphics.ExcludeClip(region); graphics.Clear(Color.Transparent); } if (scaling == 1) return new(hWnd, bitmap); var w = (int)(region.Width * scaling); var h = (int)(region.Height * scaling); var scaled = new Bitmap(w, h); using var g = Graphics.FromImage(scaled); g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(bitmap, 0, 0, w, h); return new(hWnd, scaled); } } } OCR Windows.Media.Ocr.OcrEngineという大変便利なクラスがあるのですが,こいつがWindows.Media.Ocr.SoftwareBitmapという形式の画像しか受け取ってくれないのでSystem.Drawing.Bitmapから頑張って変換しています。OCRよりもこの変換の処理の方が長いですね… using System; using System.Drawing; using System.Drawing.Imaging; using System.Globalization; using System.IO; using System.Threading.Tasks; using System.Windows.Media.Imaging; using Windows.Globalization; using Windows.Graphics.Imaging; using SysBitmapFrame = System.Windows.Media.Imaging.BitmapFrame; using WinBitmapDecoder = Windows.Graphics.Imaging.BitmapDecoder; using WinOcrEngine = Windows.Media.Ocr.OcrEngine; namespace Qiita { public class OcrEngine { private readonly WinOcrEngine ocrEngine; public OcrEngine(Language language) { this.ocrEngine = WinOcrEngine.TryCreateFromLanguage(language); } public OcrEngine(string language) : this(new Language(language)) { } internal OcrEngine() : this(CultureInfo.CurrentUICulture.Name) { } /// <summary> /// 画像から文字列を読み取る /// </summary> /// <param name="bitmap">画像</param> /// <returns>読み取った文字列</returns> async public Task<string> GetString(Bitmap bitmap) { if (bitmap == null) return null; using var ms = new MemoryStream(); bitmap.Save(ms, ImageFormat.Bmp); ms.Seek(0, SeekOrigin.Begin); var source = SysBitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); var encoder = new PngBitmapEncoder(); encoder.Frames.Add(SysBitmapFrame.Create(source)); using var tmp = new MemoryStream(); encoder.Save(tmp); using var converted = tmp.AsRandomAccessStream(); var decoder = await WinBitmapDecoder.CreateAsync(converted); var image = await decoder.GetSoftwareBitmapAsync(); return await GetString(image); } /// <summary> /// 画像から文字列を読み取る /// </summary> /// <param name="bitmap">画像</param> /// <returns>読み取った文字列</returns> async public Task<string> GetString(SoftwareBitmap bitmap) { var res = await this.ocrEngine.RecognizeAsync(bitmap); return res.Text; } } } ミーティングの状態を管理する 参加者数を管理して,一定の場合に終了した旨のイベントを発火します。 using System; namespace Qiita { internal delegate void MeetingOverEventHandler(object sender, MeetingOverEventArgs e); internal sealed class MeetingOverEventArgs : EventArgs { internal IntPtr HWnd { get; } internal int Maximum { get; } internal int Current { get; } internal MeetingOverEventArgs(IntPtr hWnd, int maximum, int current) { this.HWnd = hWnd; this.Maximum = maximum; this.Current = current; } } internal class MeetingState { private const float THRESHOLD = 0.5f; private MeetingData<int> participants; private int maximum; internal event MeetingOverEventHandler MeetingSeemsToBeOver; /// <summary> /// ミーティングの参加者 /// </summary> internal MeetingData<int> Participants { get => this.participants; set { if (value == null) value = new(IntPtr.Zero, -1); if (this.participants == value) return; if (value.Data < 0) { Reset(); return; } this.participants = value; this.maximum = Math.Max(this.maximum, value.Data); if (this.participants.Data <= this.maximum * THRESHOLD) { MeetingSeemsToBeOver?.Invoke(this, new(value.HWnd, this.maximum, value.Data)); Reset(); } } } /// <summary> /// ミーティングの情報をリセットする /// </summary> internal void Reset() { this.participants = null; this.maximum = -1; } } } Zoomを監視する Zoomを監視して,ミーティングが終了したと判断された場合には退出します。 using System; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; using Timer = System.Timers.Timer; namespace Qiita { public static class ZoomObserver { [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetForegroundWindow(IntPtr hWnd); private const float SCALING = 2.236f; // 面積で5倍くらいのつもり private static readonly Regex re_ws = new(@"\s"); private static readonly Regex re_participants1 = new(@"(\d+)参加者退出"); private static readonly Regex re_participants2 = new(@"参加者[((](\d+)[))]"); private static readonly OcrEngine ocrEngine = new(); private static readonly MeetingState meetingState = new(); private static void Main() { var timer = new Timer() { Interval = 30_000, // 30,000 ms = 30 s AutoReset = true, Enabled = true, }; timer.Elapsed += (sender, e) => _ = CheckMeetingState(); Application.Run(new Form()); } static ZoomObserver() { meetingState.MeetingSeemsToBeOver += EscapeMeeting; } async internal static Task CheckMeetingState() => meetingState.Participants = await GetParticipants(); /// <summary> /// 画像から参加者数を読み取る /// </summary> /// <param name="bitmap">画像</param> /// <returns>読み取った参加者数。ログイン画面等の関係ないウィンドウであれば<c>0</c>,読み取りに失敗した場合は<c>-1</c></returns> async private static Task<int> GetParticipants(Bitmap bitmap) { if (bitmap == null) return -1; var text = await ocrEngine.GetString(bitmap); text = re_ws.Replace(text, string.Empty); if (text.Contains("Zoomクラウドミーティング")) return 0; var mc = re_participants1.Matches(text); if (mc.Count == 0) { mc = re_participants2.Matches(text); if (mc.Count == 0) return -1; } return int.Parse(mc[0].Groups[1].Value); } async private static Task<MeetingData<int>> GetParticipants() { var bmps = CaptureUtil.GetWindows("zoom", SCALING).Where(bmp => bmp != null); foreach (var bmp in bmps) { var p = await GetParticipants(bmp.Data); if (p > 0) return new(bmp.HWnd, p); else if (p == 0) continue; // [参加者]を表示させてから読めるかどうか確認 ToggleParticipants(bmp.HWnd); var tmp = CaptureUtil.GetWindow(bmp.HWnd, SCALING); p = await GetParticipants(tmp.Data); if (p < 0) continue; return new(bmp.HWnd, p); } return null; } /// <summary> /// 退出する /// </summary> private static void EscapeMeeting(object sender, MeetingOverEventArgs e) { SetForegroundWindow(e.HWnd); SendKeys.SendWait("%(q)~"); // Alt+Q, Enter } /// <summary> /// [参加者]を表示させる /// </summary> /// <param name="hWnd">ウィンドウハンドル</param> private static void ToggleParticipants(IntPtr hWnd) { SetForegroundWindow(hWnd); SendKeys.SendWait("%u"); // Alt+U } } } 今回はめんどくさかったのでフォームを表示しましたが,タスクトレイにアイコンだけ出しておいてバックグラウンドで処理させるようにするといい感じになると思います。 参考文献 Capture screen of Window by handle C#でWindows10のOCRを使って文字認識する(WPF)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityのKeywordRecognizerを使ってみる

この記事について Unityの標準で用意されているKeywordRecognizerを利用してみます。 環境 Windows10(Windows10でしか動かないそうです) Unity 2021.3.24f1 やりたいこと 今回やりたいことはゲーム内で音声を受け取ってその結果を取得してゲームに反映させるということを使用と思います。 例:メニューと発言するとUIが表示される KeywordRecognizer KeywordRecognizerはUnityが標準で用意している音声認識ライブラリです。 このKeywordRecognizerは登録されているキーワードの中から発言内容と最も近しい内容のものを出力するというものになっています。 簡単実装 まずは簡単に実装してみましょう。 SimpleKeywordRecognizer.cs public class SimpleKeywordRecognizer:MonoBehaviour { [SerializeField]private string[] keywords; private KeywordRecognizer _recognizer; void Awake() { _recognizer = new KeywordRecognizer(keywords); } private void OnEnable() { _recognizer.OnPhraseRecognized += OnPhraseRecognized; _recognizer.Start(); } private void OnDisable() { _recognizer.OnPhraseRecognized -= OnPhraseRecognized; _recognizer.Stop(); } private void OnPhraseRecognized(PhraseRecognizedEventArgs args) { Debug.Log(args.text + $", LEVEL:{args.confidence}"); } } 出力内容 音声の入力があったと判定されると「OnPhraseRecognized」が発火します。 引数として「PhraseRecognizedEventArgs」が指定されておりますが、こちらには認識された音声内容が入っています。 値 説明 text 認識された音声の文字列データ。(typeSystem.String) confidence 認識精度。High/Middle/Lowのいずれかの値が入ります。(typeUnityEngine.Windows.Speech.ConfidenceLevel) phraseDuration 音声の長さ。(typeSystem.TimeSpan) phraseStartTime 音声開始日時。(typeSystem.DateTime) semanticMeanings 認識されたフレーズの語義的な意味(よく分からない)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AngleSharpでスクレイピング【C#】

C#でスクレイピングしてみる スクレイピングならPython一択かと思っていたが、 C#が得意なのでC#でできないか調べたらAngleSharpというライブラリを発見 AngleSharpとは DOMをクエリセレクター風に解析できるライブラリっぽい jQueryを分かっていればほとんど躓くことなく使用できるので取っ付き易いライブラリだと思う。 リンクを抽出してみる sample.cs var domain = @"https://aaaaaaaaaaaaa/"; var parser = new AngleSharp.Html.Parser.HtmlParser(); var target = parser.ParseDocument(new WebClient().OpenRead(domain)); Console.WriteLine(target.QuerySelector("title").InnerHtml); // タイトル表示 var links = target.GetElementsByClassName("links"); //classにlinksが含まれるタグをすべて取得 foreach (var link in links) { var element = link.QuerySelector("a"); //aタグを取得 Console.WriteLine(element.GetAttribute("Href")); // aタグのhref の表示 Console.WriteLine(element.InnerHtml); // aタグの文章表示 } 上記の様なやり方で、タイトルとclass="links"下のすべてのaタグを取得できる。 他のライブラリを知らないが、めちゃくちゃ使いやすいなと思った。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windowsのクリップボードをちょっと便利に使えるようになるアプリを作ってみた

概要 以下の記事を参考に、クリップボードを使いやすくするアプリを作ってみました。 https://codezine.jp/article/detail/11229 見た目は以下の図のような感じです。 サンプルとの差分は以下の通りです。 選択しているアイテムをダブルクリックでコピーできます。 [フィルタ文字列]テキストボックスに文字列を入れるとクリップボード内の履歴から、テキストが含まれるものをフィルタできます。 利用方法 以下のURLから、Relase.zipリンクをクリックしてダウンロードしてください。 https://github.com/niimima/ExtendedClipboard/releases ExtendedClipboard.exeを実行すると起動します。 もし起動できない場合はRuntimeが不足している可能性があるため、以下のURLから.NET Runtimeのインストールしてみてください。 https://dotnet.microsoft.com/en-us/download/dotnet/3.1 また、このアプリの利用にはクリップボードの履歴がオンになっている必要があります。そちらもご確認ください。 まとめ 上記の記事を参考にしてみると、思いの外簡単にOSの機能を利用することができました(記事のソースコードをベースにすると2時間ほどで実現できました)。 このように視覚化できると想像以上に楽しめるので、他にも色々OSの仕組みを使ってみたくなりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで子要素をすべてListに入れる

public List<GameObject> _Objects; void Start() { //子要素がある時のみ if (GetChild(gameObject.transform)) {Debug.Log("取れたよ");} } bool GetChild(Transform _transform) { if (_transform.childCount <= 0){return false;} for (int i = 0; i < _transform.childCount; i++) { _Objects.Add(_transform.GetChild(i).gameObject); GetChild(_transform.GetChild(i)); } return true; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Azure】AppServiceプランのFunctionsのタイマートリガー関数が一定時間後に停止してしまう

Azure Functionsのタイマートリガー関数が停止する 要約 Azure Functionsにデプロイしたタイマートリガーの関数が1時間程度で停止してしまう AppServiceプランでホスティングされたFunctionsは一定時間HTTPリクエストが来ない場合アイドルタイムとなり稼働が停止する 解決策はFunctionsの全般設定から"常時接続"設定をオンにする 事象 Azure Functions にてタイマートリガーで毎分実行させる関数をデプロイした。 しばらく後、Application Insightsのログを確認したところ、"Job host stoped"を最後にパタリとログが出力されなくなっていた。 調査・原因 本記事の議題となっているAzure Functionsの挙動について参考になりそうなMicrosoftのドキュメントには以下のように記載されている。 App Service プランを実行する場合、関数アプリが正常に実行されるように、常時接続 設定を有効にする必要があります。 App Service プランでは、関数のランタイムは非アクティブな状態が数分続くとアイドル状態となるため、関数を "起こす" ことができるのは HTTP トリガーのみとなります。 [常時接続] 設定は、App Service プランでのみ使用できます。 従量課金プランでは、関数アプリはプラットフォームにより自動的にアクティブ化されます。 https://docs.microsoft.com/ja-jp/azure/azure-functions/dedicated-plan#always-on 私は上記記事を読んで「...非アクティブな状態?それってどんな状態?」となってしまった。 この非アクティブな状態について、Azureのサポートに一定期間でFunctionsが停止してしまう旨についても含め質問をした。 頂いた回答では、非アクティブな状態というのはFunctionsに対してHTTPリクエストが一定時間以上来ていない状態である。そのような非アクティブな状態が続くとアイドル状態となり、Functionsは動作を停止してしまう。そして、Functionsがこのようなアイドル状態にならないよう、定期的にHTTPリクエストを飛ばすように設定する、それが常時接続の有効化である。とのことだった。 今回の一定期間経ってFunctionsが動作しなくなってしまった原因はここにあったようだ。 結論として、AppServiceプランでホスティングされたFunctionsに一定期間HTTPリクエストが来ない場合、そのFunctinosは停止してしまう。今回のFucntionsはタイマートリガーの関数のみでHTTPリクエストが飛ばない設計となっていたため動作が停止してしまった。以上が原因であった。 解決策 解決策としては「調査・原因」でも述べたが、Functionsの全般設定から"常時接続"の項目をオンにすることで解決する。 ただし、AppServiceプラン(F1, D1)ではそもそも常時接続について設定することができないため、B1以上のプランにアップグレードし設定する必要がある。なお、F1,D1では既定として常時接続はオフとなっている。 まとめ 以上、AppServiceプラン(F1, D1)でホスティングされたAzure Functionsのタイマートリガーの関数が一定期間で動作が停止してしまう問題についての原因と解決策についてであった。 今回はタイマートリガーの関数でこの事象が生じたが、公式のドキュメントに「関数を "起こす" ことができるのは HTTP トリガーのみとなります。」という一文があることから、HTTPトリガーではない関数については全て同様の事象が生じると考えられる。 検証 以下、AppServiceプランをF1からB1にアップグレードし常時接続をオンにした場合のタイマートリガーの関数の出力ログである。ログが常に出力されていることが確認できる。 図中の赤枠のログは公式ドキュメントに「従量課金プランでは、関数アプリはプラットフォームにより自動的にアクティブ化されます。」とあることからプラットホームからのFunctionsを起こすため定期的に飛んでくるプラットフォームからのHTTPリクエストだろう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで自動運転の車をバックさせる

概要 Unityで車のゲームを作成しています。 Unity Standard Aseetsのカーオブジェクトで,自動運転ができる,CarWayPointBasedを使用しています。 しかし,下のgifファイルの赤い車のように,前から壁などにぶつかると,そのまま進もうとして,動けなってしまいました。 バック CarAIControl.csのFixedUpdate関数ないにある m_CarController.Move(steer, accel, accel, 0f); を m_CarController.Move(-steer, -accel, -accel, 0f); に変更することで,CarWayPointBasedのカーオブジェクトをバックさせることができました。 タグ付け コースのガードレールの役割として,Cubeオブジェクトを使用していますが,このタグをwallに変更します。 大まかな流れ 今回は,CarWayPointBasedのカーオブジェクトがぶつかったオブジェクトのタグの名前がwallだった場合,カーオブジェクトをバックさせる形とします。 プログラム CarAIControl.csのクラス上で,二つの変数を宣言します。。 wallタグに変更したCubeオブジェクトに,ぶつかった時に,trueに変更するwall_touch変数と, ぶつかったあと,何秒かバックするため,Stopwatchクラスからtime変数を宣言します。 bool wall_touch = false; Stopwatch time = new Stopwatch(); CarAIControl.cs内に既にあるOnCollisionStay関数に,ソースコードを付け足します。 ぶつかったオブジェクトの名前がwallだった場合,wall_touchの値をfalseからtrueに変更し, time変数の計測をスタートします。 if (col.gameObject.tag == "wall") { wall_touch = true; time.Start(); } そして,FixedUpdate関数で,wall_touchの値がtrueだった場合,バックをおこない,3秒たったら,wall_touchの値をfalseに戻し,time変数を初期化し,普通の自動運転に戻る(elseの部分が実行される)仕様にしました。 if (wall_touch) { m_CarController.Move(-steer, -accel, -accel, 0f); if (time.Elapsed > TimeSpan.FromSeconds(3)) { time.Stop(); wall_touch = false; time = new Stopwatch(); } //Debug.Log(time.Elapsed); //Debug.Log(wall_touch); } else { m_CarController.Move(steer, accel, accel, 0f); } テスト 実際に,自動運転している赤い車を壁にぶつけてました。 バックした後,普通に走りだしたので,やりたかったことはできたと思います。 参考にさせていただいたWebサイト 特定の時間ループを実行する方法 https://www.webdevqa.jp.net/ja/c%23/%E7%89%B9%E5%AE%9A%E3%81%AE%E6%99%82%E9%96%93%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/973216817/ Unity Documentation スクリプトリファレンス Collider.OnCollisionStay(Collision) https://docs.unity3d.com/ja/current/ScriptReference/Collider.OnCollisionStay.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む