未分類 アーカイブ - ネットサーフィンの壺 https://tech-tech.xyz/category/uncategorized/ Wed, 27 Sep 2023 13:46:07 +0000 ja hourly 1 https://wordpress.org/?v=6.5.3 【Python&OpenCV】マスク画像で画像の切り抜き・透過 https://tech-tech.xyz/262-2/ https://tech-tech.xyz/262-2/#respond Wed, 27 Sep 2023 13:46:07 +0000 https://tech-tech.xyz/?p=329 # OpenCVでマスク画像(黒い部分)で画像を切り抜く -------------------------- [![透過画像](http://www.tech-tech.xyz/wp-content/uploads/2 […]

投稿 【Python&OpenCV】マスク画像で画像の切り抜き・透過ネットサーフィンの壺 に最初に表示されました。

]]>
# OpenCVでマスク画像(黒い部分)で画像を切り抜く -------------------------- [![透過画像](http://www.tech-tech.xyz/wp-content/uploads/2018/03/8731245c.jpg)](http://www.tech-tech.xyz/wp-content/uploads/2018/03/8731245c.jpg "透過画像") 元の画像を**マスク画像の黒い領域**で**透過**させる方法を紹介します。 ### サンプルコード(Python2と3に対応) - img.png(透過させたい画像) - mask.png(マスク画像) ```python #cording:utf-8 import cv2 # 画像の読み込み img = cv2.imread("img.png") # マスクをグレースケールで読み込む(第二引数を0) mask = cv2.imread("mask.png", 0) # BGRにチャンネル分解 bgr = cv2.split(img) # 透明チャンネル(マスク)を追加 bgra = cv2.merge(bgr + [mask]) cv2.imwrite("new.png", bgra)

解説

#BGRにチャンネル分解
bgr = cv2.split(img)

変数bgr[b_channel, r_channel, g_channel]という 各カラーチャンネルを成分に持つリストです。 b_channelは2次元のリストで 各成分b_channel[x][y]は、img[x][y]の青の値です。

#透明チャンネル(マスク)を追加
bgra = cv2.merge(bgr + [mask])

bgra[b_channel, r_channel, g_channel, alpha_channel]です。 alpha_channelは画像を透過させるパラメータです。 alpha_channelにマスク画像の値が 入るのでマスク画像の黒い部分が透過されます。

関連記事

https://www.tech-tech.xyz/clip_alpha_image
“`

投稿 【Python&OpenCV】マスク画像で画像の切り抜き・透過ネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/262-2/feed/ 0
翻訳記事「Amazonアソシエイトで月100万円稼いでるけど質問ある?」 https://tech-tech.xyz/268-2/ https://tech-tech.xyz/268-2/#respond Wed, 27 Sep 2023 13:46:03 +0000 https://tech-tech.xyz/?p=303 海外版「○○だけどなんか質問ある?」 AMAという海外掲示板redditの名物企画がある。AMAはAsk Me Anything(何でも聞いて)という意味で2chで言うところの「○○だけどなんか質問ある?」である。 目次 […]

投稿 翻訳記事「Amazonアソシエイトで月100万円稼いでるけど質問ある?」ネットサーフィンの壺 に最初に表示されました。

]]>
海外版「○○だけどなんか質問ある?」

AMAという海外掲示板redditの名物企画がある。AMAはAsk Me Anything(何でも聞いて)という意味で2chで言うところの「○○だけどなんか質問ある?」である。

有名人が多数登場

過去には、ビル・ゲイツやバラク・オバマ、エドワード・スノーデンなど数々の有名人が登場して質問を受け付けたこともあるので知っている人も居るだろう。

money-515058_1280

Amazonのアソシエイトでひと月に1万ドル稼いだ人が登場

今回はこの企画にAmazonのアソシエイトプログラム(アフィリエイト)でひと月に1万ドル(約110万円)を稼いでいる人物が登場した。いくつかのQ&Aで自ら開拓してきたアフィリエイトサイトのノウハウを公開した。

本当に稼いでいるの?
質問に答えているのは、reddit上ではアカウント名「tjyedon」を使用している「TJ」と名乗る人物。「午後の仕事がオフになって暇になったから、質問に答える」と投稿。まずは本当にアフィリエイトで100ドル稼いでいることを証明するために、Imgurに下記の証拠画像を公開した。1ヶ月で1万556ドル40セント(約120万円)のアフィリエイト収入があったことを示している。

TJでどんな人?
TJの年齢は30代前半である。TJは副業としてサイトの作成を3年にわたって続けてきているとのこと。作成したサイトは専門性の高い商品を扱った情報サイトと述べている。また、サイトの詳細については述べておらず、詮索しないで欲しいとページの冒頭で述べている。

労働時間やコストは?
コストは月額600ドル~700ドルであり、労働時間は週5~10時間であり、純利益は毎月9200ドルから1万200ドルであると述べている。

実際にどんな記事を書くのか?
まず、大きな需要が見込まれて競合のいないカテゴリーを選び、また、アフィリエイト収入につながる専門性の高い商品の紹介記事を作成することを勧めている。

被リンクを増やす事が重要
サイトの被リンクを増やすことも重要だと述べており、スカイスクレイパー戦略や他のサイトへの寄稿などを挙げている。

プラグインを使ってる?
プラグインは、サイトを軽くするために少ししか使っていないと述べている。

アフィリエイトの商品はどのように決めている?
Amazonで最も評価の高い商品を選んでおり、商品レビューや売れ筋商品を参考にすると述べている。

広告は「クリック報酬型?」「売り上げ報酬型?」
報酬は実際に商品が売れたときに発生するため、広告は「売り上げ報酬型」となる。

1つ商品を売るたびにどれぐらい利益が入るの?
利益率は商品カテゴリーによって異なるが、現在は3%から10%と推定しており、商品価格の約6%の利益が得られると述べている。

Webエンジニアにとって有利なことはありますか?
Web開発の知識は関係なく、コンテンツの品質とSEO対策が重要であると述べている。

たとえば、電気シェーバーを売る場合どのような記事を書く?
競合記事よりも良いコンテンツを作成し、Men’s FitnessやGreatistなどの関連するサイトや雑誌にまとめた情報を提供すると述べている。

最初にある程度の収入を得るためにかかった期間は?更新頻度は?
Googleがサイトを信頼するまでには時間がかかるため、1000ドルの収入を得るまでに6〜8ヶ月かかったと述べている。また、サイトの鮮度を保つために毎月新しい記事を追加している。

サイトには「アフィリエイト広告」以外を掲載している?
現時点では掲載していないが、将来的には可能性があると述べている。

Googleは何を元にサイトを評価する?
Googleはコンテンツの品質とバックリンク数、サイトの年齢を評価基準としていると考えている。

実際に商品を買ってレビューを書いている?
実際に商品を購入してレビューを書いていない。他の人の意見を参考にし、商品を選んで情報をまとめていると述べている。

成功するために最も有効なテクニックは何ですか?
成功のためには忍耐強さと地道な努力が重要であり、スカイスクレイパー戦略やインフォグラフィックが役立つと述べている。また、記事の追加とサイトの拡大にも取り組んでいると述べている。

投稿 翻訳記事「Amazonアソシエイトで月100万円稼いでるけど質問ある?」ネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/268-2/feed/ 0
Pythonでテンプレートマッチング、OpenCVサンプルコードと解説 https://tech-tech.xyz/275-2/ https://tech-tech.xyz/275-2/#respond Wed, 27 Sep 2023 13:45:59 +0000 https://tech-tech.xyz/?p=305 目次 準備OpenCVで物体認識する方法テンプレートマッチングとは?テンプレート画像と一致する部分を検出メリットとデメリットOpenCVで使用する関数類似度を表すグレースケール画像を出力minMaxLoc関数を使って、最 […]

投稿 Pythonでテンプレートマッチング、OpenCVサンプルコードと解説ネットサーフィンの壺 に最初に表示されました。

]]>

準備

Pythonでテンプレートマッチングする場合、画像処理ライブラリOpenCVが必要です。入れてない人は次の記事を参考にしてください。

https://www.tech-tech.xyz/python-opencv-pip-install/

OpenCVで物体認識する方法

OpenCVで静止画から物体認識をする場合、つぎの3つの方法があります。

  1. テンプレートマッチング法 今回は、テンプレートマッチング法で物体認識を行います。

  2. 特徴点の抽出し、比較することで検出
    https://www.tech-tech.xyz/sift-surf-akaze/

  3. カスケード型分類器を用いて検出
    https://www.tech-tech.xyz/haar-cascade/

これらは、お手軽順で並べてます。つまり、1.が最もお手軽な方法で、3.は判別機の学習のためにデータを集めて成形するのが面倒です。ただし、3.の方法を使った顔認識は優秀な判別機が公式から配布されているのでお手軽にできます。今回紹介するテンプレートマッチング法は最も単純な方法です。

テンプレートマッチングとは?

テンプレート画像と一致する部分を検出

テンプレートマッチングの説明図

画像からテンプレート画像と一致する部分を検出するのがテンプレートマッチングです。テンプレートマッチングは、テンプレート画像を被検出画像上でスライドさせ、テンプレート画像と被検出画像の領域を比較し、類似度の高い領域を検出します。

メリットとデメリット

他の画像認識アルゴリズムと比べるとテンプレートマッチングは次のようなメリットとデメリットがあります。

  • メリット:お手軽
  • デメリット:柔軟性がない(拡大や縮小程度の違いでも認識できなくなる。)

OpenCVで使用する関数

類似度を表すグレースケール画像を出力

result = cv2.matchTemplate(image, template, method)

OpenCvではcv2.matchTemplate関数でテンプレートマッチングを行います。入力画像、テンプレート画像、比較方法を入力します。出力のresultは各画素が入力画像とテンプレート画像の類似度を表すグレースケール画像になります。

minMaxLoc関数を使って、最も明るい領域と暗い領域を検出

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(gray_image)

cv2.minMaxLoc関数は入力画像(グレースケール)から最も明るい画素と暗い画素の位置を出力します。出力のmin_val, max_valは画素の値、min_loc, max_locは画素の位置です。cv2.matchTemplateの結果を入力することで最もマッチする領域を検出するのに用います。

テンプレートマッチングでダイヤの4を探す

準備

トランプの画像からダイヤの4をテンプレートマッチングで検出します。テンプレート画像は被検出画像から切り取った画像です。

  • 被検出画像(“img.png”で保存)
    被検出画像

  • テンプレート画像(“temp.png”で保存)
    テンプレート画像

サンプルコード

#coding:utf-8
import cv2

#画像をグレースケールで読み込む
img = cv2.imread("img.png", 0)
temp = cv2.imread("temp.png", 0)

#マッチングテンプレートを実行
#比較方法はcv2.TM\_CCOEFF\_NORMEDを選択
result = cv2.matchTemplate(img, temp, cv2.TM\_CCOEFF\_NORMED)

#検出結果から検出領域の位置を取得
min\_val, max\_val, min\_loc, max\_loc = cv2.minMaxLoc(result)
top\_left = max\_loc
w, h = temp.shape\[:: -1\]
bottom\_right = (top\_left\[0\] + w, top_left\[1\] + h)

#検出領域を四角で囲んで保存
result = cv2.imread("img.png")
cv2.rectangle(result,top\_left, bottom\_right, (255, 0, 0), 2)
cv2.imwrite("result.png", result)

サンプルコードの解説

#画像をグレースケールで読み込む
img = cv2.imread("img.png", 0)
temp = cv2.imread("temp.png", 0)

入力画像とテンプレート画像はグレースケールで読み込んでます。色情報を使う場合は特定の色を抽出してグレースケールへ変換する場合が多いようです。

# テンプレートマッチングを実行
# 比較方法はcv2.TM\_CCOEFF\_NORMEDを選択
result = cv2.matchTemplate(img, temp, cv2.TM\_CCOEFF\_NORMED)

比較方法はcv2.TM_CCOEFF_NORMEDにしました。私の実感ではこの比較方法が最も精度が良いです。

#検出結果から検出領域の位置を取得
min\_val, max\_val, min\_loc, max\_loc = cv2.minMaxLoc(result)

cv2.minMaxLocは入力としてグレースケール画像をとります。そして、最も明るい位置(max_loc)とその色の値(max_val)、最も暗い位置(min_loc)とその色の値(min_val)を返します。resultにはcv2.matchTemplateの結果が入ってます。この結果画像(result)は類似度が高いほど明るくなるので最も明るいところ(max_loc)が類似度の高い領域の左上の位置を指しています。ただし、比較方法がcv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMEDの場合は類似度が高いほど黒くなるのでcv2.minMaxLocでテンプレートマッチングする場合はmin_locが類似度の高い領域になります。

結果

ダイヤの4が無事に囲まれました。

テンプレートマッチング法による物体検出の結果

複数の物体を検出する

次はテンプレートマッチングでダイヤマークをすべて検出したいと思います。ダイヤマークを用意します。(mark.pngで保存)

テンプレート画像

サンプルコード

#coding:utf-8
import cv2
import numpy as np

#画像をグレースケールで読み込む
img = cv2.imread("img.png", 0)
temp = cv2.imread("mark.png", 0)

#マッチングテンプレートを実行
result = cv2.matchTemplate(img, temp, cv2.TM\_CCOEFF\_NORMED)

#類似度の設定(0~1)
threshold = 0.9

#検出結果から検出領域の位置を取得
loc = np.where(result >= threshold)

#検出領域を四角で囲んで保存
result = cv2.imread("img.png")
w, h = temp.shape\[:: -1\]
for top_left in zip(*loc\[:: -1\]):
    bottom\_right = (top\_left\[0\] + w, top_left\[1\] + h)
    cv2.rectangle(result,top\_left, bottom\_right, (255, 0, 0), 2)
cv2.imwrite("result2.png", result)

サンプルコードの解説

#類似度の設定(0~1)
threshold = 0.9

#検出結果から検出領域の位置を取得
loc = np.where(result >= threshold)

複数の物体を検出する場合は閾値が必要になります。今回は類似度を0.9に設定します。

結果

ほとんどのダイヤマークが検出できました。検出に失敗した箇所は四角い領域内に余計な物体が入ったからだと考えられれます。ダイヤのQの上部を拡大すると次のようになっています。

fail_case

まとめ

テンプレートマッチングは単純な物体検出方法なので向きが変わったり、四角い領域に余計な物が入り込むと検出が失敗してしまうことがわかりました。もう少し高度なマッチング技術をためしたい場合、下記の記事が参考になると思います。

投稿 Pythonでテンプレートマッチング、OpenCVサンプルコードと解説ネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/275-2/feed/ 0
OpenCV(Python)GUIの基礎、画像の読み込みと表示 https://tech-tech.xyz/279-2/ https://tech-tech.xyz/279-2/#respond Wed, 27 Sep 2023 13:45:55 +0000 https://tech-tech.xyz/?p=306 関数の説明 目次 cv2.imread読み込みのオプション実際に使ってみる準備ソースコード解説まとめ cv2.imread cv2.imread(filename[, flag]) OpenCVでの画像の読み込みはcv2 […]

投稿 OpenCV(Python)GUIの基礎、画像の読み込みと表示ネットサーフィンの壺 に最初に表示されました。

]]>
関数の説明

cv2.imread

cv2.imread(filename[, flag])

OpenCVでの画像の読み込みはcv2.imreadで行います。filenameは読み込むファイルのパスです。flagは画像を読み込むときのオプションです。

読み込みのオプション

  • cv2.IMREAD_COLORは画像をカラー画像として読み込みます。読み込まれるカラー画像は一般的なRGB(赤青緑)ではなくBGR(青緑赤)です。
  • ccv2.IMREAD_GRAYSCALEは画像をグレースケール画像として読み込みます。
  • cv2.IMREAD_UNCHANGEDは画像をそのまま読み込みます。透過部分のある画像の場合はアルファチャンネルも読み込みます。

実際に使ってみる

準備

img.jpgというファイル名で画像を用意してください。今回は下記の画像を使用して解説します。また、保存した画像と同じディレクトリでスクリプトを実行していきます。

img

ソースコード

以下のスクリプトが画像を表示するスクリプトです。

# -*- coding: utf-8 -*-
import cv2
#画像を読み込む
img = cv2.imread("img.jpg")
#ウィンドウの名前を設定
cv2.namedWindow('gui', cv2.WINDOW_NORMAL)
#ウィンドウの名前を選択して画像を表示
cv2.imshow('gui', img)
#入力待ち
cv2.waitKey(0)

実行すると次のような画面が出てきます。キーボードから何か入力すると終わります。

gui1

解説

上記のスクリプトの細かい部分を解説していきます。

#ウィンドウの名前を設定
cv2.namedWindow('gui', cv2.WINDOW_NORMAL)

cv2.namedWindowで画像を表示するウィンドウを準備します。第1引数がウィンドウの名前で、第2引数がウィンドウの設定になっています。cv2.WINDOW_NORMALはウィンドウサイズをマウスで変更できるようにする設定です。

#ウィンドウの名前を選択して画像を表示
cv2.imshow('gui', img)

cv2.imshowの第1引数は画像を表示するウィンドウ名を指定します。今回は”gui”というウィンドウを作ったのでそれを指定します。第2引数には表示する画像を渡します。

cv2.waitKey(0)

cv2.waitKeyはキー入力を待つ関数です。引数には待ち時間を入力します。0を引数に渡したときはキー入力があるまで待ちます。それ以外のときは与えた値×0.001秒の間、キー入力があるまで待ちます。

まとめ

今回は、OpenCVの基礎的な内容で終わりました。次回は簡単なペイントツールを作成してGUIの機能をもっと掘り下げていきたいと思います。

投稿 OpenCV(Python)GUIの基礎、画像の読み込みと表示ネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/279-2/feed/ 0
OpenCV(Python)GUI基礎編、ペイントツールを作ってみた https://tech-tech.xyz/286-2/ https://tech-tech.xyz/286-2/#respond Wed, 27 Sep 2023 13:45:51 +0000 https://tech-tech.xyz/?p=315 簡単なペイントツールを作ってみた。 OpenCVのGUI基礎編、これが3回目です。1回は画像の表示、2回目は線の描画について解説しました。今回は簡単なペイントツールを作ってトラックバーについて解説します。 ペイントツール […]

投稿 OpenCV(Python)GUI基礎編、ペイントツールを作ってみたネットサーフィンの壺 に最初に表示されました。

]]>
簡単なペイントツールを作ってみた。

OpenCVのGUI基礎編、これが3回目です。1回は画像の表示、2回目は線の描画について解説しました。今回は簡単なペイントツールを作ってトラックバーについて解説します。

ペイントツール

# -*- coding: utf-8 -*-
import cv2
#sx, syは線の始まりの位置
sx, sy = 0, 0
#ペンの色
color = (0, 0, 0)
#ペンの太さ
thickness = 1
#マウスの操作があるとき呼ばれる関数
def callback(event, x, y, flags, param):
    global img, sx, sy, color, thickness
    #マウスの左ボタンがクリックされたとき
    if event == cv2.EVENT_LBUTTONDOWN:
        sx, sy = x, y
    #マウスの左ボタンがクリックされていて、マウスが動いたとき
    if flags == cv2.EVENT_FLAG_LBUTTON and event == cv2.EVENT_MOUSEMOVE:
        cv2.line(img, (sx, sy), (x, y), color, thickness)
        sx, sy = x, y
# トラックバーを変更したらペンを変更
def changePencil(pos):
    global color, thickness
    r = cv2.getTrackbarPos("R", "img")
    g = cv2.getTrackbarPos("G", "img")
    b = cv2.getTrackbarPos("B", "img")
    color = (b, g, r)
    thickness = cv2.getTrackbarPos("T", "img")
#画像を読み込む
img = cv2.imread("img.jpg")
#ウィンドウの名前を設定
cv2.namedWindow("img", cv2.WINDOW_NORMAL)
#マウス操作のコールバック関数の設定
cv2.setMouseCallback("img", callback)
#トラックバーのコールバック関数の設定
cv2.createTrackbar("R", "img", 0, 255, changePencil)
cv2.createTrackbar("G", "img", 0, 255, changePencil)
cv2.createTrackbar("B", "img", 0, 255, changePencil)
cv2.createTrackbar("T", "img", 1, 100, changePencil)
while(1):
    cv2.imshow("img", img)
    k = cv2.waitKey(1)
    #Escキーを押すと終了
    if k == 27:
        break
    #sを押すと画像を保存
    if k == ord("s"):
        cv2.imwrite("painted.png", img)
        break

このコードを実行すると次のような画面が現れます。画面を左クリックでなぞると線が描けます。 paint_tool1 このウィンドには4つのトラックバーが付いてます。RGBの3つのバーでペンの色を設定します。Tのトラックバーで線の太さを調整します。

トラックバーの解説

#トラックバーのコールバック関数の設定
cv2.createTrackbar("R", "img", 0, 255, changePencil)
cv2.createTrackbar("G", "img", 0, 255, changePencil)
cv2.createTrackbar("B", "img", 0, 255, changePencil)
cv2.createTrackbar("T", "img", 1, 100, changePencil)

cv2.createTrackbar(name, windowName, startPos, max, callback)でトラックバーを追加します。nameはトラックバーの名前です。windowNameはトラックを表示するウィンドウの名前です。 startPosはトラックバーが最初に表示されたときの初期値です。maxはトラックバーの目盛の最大値です。最小値は0なのは変えられません。callbackはトラックバーを変えたときに呼ばれる関数を設定します。

トラックバーのコールバック関数

# トラックバーを変更したらペンを変更
def changePencil(pos):
    global color, thickness
    r = cv2.getTrackbarPos("R", "img")
    g = cv2.getTrackbarPos("G", "img")
    b = cv2.getTrackbarPos("B", "img")
    color = (b, g, r)
    thickness = cv2.getTrackbarPos("T", "img")

トラックバーのコールバック関数はトラックバーの位置を引数として受け取ります。つまり、posには変更されたトラックバーの位置が入ってます。 けれど今回はトラックバーの位置をcv2.getTrackbarPosでトラックバーの位置を取得してます。これはペンの色を決めるには変更されたトラックバーの位置だけではなく他のトラックバーの位置も取得しなけらばいけないからです。 トラックバーが変更されたらグローバル変数のcolor,thicknessに値を入れます。colorはペンの色、thicknessはペンの太さを表しています。

カラーパレットの分離

pallete_bunri このままだとペイントツールらしくないので、カラーパレットを別のウィンドに分離してみまた。

# -*- coding: utf-8 -*-
import cv2
import numpy as np
#sx, syは線の始まりの位置
sx, sy = 0, 0
#ペンの色
color = (0, 0, 0)
#ペンの太さ
thickness = 1
#マウスの操作があるとき呼ばれる関数
def callback(event, x, y, flags, param):
    global img, sx, sy, color, thickness
    #マウスの左ボタンがクリックされたとき
    if event == cv2.EVENT_LBUTTONDOWN:
        sx, sy = x, y
    #マウスの左ボタンがクリックされていて、マウスが動いたとき
    if flags == cv2.EVENT_FLAG_LBUTTON and event == cv2.EVENT_MOUSEMOVE:
        cv2.line(img, (sx, sy), (x, y), color, thickness)
        sx, sy = x, y
# トラックバーの値を変更する度にRGBを出力する
def changePencil(pos):
    global palette_img, color, thickness
    r = cv2.getTrackbarPos("R", "palette")
    g = cv2.getTrackbarPos("G", "palette")
    b = cv2.getTrackbarPos("B", "palette")
    palette_img[:] = [b, g, r]
    color = (b, g, r)
    thickness = cv2.getTrackbarPos("T", "palette")
#画像を読み込む
img = cv2.imread("img.jpg")
#パレット画像を作成
palette_img = np.zeros((200, 512, 3), np.uint8)
#ウィンドウの名前を設定
cv2.namedWindow("img", cv2.WINDOW_NORMAL)
#パレットウィンドを生成
cv2.namedWindow("palette")
#マウス操作のコールバック関数の設定
cv2.setMouseCallback("img", callback)
#トラックバーのコールバック関数の設定
cv2.createTrackbar("R", "palette", 0, 255, changePencil)
cv2.createTrackbar("G", "palette", 0, 255, changePencil)
cv2.createTrackbar("B", "palette", 0, 255, changePencil)
cv2.createTrackbar("T", "palette", 1, 100, changePencil)
while(1):
    cv2.imshow("img", img)
    cv2.imshow("palette", palette_img)
    k = cv2.waitKey(1)
    #Escキーを押すと終了
    if k == 27:
        break
    #sを押すと画像を保存
    if k == ord("s"):
        cv2.imwrite("painted.png", img)
        break

numpyインポート

# -*- coding: utf-8 -*-
import cv2
import numpy as np

単色画像を作るために、numpyをインポートしましょう。

パレットウィンドの生成

#パレットウィンドを生成
cv2.namedWindow("palette")

paletteという名前でウィンドを生成しましょう。

パレット画像の生成

#パレット画像を作成
palette_img = np.zeros((200, 512, 3), np.uint8)

np.zerosで高さが200、幅が512、各値が[0, 0, 0]の画像を生成します。

パレットに色を表示

cv2.imshow("palette", palette_img)

palette_imgに現在のペンの色を表示します。色情報の更新はトラックバーのコールバック関数から行います。

トラックバーの表示ウィンドの変更

後はトラックバーの表示ウィンドを変更をすれば完成です。

# トラックバーの値を変更する度にRGBを出力する
def changePencil(pos):
    global palette_img, color, thickness
    r = cv2.getTrackbarPos("R", "palette")
    g = cv2.getTrackbarPos("G", "palette")
    b = cv2.getTrackbarPos("B", "palette")
    palette_img[:] = [b, g, r]
    color = (b, g, r)
    thickness = cv2.getTrackbarPos("T", "palette")

#トラックバーのコールバック関数の設定
cv2.createTrackbar("R", "palette", 0, 255, changePencil)
cv2.createTrackbar("G", "palette", 0, 255, changePencil)
cv2.createTrackbar("B", "palette", 0, 255, changePencil)
cv2.createTrackbar("T", "palette", 1, 100, changePencil)

投稿 OpenCV(Python)GUI基礎編、ペイントツールを作ってみたネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/286-2/feed/ 0
Pythonでシューティングゲームを作る2(Tkinterの並列処理) https://tech-tech.xyz/308-2/ https://tech-tech.xyz/308-2/#respond Wed, 27 Sep 2023 13:45:47 +0000 https://tech-tech.xyz/?p=348 Pythonでシューティングゲームを作る2回目です。 目次 はじめにtkinterのネタが尽きたかもしれない…ひとまず、シューティングゲームの完成を目指すこのアプリケーションのコードコードを見ながらクラス継承の説明継承を […]

投稿 Pythonでシューティングゲームを作る2(Tkinterの並列処理)ネットサーフィンの壺 に最初に表示されました。

]]>
warbird-2536186_640 Pythonでシューティングゲームを作る2回目です。

はじめに

tkinterのネタが尽きたかもしれない…

tkinterの入門のつもりで始めたもののtkinterで重要そうな関数はほどんど説明してしまいました。 キーボード操作やさまざまな図形の描写がありますがどれも過去に紹介した関数と類似した関数なのでチュートリアルで説明するよりはドキュメント風にまとめるほうが良いと考えました。

ひとまず、シューティングゲームの完成を目指す

tkinterのネタはこれから探すとして、ひとまずシューティングゲームの完成を目指そうと思います。 game3

このアプリケーションのコード

import tkinter

def motion(mouse):
    global player
    player.x = mouse.x - int(0.5 * player.w)
    player.y = mouse.y - int(0.5 * player.h)

def click1(mouse):
    global player
    player.shoot()

def is_in_window(flyer):
    flyer.move()
    return flyer.exist

def collision_flyers(flyers):
    f_1 = flyers[0]
    for f_2 in flyers[1:]:
        f_1.collision(f_2)
    return f_1.exist

def game_loop():
    Flyer.flyers = {k:v for k, v in Flyer.flyers.items() if is_in_window(v)}
    #Flyerの衝突判定を行う。ループ内で削除を行うのでforを使わない。
    i = 0
    while i < len(Flyer.flyers):
        k = list(Flyer.flyers.keys())[i]
        flyer_exist = collision_flyers(list(Flyer.flyers.values())[i:])
        if not flyer_exist:
            Flyer.flyers.pop(k)
        else:
            i += 1
    #60(ms)ごとにgame_loop()を呼びだす。
    window.after(60, game_loop)

class Flyer:
    flyers = {}
    next_id = 0
    def __init__(self, x, y, w, h, tag, vx=0, vy=0, atk=0, dfn=0, hp=1, color="white"):
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.tag = tag
        self.vx = vx
        self.vy = vy
        self.atk = atk
        self.dfn = dfn
        self.hp = hp
        self.color = color
        self.exist = True
        self.id = Flyer.next_id
        Flyer.next_id += 1
        Flyer.flyers[self.id] = self

    def move(self):
        canvas.delete(self.tag)
        self.x += self.vx
        self.y += self.vy
        if self.x + self.w < 0 or self.y + self.h < 0:
            self.exist = False
        canvas.create_rectangle(self.x, self.y, self.x + self.w, self.y + self.h, fill=self.color, tag=self.tag)

    def __is_collision(self, flyer):
        return (flyer.y < self.y + self.h and self.y < flyer.y + flyer.h and flyer.x < self.x + self.w and self.x < flyer.x + flyer.w)

    def collision(self, flyer):
        if self.__is_collision(flyer):
            self.hp += min(0, self.dfn - flyer.atk)
            flyer.hp += min(0, flyer.dfn - self.atk)
            if self.hp < 0:
                self.exist = False
                canvas.delete(self.tag)
            if flyer.hp < 0:
                flyer.exist = False
                canvas.delete(flyer.tag)

class Bullet(Flyer):
    def __init__(self, x, y, w, h, tag, vx, vy, atk=1, dfn=0, hp=1, color="blue"):
        super().__init__(x, y, w, h, tag, vx, vy, atk, dfn, hp, color)

class Player(Flyer):
    game_over = False
    bullet_c = 0
    def __init__(self, x, y, w, h, tag="player", vx=0, vy=0, atk=1, dfn=0, hp=1, color="white"):
        super().__init__(x, y, w, h, tag, vx, vy, atk, dfn, hp, color)

    def shoot(self):
        bullet_w = 10
        bullet_h = 10
        bullet_v_x = 0
        bullet_v_y = -10
        bullet_atk = 10
        bullet_tag = "player_bullet" + str(Player.bullet_c)
        Bullet(self.x + int(0.5 * self.w - 0.5 * bullet_w), self.y - bullet_h, bullet_w, bullet_h, bullet_tag, bullet_v_x, bullet_v_y)
        Player.bullet_c += 1

class Enemy(Flyer):
    def __init__(self, x, y, w, h, tag, vx=0, vy=1, atk=1, dfn=0, hp=1, color="red"):
        super().__init__(x, y, w, h, tag, vx, vy, atk, dfn, hp, color)

#画面の設定
window = tkinter.Tk()
window.resizable(width=False, height=False)

#背景の設定
canvas = tkinter.Canvas(window, width = 400, height = 600)
canvas.create_rectangle(0, 0, 400, 600, fill="black")
canvas.pack()

#自機と敵機の設定
player = Player(180, 560, 40, 40)
enemy_1 = Enemy(180, 50, 40, 40, "enemy_1")
enemy_2 = Enemy(0, 50, 40, 40, "enemy_2")
enemy_3 = Enemy(360, 50, 40, 40, "enemy_3")

#マウスイベントを受け付ける
window.bind("<Motion>", motion)
window.bind("<Button-1>", click1)

game_loop()

#ウィンドウの応答
window.mainloop()

コードを見ながらクラス継承の説明

継承をどこで使うのか?

継承の説明 今回、書いたクラスはFlyer, Bullet, Player, Enemyの4つです。Bulletは発射される弾で、Playerは操作する機体、Enemyは敵です。これら3つは共通する要素(飛行する、ぶつかると壊れる)があります。できれば一箇所にまとめたい。 そこで継承が登場します。Flyerという3つのクラスの共通する要素を抜き出したクラスを書きます。そして、それを継承することで共通部分をまとめることができます。

Flyerオブジェクトのグローバル変数

class Flyer:
    flyers = {}
    next_id = 0

グローバル変数はすべてのFlyerを管理するための変数です。グローバル変数は__init__の前に記述します。

  • flyers: すべてのFlyerの辞書。idとFlyerを格納する。
  • next_id: Flyerに割り振るidを被らないように決める。

Flyerオブジェクトのメンバ変数

    def __init__(self, x, y, w, h, tag, vx=0, vy=0, atk=0, dfn=0, hp=1, color="white"):
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.tag = tag
        self.vx = vx
        self.vy = vy
        self.atk = atk
        self.dfn = dfn
        self.hp = hp
        self.color = color
        self.exist = True
        self.id = Flyer.next_id
        Flyer.next_id += 1
        Flyer.flyers[self.id] = self

すべてのFlyerは四角とします。
説明

  • self.x(x座標)、self.y(y座標)でFlyerの位置の位置を表します。
  • self.w(幅)、self.h(高さ)でFlyerの大きさを表します。
  • self.vx(x軸方向の移動速度)、self.vy (y軸方向の移動速度)
  • self.atk(衝突したとき相手に与えるダメージ量)、self.dfn(衝突したとき受けるダメージの軽減量)、self.hp(ヒットポイント0になると消える)
  • self.colorは画面に表示されるFlyer色
  • self.existはFlyerが存在しているかどうかを示す変数
  • self.id は識別子

Flyerオブジェクトのメンバ関数

    def move(self):
        canvas.delete(self.tag)
        self.x += self.vx
        self.y += self.vy
        if self.x + self.w < 0 or self.y + self.h < 0:
            self.exist = False
        canvas.create_rectangle(self.x, self.y, self.x + self.w, self.y + self.h, fill=self.color, tag=self.tag)

    def __is_collision(self, flyer):
        return (flyer.y < self.y + self.h and self.y < flyer.y + flyer.h and flyer.x < self.x + self.w and self.x < flyer.x + flyer.w)

    def collision(self, flyer):
        if self.__is_collision(flyer):
            self.hp += min(0, self.dfn - flyer.atk)
            flyer.hp += min(0, flyer.dfn - self.atk)
            if self.hp < 0:
                self.exist = False
                canvas.delete(self.tag)
            if flyer.hp < 0:
                flyer.exist = False
                canvas.delete(flyer.tag)

説明

  • self.__is_collisionは衝突判定を行います。
  • self.moveはFlyerの移動を表してます。
  • self.collisionは衝突したときの処理を行ってます。

Flyerオブジェクトを継承

class Player(Flyer):
    game_over = False
    bullet_c = 0
    def __init__(self, x, y, w, h, tag="player", vx=0, vy=0, atk=1, dfn=0, hp=1, color="white"):
        super().__init__(x, y, w, h, tag, vx, vy, atk, dfn, hp, color)

    def shoot(self):
        bullet_w = 10
        bullet_h = 10
        bullet_v_x = 0
        bullet_v_y = -10
        bullet_atk = 10
        bullet_tag = "player_bullet" + str(Player.bullet_c)
        Bullet(self.x + int(0.5 * self.w - 0.5 * bullet_w), self.y - bullet_h, bullet_w, bullet_h, bullet_tag, bullet_v_x, bullet_v_y)
        Player.bullet_c += 1

継承はclass クラス名(親クラス名)という書き方で行います。PlayerがFlyerを継承する場合はclass Player(Flyer)と書きます。Playerは特殊なクラスなので新たな機能を追加してます。

Tkinterでの簡単な並列処理

今回、サブプロセスで実行される関数

def game_loop():
    ...Flyerの移動とFlyerの衝突判定
    (ここら辺は汚くなってるので書き直すと思います)...
    #60(ms)ごとにgame_loop()を呼びだす。
    window.after(60, game_loop)

game_loopがメインプロセスと別に実行される処理です。 サブプロセスといってもゲームに関する処理はほとんどgame_loopで行われます。 メインプロセスではマウスやキーボードの応答を行ってます。 並列処理を簡単に行う方法としてwindow.afterを使います。

window.afterの使い方。

window.after(delay_time, sub_process)

sub_processはサブプロセスで実行する関数です。 delay_timeはwindow.afterが実行されてから遅延してサブプロセスが開始されるまでの時間です。

おわり

まだ、シューティングゲームには足りないところがあるので追加して解説していこうと思います。今回の記事もコードが長くてまだまだ足りないところがあるので気が向いたらリライトします。

投稿 Pythonでシューティングゲームを作る2(Tkinterの並列処理)ネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/308-2/feed/ 0
OpenCVオープニング(opening)とクロージング(closing)でマスク画像を加工 https://tech-tech.xyz/350-2/ https://tech-tech.xyz/350-2/#respond Wed, 27 Sep 2023 13:45:44 +0000 https://tech-tech.xyz/?p=332 オープニングとクロージング オープニングとは白黒画像の細かいごみを取り除く手法です。縮小→拡大という処理を行います。クロージングとは白黒画像の細かい穴を取り除く手法です。 OpenCVでオープニング処理とクロージング処理 […]

投稿 OpenCVオープニング(opening)とクロージング(closing)でマスク画像を加工ネットサーフィンの壺 に最初に表示されました。

]]>
オープニングとクロージング

オープニングとは白黒画像の細かいごみを取り除く手法です。縮小→拡大という処理を行います。クロージングとは白黒画像の細かい穴を取り除く手法です。

OpenCVでオープニング処理とクロージング処理

cv2.morphologyExを使うとオープニング処理とクロージング処理を行えます。

cv2.morphologyEx(img, flag, kernel)

imgは処理を行う画像です。flagでcv2.MORPH_OPENを指定するとオープニング処理ができます。flagでcv2.MORPH_CLOSEを指定するとクロージング処理ができます。kernelはオープニング処理の場合、消したいくずの形、クロージング処理の場合、埋めたい穴の形を表す各成分が1または0の行列です。

オープニング処理、クロージング処理でマスク画像を加工

準備

まずはマスク画像を用意します。前景を抽出するためにHSV分解と2値化で作ったマスク画像です。詳しいマスク画像の作り方については OpenCV(Python)2値化の使い方で紹介しています。

jeans-564089_640 jeans_mask

オープニング処理でくずを除去

赤で囲んでいるくずをオープニング処理で取り除きます。

jeans_mask_openning

引数に画像のパスを指定するとオープニング処理が実行されます。

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import sys

filename = sys.argv[1]
img = cv2.imread(filename, 1)
kernel = np.ones((2,2),np.uint8)
result = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imwrite(filename[:filename.rfind(".")] + "_opening.png", result)

オープニング処理の結果

小さいくずが消えているのがわかると思います。

jeans_mask_opening

クロージング処理で穴を除去

ジーンズのしわの部分が穴になっているので埋めていきます。大きい穴なのでkernelを大きくしています。

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import sys

filename = sys.argv[1]
img = cv2.imread(filename, 1)
kernel = np.ones((25,25),np.uint8)
result = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imwrite(filename[:filename.rfind(".")] + "_closing.png", result)

クロージングの実行結果

オープニング処理をした先ほどの画像を引数に渡してスクリプトを実行すると下記のような画像が生成されます。

jeans_mask_opening_closing

HSV分解+2値化+オープニング処理+クロージング処理で前景抽出

OpenCV(Python)2値化の使い方で紹介したスクリプトにクロージング処理とオープニング処理を行って前景抽出を行います。

サンプルコード

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import sys

filename = sys.argv[1]
#画像を読み込む
img = cv2.imread(filename)
#BGR画像をHSV画像に変換
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#HSVに分解
h_img, s_img, v_img = cv2.split(hsv)
#彩度のレイヤーを2値化してマスク画像を生成
_, mask_img = cv2.threshold(s_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
#オープニングとクロージング
kernel = np.ones((2,2),np.uint8)
mask_img = cv2.morphologyEx(mask_img, cv2.MORPH_OPEN, kernel)
kernel = np.ones((25,25),np.uint8)
mask_img = cv2.morphologyEx(mask_img, cv2.MORPH_CLOSE, kernel)
#マスク画像を合成
result = cv2.merge(cv2.split(img) + [mask_img])
#「マスク部分を透明化した結果画像」とマスク画像を書き出す
cv2.imwrite(filename[:filename.rfind(".")] + "_result.png", result)
cv2.imwrite(filename[:filename.rfind(".")] + "_mask.png", mask_img)

結果

左の画像が入力で右の画像が出力です。ざっくりと前景が抽出できています。

jeans-564089_640jeans_result

投稿 OpenCVオープニング(opening)とクロージング(closing)でマスク画像を加工ネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/350-2/feed/ 0
UbuntuにApacheを入れてwebサイトのローカル環境を作ってみた。 https://tech-tech.xyz/359-2/ https://tech-tech.xyz/359-2/#respond Wed, 27 Sep 2023 13:45:40 +0000 https://tech-tech.xyz/?p=327 Apacheとは? Apacheは無料で公開されているWebサーバーです。半数のWebサイトがApacheを使って動いています。 目次 Apacheがやっていることって?もう少し、具体的に…Apacheにアクセス表示され […]

投稿 UbuntuにApacheを入れてwebサイトのローカル環境を作ってみた。ネットサーフィンの壺 に最初に表示されました。

]]>
Apacheとは?

Apacheは無料で公開されているWebサーバーです。半数のWebサイトがApacheを使って動いています。

Apacheがやっていることって?

Apacheがやっていることはブラウザからメッセージ(getリクエスト、postリクエスト)を受け取り、それに応じてなんらかの応答(ページの表示、301リダイレクト、404エラーなど…)を返します。つまり、リクエストという入力に対してレスポンスという出力を行っているブラックボックスがApacheです。

もう少し、具体的に…

例えば、サーバーにおいてあるhtmlで書かれたWebページにリクエストがあったとします。このとき、Apacheはhtmlを読み込みます。実は、htmlをそのままメッセージとして返信するだけでは通信はできません。メッセージを送信するためにはヘッダ情報が必要です。ヘッダ情報にはステータスコード、サーバー情報、応答日時などが含まれて居ます。このヘッダを付けるのがApacheの主な役目です。他にはPHP、Perlなどのサーバーサイドスクリプトの処理やMysqlとスクリプトの連携なども行ってます。

ApacheをUbuntuにインストール

Apacheはパッケージ管理ソフトで簡単にインストール可能です。Ubuntuならapt-getで簡単にインストールできます。(centosはyum)端末を開いて下記のようにコマンドを入力しましょう。

$ sudo apt-get update
$ sudo apt-get install apache2

Apacheにアクセス

VirtualBox_ub_09_09_2017_14_27_43
インストールが終わるとApacheは自動で起動します。ブラウザでhttp://localhost/にアクセスしてみましょう。上記のような画面が表示されていれば正常に動いてます。

表示されない場合

もし、Apacheの画面が表示されない場合、下記のコマンドを入力してApacheを起動しなおしてください。restartは起動もかねてます。

$ sudo /etc/init.d/apache2 restart

Apacheの基本的なコマンド

Apacheを起動

$ sudo /etc/init.d/apache2 start

または

$ sudo service apache2 start

Apacheを終了

$ sudo /etc/init.d/apache2 stop

または

$ sudo service apache2 stop

Apacheを再起動

$ sudo /etc/init.d/apache2 restart

または

$ sudo service apache2 restart

Apacheの状態確認

$ sudo /etc/init.d/apache2 status

または

$ sudo service apache2 status

投稿 UbuntuにApacheを入れてwebサイトのローカル環境を作ってみた。ネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/359-2/feed/ 0
Python(OpenCV)透明画像の重ね合わせ https://tech-tech.xyz/367-2/ https://tech-tech.xyz/367-2/#respond Wed, 27 Sep 2023 13:45:35 +0000 https://tech-tech.xyz/?p=323 OpenCVで透明画像の重ね合わせ =================== 今回やること 左の “background.png” 画像に透明チャンネル付き “penguin.png&# […]

投稿 Python(OpenCV)透明画像の重ね合わせネットサーフィンの壺 に最初に表示されました。

]]>
OpenCVで透明画像の重ね合わせ

===================

今回やること

左の “background.png” 画像に透明チャンネル付き “penguin.png” 画像を貼り付けて、下のような画像を作る。

background penguin
result

サンプルコード

# -*- coding: utf-8 -*-
import cv2
import numpy as np

def clip_alpha_image(x, y):
    # x, yで貼り付け位置を選択
    f_h, f_w, _ = foreground.shape
    # 透明部分が0、不透明部分が1のマスクを作る
    alpha_mask = np.ones((f_h, f_w)) - np.clip(cv2.split(foreground)[3], 0, 1)
    # 貼り付ける位置の背景部分
    target_background = background[y:y+f_h, x:x+f_w]
    # 各BRGチャンネルにalpha_maskを掛けて、前景の不透明部分が[0, 0, 0]のnew_backgroundを作る
    new_background = cv2.merge(list(map(lambda x: x * alpha_mask, cv2.split(target_background))))
    # BGRAをBGRに変換した画像とnew_backgroundを足すと合成できる
    background[y:y + f_h, x:x+f_w] = cv2.merge(cv2.split(foreground)[:3])

background = cv2.imread("background.png")
foreground = cv2.imread("penguin.png", -1)
clip_alpha_image(600, 250)
cv2.imwrite("result.png", background)

関数の説明

np.clip(array, min, max)

np.clip は array の値で min より小さい値は min に、 max より大きな値は max に変換した array を返します。

BGR = cv2.split(img)

cv2.split は img をチャンネル分解する関数です。 img が BGR (青緑赤) 画像のとき、 BGR[0] は青チャンネル、 BGR[1] は緑チャンネル、 BGR[2] は赤チャンネルとなるようなリストを返します。 RBG ではなく BGR となるところに注意が必要です。

img = cv2.merge(BGR)

cv2.merge は cv2.split の逆の処理を行う関数です。

投稿 Python(OpenCV)透明画像の重ね合わせネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/367-2/feed/ 0
OpenCV(Python)画像の拡大・縮小 https://tech-tech.xyz/370-2/ https://tech-tech.xyz/370-2/#respond Wed, 27 Sep 2023 13:45:31 +0000 https://tech-tech.xyz/?p=301 目次 サイズを指定する場合使用例倍率を指定する場合使用例高度なリサイズ方法pillow, PILによる画像のリサイズ サイズを指定する場合 cv2.resize(img,(width,height)) 第1引数: 画像 […]

投稿 OpenCV(Python)画像の拡大・縮小ネットサーフィンの壺 に最初に表示されました。

]]>
サイズ指定の例

サイズを指定する場合

cv2.resize(img,(width,height))
  • 第1引数: 画像
  • 第2引数: リサイズしたい(幅, 高さ)のタプル

使用例

import cv2
img = cv2.imread('img.jpg')
width, height = 640, 480
img = cv2.resize(img,(width,height))
cv2.imwrite('img-resize.jpg',img)

画像を幅:640、高さ:480で保存します。

倍率を指定する場合

cv2.resize(img,fx,fy)
  • 第1引数: 画像
  • 第2引数: リサイズしたい幅の倍率
  • 第3引数: リサイズしたい高さの倍率

使用例

import cv2
img = cv2.imread('img.jpg')
img = cv2.resize(img,0.5,0.5)
cv2.imwrite('img-half.jpg',img)

画像を半分のサイズにして保存します。

高度なリサイズ方法

cv2.resize(img,(width,height),fx,fy,interpolation)

interpolationを指定することで拡大・縮小の補完方法を指定することができます。

interpolationの一覧

  • cv2.INTER_NEAREST: 最近傍補間
  • cv2.INTER_LINEAR: バイリニア補間(デフォルト)
  • cv2.INTER_AREA: 平均画素法
  • cv2.INTER_CUBIC: 4×4 の近傍領域を利用するバイキュービック補間
  • cv2.INTER_LANCZOS4: 8×8 の近傍領域を利用する Lanczos法の補間

下の補完方法になるにつれて計算量が増えますが画像がなめらかになります。

pillow, PILによる画像のリサイズ

pillowによる画像のリサイズについても下記の記事で解説しています。

https://www.tech-tech.xyz/archives/python-img-resize.html

投稿 OpenCV(Python)画像の拡大・縮小ネットサーフィンの壺 に最初に表示されました。

]]>
https://tech-tech.xyz/370-2/feed/ 0