Stable Diffusion環境構築メモ

PCスペック

このPCはノートです。単に動かしてみたいだけだから。

CPU:AMD Ryzen 7 4700U with Radeon Graphics 2.00 GHz

CPUは大して重要ではない。

RAM:8.00 GB (7.37 GB 使用可能)

ギリギリ。

グラフィックボード:

VRAMが4GB。超ギリギリ。まさに最小スペックといった感じ。

Pythonインストール

バージョン3.10.6をインストールする。

www.python.org

PATHに追加するか?のチェックが出なかったので、自力でC:\Users\ユーザー名\AppData\Local\Programs\Python\Python310を環境変数PATHに追加した。

ソースのダウンロード

コミットしないので、git cloneする必要はなし。Zipでダウンロードする。

この環境はCUDAではなくRadeonなのでForkされたこれを使う。

github.com

インストール

webui-user.batにPython.exeのパスを追加する。PATHに追加した意味あったかな?

webui-user.batを実行する。

こんな感じで進む。ネットワーク環境及びCPU及びストレージ書き込みの能力に依存した時間を消費する。

すると、ここでランタイムエラー。

結局git cloneはさせられるんかい!
git-scm.com

しゃーなしGitインストール。

もう一回webui-user.batを実行。

完了。

実行してみる

WebUIが表示できました。

ビックリするほどダメ!

Raspberry Pi Zero W セットアップ手順メモ

1.OS(Raspbian)のセットアップ

www.shumi-tech.online

ほぼここの通り。情報が少々古いので読み替えていく。

1.1.Raspbian Busterのダウンロード

www.raspberrypi.org

今回はストレージサイズが32GBと余裕があるため、「Raspbian Buster with desktop and recommended software」を選択する。
ZIPを設定用PCに保存。

1.2.SDカードのフォーマット

Windowsの標準(エクスプローラーでmicroSDカードを右クリック→フォーマット)でできた。

1.3.イメージファイルのマウント

1.1.でダウンロードしたZIPを解凍すると.imgファイルがある。
これを何でもいいのだが、ここでは情報通りbalenaEtcherでmicroSDカードにマウントした。
www.balena.io

microSDカードをRaspberry Piに挿入する。

1.4.外部入力機器の用意

ここでSSHなどを使えば専用にマウス・キーボード・モニターなしでセットアップが継続できなくはない気がするが、WindowsがPCそのものだと思っているGUIの申し子世代であるため、全て用意する。
と言っても、設定用PCで使ったマウス・キーボード・モニターを流用する。
miniHDMI→HDMIに変換するアダプタと、USB-microBをUSB2.0×2以上に分岐するアダプタを用意すればよい。
そしてつなぐ。

2.起動&初期設定

2.1.初回起動

起動する。
と言ってもスイッチはないため、PWR用USB-microBとUSB給電できる何らかの装置を接続すれば起動する。
Welcome to Raspberry Pi Desktop!と言われるのでThank you.と唱えてNext
Set CountryでJapan, Japanese, Tokyoを選択。設置場所が海外ならTimezoneはそこにする。
パスワードは標準ではオートログインになっている。変えたければ変える。もちろん変えた方がセキュリティ的に良い。
ここからはネットワークが必要な作業。

2.2.Wifi接続設定

Zero Wを買ったためWifiはモジュールなしで対応している(802.11 b/g/n)。
しかし元記事のようにすんなりとはWifiに繋がらなかったため、Check for updates以降はSkipで一旦ウィザードを終了させた。
ターミナルを開いて直接configを編集してしまおう。

>sudo nano /etc/wpa_supplicant/wpa_supplicant.conf

と入力してEnterするとWPA設定ファイルを開けるので、

network={
       ssid="*****"   (繋ぎたいWifiのSSID)
       psk="******"   (繋ぎたいWifiのpassword)
       key_mgmt=WPA-PSK 
       scan_ssid=1    
      }

として、上書き保存。
そしてメニューからRaspbianのリブートを行う。
すると、再起動後からWifiに接続できた。

2.3.VNC,SSH等の有効化

メニューから「設定」→「Raspberry Piの設定」を開く。
色々ならんでいるのでVNCSSHを有効にする。

2.4.IPアドレスの固定

IoTを実現するのにあたって、最も重要な設定の1つ。
これも直接configをいじって解決する。

>sudo nano /etc/dhcpcd.conf

としてdhcpcd.confを開き、

interface wlan0
static ip_address=[192.168.XXX.XXX]/24   (設定したい固定IPアドレス)
static routers=[192.168.XXX.XXX]      (デフォルトゲートウェイのIPアドレス)
static domain_name_servers=[192.168.XXX.XXX](DNSサーバーのIPアドレス、大抵デフォルトゲートウェイのIPアドレスと同じ)

として、上書き保存。
そしてメニューからRaspbianのリブートを行う。

3.まとめ

以上で「同一LAN上からアクセスできるシングルボードコンピュータ」の準備が完了した。
ではこれを何に使うか?
それはこれから考えれば良いことだ。

AngleSharpで上州屋の釣果情報をぶっこ抜く2

前回までのあらすじ

my-income-is-small.hatenablog.com
継続的アウトプット!継続的アウトプット!

進捗

  • 11件目以降のデータも取得する→できた
  • dataGridのサイズを動的に変える→まあできた
  • 魚の名前を抽出する→まだ
  • 縮小した釣果画像を出す→あえてやらない
  • リファクタリング(もうかよ)→少しやった

ソース

    public partial class Form1 : Form
    {
        private int pageNum = 1;
        private int numOfArticlesPerPage = 10;

        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_ClickAsync(object sender, EventArgs e)
        {
            var urlstring = urlTextBox.Text;
            await GetHTMLFromStream(urlstring);
        }

        /// <summary>
        /// 指定したサイトのHTMLをストリームで取得する
        /// </summary>
        /// <param name="urlstring">URL</param>
        /// <returns></returns>
        private async Task GetHTMLFromStream(string urlstring)
        {
            // データ表示の設定
            SetDataGridViewProperty();

            // 指定したサイトのHTMLをストリームで取得する
            var doc = default(IHtmlDocument);
            using (var client = new HttpClient())
            using (var stream = await client.GetStreamAsync(new Uri(urlstring)))
            {
                // AngleSharp.Html.Parser.HtmlParserオブジェクトにHTMLをパースさせる
                var parser = new HtmlParser();
                doc = await parser.ParseDocumentAsync(stream);
            }

            // HTMLから県を抽出する
            ExtractArea(doc);
            
            // HTMLから釣り場と釣り人を抽出する
            ExtractPointAndAngler(doc);

            // HTMLから店員コメントを抽出する
            ExtractComment(doc);
        }

        /// <summary>
        /// DataGridViewの設定
        /// </summary>
        private void SetDataGridViewProperty()
        {
            // DataGridViewの列数とヘッダーの設定
            chokaDataGridView.ColumnCount = 4;
            chokaDataGridView.Columns[0].HeaderText = "prefecture";
            chokaDataGridView.Columns[1].HeaderText = "point";
            chokaDataGridView.Columns[2].HeaderText = "angler";
            chokaDataGridView.Columns[3].HeaderText = "comment";

            // DataGridViewの列幅・改行の設定
            chokaDataGridView.Columns[3].DefaultCellStyle.WrapMode = DataGridViewTriState.True;
        }

        /// <summary>
        /// info__areaから県を抽出する
        /// </summary>
        /// <param name="doc">HTMLドキュメント</param>
        private void ExtractArea(IHtmlDocument doc)
        {
            var info_area = doc.GetElementsByClassName("info__area");
            int i = 0;
            foreach (var element in info_area)
            {
                string prefecture = string.Empty;
                foreach (var p in element.GetElementsByClassName("info__tag"))
                {
                    prefecture += p.TextContent.ToString();
                }
                chokaDataGridView.Rows.Add(prefecture, string.Empty, string.Empty, string.Empty);
                // DataGridViewのコメント列の幅を自動調整する
                i++;
            }
        }

        /// <summary>
        /// info_detailから釣り場と釣り人を抽出する
        /// </summary>
        /// <param name="doc">HTMLドキュメント</param>
        private void ExtractPointAndAngler(IHtmlDocument doc)
        {
            var info_details = doc.GetElementsByClassName("info__detail");
            int i = 0;
            foreach (var element in info_details)
            {
                string point = string.Empty;
                string angler = string.Empty;
                foreach (var children in element.GetElementsByTagName("tr"))
                {
                    string label = string.Empty;
                    foreach (var th in children.GetElementsByTagName("th"))
                    {
                        label += th.TextContent.ToString();
                    }
                    foreach (var td in children.GetElementsByTagName("td"))
                    {
                        if (label == "釣り場")
                            point += td.TextContent.ToString();
                        else if (label == "釣り人")
                            angler += td.TextContent.ToString();
                    }
                }
                chokaDataGridView.Rows[i + (numOfArticlesPerPage * pageNum - numOfArticlesPerPage)].Cells[1].Value = point;
                chokaDataGridView.Rows[i + (numOfArticlesPerPage * pageNum - numOfArticlesPerPage)].Cells[2].Value = angler;
                i++;
            }
        }

        /// <summary>
        /// info_commentを抽出する
        /// </summary>
        /// <param name="doc">HTMLドキュメント</param>
        private void ExtractComment(IHtmlDocument doc)
        {
            var info_comments = doc.GetElementsByClassName("info__comment");
            int i = 0;
            foreach (var element in info_comments)
            {
                string comments = string.Empty;
                foreach (var p in element.GetElementsByTagName("p"))
                {
                    comments += p.TextContent.ToString();
                }
                chokaDataGridView.Rows[i + (numOfArticlesPerPage * pageNum - numOfArticlesPerPage)].Cells[3].Value = comments;
                // DataGridViewのコメント列の幅を自動調整する
                chokaDataGridView.AutoResizeColumn(3, DataGridViewAutoSizeColumnMode.AllCells);
                chokaDataGridView.AutoResizeRow(i + (numOfArticlesPerPage * pageNum - numOfArticlesPerPage), DataGridViewAutoSizeRowMode.AllCells);
                i++;
            }
        }

        /// <summary>
        /// 次の10件を取得する
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void nextButton_Click(object sender, EventArgs e)
        {
            pageNum++;
            var urlstring = urlTextBox.Text + "/search.php?pref=&page=2";
            await GetHTMLFromStream(urlstring);
        }
    }

カラムのNo.をハードコーティングしたりしてて酷いけど次で直すから許して

動作確認

OKボタン押したとき

f:id:satoshi_cs12:20190816135558p:plain
だいぶ見れるようになってきた。凝りだすとキリがないし、文字情報だけにとどめておく意味もあるからUIはしばらくこれでいいかな。

Nextボタン押したとき

f:id:satoshi_cs12:20190816135836p:plain
次の10個をちゃんと表示してる

次バージョン以降

すぐやりたいこと

  • 魚の名前を抽出する
  • 県別に情報を抽出する(多分、店の所在地しかできないが)
  • リファクタリング(もうかよ)

システム的な伸びしろ

  • fimoからも引っ張る
  • 自動化してデイリー情報を自分のスマホにプッシュする
  • 年単位でのリピート釣行をしやすい上州屋店員の特性を利用して、統計的に次シーズンのターゲットの出来を予想し、釣行予算を的確に立てて無駄釣具・不足釣具の発生を防止する

AngleSharpで上州屋の釣果情報をぶっこ抜く

釣り情報は釣具店に集まるのが定番である。

釣具店は数多くあれど、釣り業界のIT化の遅れはスポーツ・レジャー界において屈指であり、店舗に赴くことなく情報を得られるのは上州屋・ポイント・キャスティングの3大釣具チェーンのみであると言ってよい。

3大釣具チェーンの中で最もコンテンツが自分にマッチしてるのは上州屋である。

ポイント

  • 情報が釣果持ち込みに偏っており、子供が釣ったとんでもない小型魚など、不要コンテンツが多い
  • どう釣ったかの情報が不足している
  • 関東の情報が少ない

キャスティング

  • 更新頻度が低い
  • 釣れていない

しかし、上州屋の釣果情報にも以下の問題がある。

  • 会社でも見たいので、いかにも釣り情報見てる、というような状態を作りたくない

  • 情報の絞り方が都道府県(しかも釣り場ではなく店舗の)→魚種という順序なので、例えば東陽町店の情報で東伊豆の良質コンテンツがあってもたどり着きにくい

よって、スクレイピングのモチベーションができた。自分の見たい情報を店舗によらず的確に収集したいし、その閲覧はあたかもデータの集約された表を眺めているようにしたい。

htmlを文字列解析して作れるかなと思ったけど、

  • 文字列の解析をするのって嫌だ(人間が読むときにいちいち解析してるわけじゃないのに)

  • 何かXMLって嫌だな

  • C#使いなのでLINQサポートOK

ということで、AngleSharpというものを使ってみることにした。

使い方

qiita.com
使い方も簡単でいいね。

超お試し版の作成

上州屋の釣果情報の「1釣果情報」あたりのhtmlは以下のようになっている。

<!--コンテンツ-->
<div class="info__body">
<div class="info__bodyin">

		<div class="info__head">
			<div class="info__date">
				<p class="info__date_txt"><span>‘19 </span>08月05日</p>
			</div>
			<div class="info__area">
				<span class="info__tag">愛知県</span>豊川店
				<span class="info__link"><a href="../shop/top.php?s=136">店舗情報</a></span>
				<span class="info__link"><a href="../shop/choka.php?s=136">店舗の釣り情報</a></span>
			</div>
			<p class="info__note">登録日:2019年08月06日<br>画像をクリックすると、拡大画像が表示されます</p>
		</div>
		
<div class="info__photo_wrap">
<div class="slider info__photo">
	<div class="slider-for" id="slider-for3307238">

		<div class="sp-slide"><a href="../choka_img/3307238_1.jpg" class="boxer" data-gallery="gallery" title=""><img src="../choka_img/3307238_1.jpg" alt=""></a></div>

		<div class="sp-slide"><a href="../choka_img/3307238_2.jpg" class="boxer" data-gallery="gallery" title=""><img src="../choka_img/3307238_2.jpg" alt=""></a></div>

		<div class="sp-slide"><a href="../choka_img/3307238_3.jpg" class="boxer" data-gallery="gallery" title=""><img src="../choka_img/3307238_3.jpg" alt=""></a></div>

	</div>     
	<div class="slider-nav-wrap">
		<div class="slider-nav" id="slider-nav3307238">

				<div class="sp-thumbnail"><span><img src="../choka_img/3307238_1.jpg" alt=""></span></div>

				<div class="sp-thumbnail"><span><img src="../choka_img/3307238_2.jpg" alt=""></span></div>

				<div class="sp-thumbnail"><span><img src="../choka_img/3307238_3.jpg" alt=""></span></div>

		</div>
		<div class="slick-nav-arrows">
				<div class="slick-next"></div>
				<div class="slick-prev"></div>
		</div>
</div>
</div>
</div>	
		

			
		<div class="info__box">
		<div class="info__boxin">
			<div class="info__boxin02">
				<div class="clearfix">
					<p class="info__sttl">豊川店(愛知県):2019年08月05日の釣果</p>
					<p class="info__weather" style="display:">晴れ</p>
				</div>
					<table class="info__table">
				
						<tbody>

						</tbody>
					</table>
					<div class="info__detail">
						<table style="display:">
							<tr>
								<th>釣り場</th>
								<td>豊川河口周辺</td>
							</tr>
						</table>
						<table style="float:right; display:">
							<tr>
								<th>釣り人</th>
								<td>スタッフ 林</td>
							</tr>
						</table>
					</div>
				</div>					
				<div class="info__comment">
					<p>豊川河口周辺のハゼの様子を見てきました。<br />
現場到着時は下げ潮。<br />
ルアータックルで少し遠投したところにハゼが固まっていました。<br />
のべ竿の方たちは苦戦気味でした。<br />
日によって群れが溜まる場所が変わります。<br />
リール竿も用意していきましょう。<br />
詳細は御来店の上スタッフにお問い合わせ下さい。</p>
					<p class="choka_item">

					</p>
				</div>			
		</div>
		</div>
		
	
</div>
</div>
<!--コンテンツ-->

このように、info_detailクラスの中に釣り人と釣り場、info_commentクラスの中に詳細が記述されているのがわかる。本当はinfo_detailの中にコメントも入れてほしい所だが、そうなってないのが初学者たる私にはちょっと厄介であった。パイロット版を作ったら早速ダサい実装になってしまった。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;

namespace Chokascraper
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_ClickAsync(object sender, EventArgs e)
        {
            var urlstring = textBox1.Text;
            dataGridView1.ColumnCount = 3;
            // 指定したサイトのHTMLをストリームで取得する
            var doc = default(IHtmlDocument);
            using (var client = new HttpClient())
            using (var stream = await client.GetStreamAsync(new Uri(urlstring)))
            {
                // AngleSharp.Html.Parser.HtmlParserオブジェクトにHTMLをパースさせる
                var parser = new HtmlParser();
                doc = await parser.ParseDocumentAsync(stream);
            }

            // HTMLからtitleタグの値(サイトのタイトルとして表示される部分)を取得する
            var title = doc.Title;

            var info_details = doc.GetElementsByClassName("info__detail");
            foreach (var element in info_details)
            {
                string point = string.Empty;
                string angler = string.Empty;
                foreach (var children in element.GetElementsByTagName("tr"))
                {
                    string label = string.Empty;
                    foreach (var th in children.GetElementsByTagName("th"))
                    {
                        label += th.TextContent.ToString();
                    }
                    foreach (var td in children.GetElementsByTagName("td"))
                    {
                        if (label == "釣り場")
                            point += td.TextContent.ToString();
                        else if (label == "釣り人")
                            angler += td.TextContent.ToString();
                    }
                }
                dataGridView1.Rows.Add(point, angler, "");
            }

            var info_comments = doc.GetElementsByClassName("info__comment");
            int i = 0;
            foreach (var element in info_comments)
            {
                string comments = string.Empty;
                foreach (var p in element.GetElementsByTagName("p"))
                {
                    comments += p.TextContent.ToString();
                }
                dataGridView1.Rows[i].Cells[2].Value = comments;
                i++;
            }
        }
    }
}

結果

f:id:satoshi_cs12:20190807171419p:plain
見た目はカスだが、とりあえず先頭10件の釣り人・釣り場・コメントの情報を得ることは出来ているみたい。

次バージョン以降

すぐやりたいこと

  • 11件目以降のデータも取得する
  • dataGridのサイズを動的に変える
  • 魚の名前を抽出する
  • 縮小した釣果画像を出す

システム的な伸びしろ

  • fimoからも引っ張る
  • 自動化してデイリー情報を自分のスマホにプッシュする
  • 年単位でのリピート釣行をしやすい上州屋店員の特性を利用して、統計的に次シーズンのターゲットの出来を予想し、釣行予算を的確に立てて無駄釣具・不足釣具の発生を防止する

FFMpegをキックして簡単な動画変換ソフトを作る2

今日の進捗

GUIが早くも完成

f:id:satoshi_cs12:20130523230236p:plain

足りなければコンボボックス増やせばOK

IntelliSenseが使えないことが発覚

何なの2010って?バカにしてんの?
俺らって思ったほどタイピングできないんだよ?

プログラム的進捗

各アイテムのText取得して,ffmpegのコマンド作ることに成功

f:id:satoshi_cs12:20130523231457p:plain

このエントリーの狙い

×日記をつけることで三日坊主にならない
○ゆきのんとガハマさんの可愛さを世にしらしめる

FFMpegをキックして簡単な動画変換ソフトを作る1

昨日のエントリーとも関係あるのですが,俺は動画変換をするときにWindowsムービーメーカーなんて使わず,ffmpegを使ってコマンドラインから色々オプションをイジって変換しています。しかしこの作業がだるい。
ffmpeg -i inputfile.flv -f mp4 -vcodec libx264 -acodec libfaac -vb 512k -ab 192k outputfile.mp4
とか手で打つのがムズい。途中で間違ってエンター押しちゃう。だからVisualStudioを使って,ffmpegを好みのオプションでキックするだけという簡単なプログラムを作ってしまおう

せっかくなので仕様をちゃんとさせておこうと思います

仕様

【機能要件】
扱うデータの種類:ビデオファイル一般
処理内容:ffmpegGUIから選択したオプションで起動し,入力データを変換
画面表示:ウインドウ1つ
操作の方法:ファイルを開いてコンボボックスからオプション選んでボタン押すだけ
出力の形式:ビデオファイル(コーデックがあるもの)

【非機能要件】
機能性:動けばいいよ
信頼性:PCが爆破しなければよし
使用性:俺が使えればよし
効率性:暇な時しか使わないので何でもよし
保守性:保守しないのでOK
移植性:ここでしか使わないのでOK
障害抑制性:障害発生したら消せばOK
効果性:投資してないし何でも
運用性:使うの俺だけだからどうでもいいい
技術要件:ウイルス作りに定評のあるVisual Studio

【入力】
■ソースファイル
任意の動画ファイルとする。フォーマットは問わない。(OpenFileダイアログ)
■オプション
アスペクト
 4:3と16:9からの選択とする。(コンボボックス)
・サイズ
 アスペクトが4:3の場合QVGAVGASVGAXGA・Quad-VGASXGA+から,
 16:9の場合はWQVGA+・HVGAW・WVGA・WSVGA・HD 720p・HDから選択する。(コンボボックス)
・フォーマット
 ビデオコーデックとオーディオコーデックを選択する。(コンボボックス)
ビットレート
 同じくビットレートを選択する。ビデオのフレームレートは固定でOK(コンボボックス)
・タイトル
 出力ファイル名を入力する。(テキストボックス)

【出力】
■アウトプットファイル
変換された動画ファイル

【その他】
面倒くさいからシングルスレッドのみ実装する。CPUがんばれ。コア数に勝手に最適化されるような気がしたから別にいいのかもしれない

はてなブログにきれいにソースコードを表示させる

はてなブログによるアウトプットの基本中の基本。

元々ソースコード記述に強いことからはてなダイアリーは技術者に愛されてきた。

はてなブログでも当然その流れは受け継いでおり、きれいに表示させることが容易にできる。

はてな記法を選択する

編集の▽より「はてな記法」を選択する。

f:id:satoshi_cs12:20190401222624p:plain

これでソースコードをきれいに表現することができる。実は見たまま記法でもプレビューをどうにかして行うことができるようだが、多分作業時間的にはてな記法のほうが早い。Markdown記法でも同じようにできるが、はてなに慣れた状態ならはてな記法が最もすんなり入れるだろう。

「 スーパーpre記法(シンタックス・ハイライト)」でソースコードを記述する。

はてな記法一覧 - はてなブログ ヘルプ

上記ヘルプにあるとおりに、「>|??| 」「||<」で囲むことにより、??(言語)で使用される構文規則をハイライト表示できる。例えば、
f:id:satoshi_cs12:20190401225145p:plain

上記のように記述すると(編集画面の時点でインデントが美しく再現されてとても良い)、

using System;
namespace Test
{
	class HelloWorld
	{
		static void Main() 
		{
			Console.WriteLine("Hello World!");
			// ウィンドウを出し続けるため
			Console.WriteLine("終了するには何かキーを押してください");
			Console.ReadKey();
		}
	}
}

このように、美しく表示される。これで継続的アウトプットの準備が完了したも同然。準備倒れにならないようにしないと。