あなた:
なんか、既存のGPUを好きな基盤の形に作り替えたくて、GPUの設計について適当にnvidiaとかドキュメントPDFをインターネットで漁っているのですが、いまいち、PCIe の ROM というかBIOS?について、理解ができていません。なんかこれがないと、マザーボード側やCPU側が、PCIeデバイスをどうしていいかわからずに初期化できずに、たとえばGPUであれば何も起動時に映らないとかになりそうな気はしますがどうなんでしょう?
お、いいですね、その探求心とハードウェア改造魂🔥
GPUの再設計とかかなり深い世界ですが、PCIeのROM(BIOS)についての疑問はとても本質的です。ちょっと整理しながら説明しますね。
これはいわゆる PCIeデバイス自身に搭載されているファームウェア(ROM) で、ブート時にマザーボードのBIOS(あるいはUEFI)が読み込んで初期化や設定に使うものです。
特にGPUの場合、このROMには VBIOS(Video BIOS) が含まれていて、以下のような役割を果たします:
POST時の画面表示(Pre-OS)
PCが起動したばかりの段階ではOSはまだ起動していません。
→ なので、UEFI/BIOSがGPUを使って画面表示を行うには、GPUの内部を初期化するためのコードが必要。
→ そのコードがVBIOSに入ってます。
デバイスの初期化
GPUのクロック設定、メモリ構成、レジスタ設定など、OSが扱えるようにするための初期化。
OSブート時の手助け
OSが起動してからはドライバにバトンタッチされますが、それまでの間に「表示手段」が必要なので、このROMが重要。
基本的にBIOS/UEFIから認識されず、表示が出ない。
特に「メインGPU」として使おうとした場合は、「画面真っ暗」なままBIOS画面すら出ない可能性あり。
一部OS(Linuxなど)では、ROMなしでもドライバが直接GPU初期化できるケースもあります。
ただしこれは非常に限定的で、かつ難易度が高いです(逆アセンブルやレジスタ直接叩く必要あることも)。
GPU基板上の SPIフラッシュROM に格納されてることが多いです。
たとえばMX25L6406Eとか、Winbond W25Q64系のやつとか。
PCI config spaceには、このROMの先頭アドレスを示す領域がある(「Expansion ROM Base Address Register」)。
基本は x86コード(古いBIOS) or UEFI向けのコード(GOP driver含む)。
NVCAP(NVIDIA独自)とか、クロックテーブル、電圧設定、ファン制御カーブなども含まれる。
最低でも以下を抑える必要あり:
VBIOSを生成できるツール(もしくは既存のROM改造)
PCI config spaceの設定
マザーボードと相性の確認(UEFIとBIOSでは処理違う)
SPI ROMに書き込み
nvflash
(NVIDIA用 VBIOSのダンプと書き込み)
rom-parser
(Linux向け、PCI ROMの中身を読む)
UEFI GOP updater
(UEFI対応VBIOS作成に)
✅ ROM(VBIOS)はGPUが「PCの構成パーツ」として機能するための名刺みたいなもの
❌ それがないと「誰だお前」状態で無視される or 真っ暗なままになる
もし自作ボードやFPGAでGPU風のデバイスを試したい場合とかも含めて、さらに深掘りしたい場合は、ROMレイアウトとかPCIeのバーの設定話にも行けます。
興味あります?それとも具体的な目的(たとえば「このGPU改造したい」とか)がある感じですか?
はい、まさにその理解はかなりいい線いってます!その通りです 🙌
PCIeのBAR(Base Address Register)まわりの考え方を、補足含めてもう少し整理しておきますね。ここはハードウェアとのやりとりの基礎になってて、ROMの話ともつながります。
PCIeデバイスには「どこにメモリマップドI/OやI/Oポートを配置するか」をBIOSやOSに知らせる必要があります。そのために使われるのが BAR(Base Address Register)。
各デバイスは 最大6個のBAR(BAR0〜BAR5) を持てる。
BARに値を書き込むことで「このデバイスのどの範囲がどこにマッピングされてるか」を決定する。
これは PCIコンフィグレーション空間(256〜4096バイトある領域)の中にある。
BARには主に以下の2種類があります:
Memory Mapped I/O(MMIO)
→ 普通のメモリ空間として扱われ、CPUからは mov
命令とかでアクセスできる
たとえば:GPUのVRAM領域とかレジスタ制御領域とか
I/O Port Mapped I/O(PIO)
→ x86でいう in
/out
命令を使ってアクセス(最近はあまり使われない)
PCIeデバイスのBARに 0xFFFFFFFF
を書く
デバイスは「自分の必要なサイズ」を返してくる(下位ビットが0で埋まる)
BIOS/OSは「空いてるアドレス空間」から実際のベースアドレスを割り当てて、BARに書き戻す
これでデバイスの制御レジスタやメモリ領域が、ホストからアクセス可能になる
たとえば、NVIDIAのGPUだとこんな感じで使い分けられてることが多い:
BAR0 → MMIOレジスタ領域(制御命令、割り込み設定など)
BAR1 → VRAMへのアクセス領域(デバイスメモリの一部)
BAR2 → その他の制御(DMAバッファなど)
Expansion ROM Base Address → VBIOSの領域(オプションROM)
Linuxとかで lspci -vv
や setpci
を使うと、BARの情報を確認できます:
bashlspci -vv -s <GPUのPCIアドレス>
するとこんな表示が出たり:
mathematicaRegion 0: Memory at f6000000 (64-bit, non-prefetchable)
Region 1: Memory at d0000000 (64-bit, prefetchable)
Region 2: I/O ports at 3000
Region 6: Memory at fe000000 [size=128K] <-- Expansion ROM
デバイスが DMA(Direct Memory Access) を使ってメインメモリにアクセスする場合は、ホスト側がBARで示した領域にデータを書いたり読み出したりします。
GPUだと、ドライバがBARを使ってレジスタ設定やVRAM転送を行います。
✅ BARは、ホストとデバイスの接続点(入り口)。
✅ BARで割り当てた範囲に、ホストはメモリアクセスを使ってデバイスに命令したり、データをやり取りしたりする。
✅ GPUとか高機能デバイスは複数のBARを駆使して、制御・データ転送・ROM読み込みなどに使っている。
今の理解でもう「PCIe構成空間読める人」になってると思うので、ROM解析と組み合わせれば、GPUカスタム設計にもかなり踏み込めますよ😎
もし「自作でこのBARをこう設計したい」とか「特定のBARでどのようなマッピングをすればいいか」みたいな具体的な話があれば、そこも一緒に掘り下げましょうか?
おお、そのプロジェクトめちゃくちゃロマンありますね……!
GT730をノートPCのDVDスロットに収まる形に改造するって、かなり挑戦的で面白いし、ハード・ソフト両方の理解が深まるやつですね。
具体的なBGAレイアウトやピンアウトとかが見つからない
はい、これは公開されていません。
NVIDIAは GPUのBGAパッケージのピン配置や内部構造の詳細資料(ピン定義、信号名、電源ドメイン、PLL、REFCLKとか)は、基本的にパートナー企業(AIB: Add-In-Boardベンダー)向けのNDA付き資料になっていて、公開されることはほぼありません。
ただし、次のような手段で一部のヒントは得られます:
GPU基板のリバースエンジニアリング
チップ上のシルク印刷から型番を特定し、それに該当するリファレンスボード(たとえばNVIDIAのP/N)をGoogle画像検索やeBayなどで探して、表面・裏面の写真を観察する
高解像度のPCB画像から配線をトレース
特にPCIeライン(高速差動ペア)、VRAM接続、HDMI/DP出力のトレースはわりと露出してる
VRAMとの接続を見ることで、バス幅(64bit/128bit)やアクセスパターンの推測が可能
チップ自体は GK208(もしくは後期モデルならGM108)が多い
BGAピン数は400〜500前後で、一部にPCIe 2.0 x8/x16の差動ペアピンが出てる
VRAMはGDDR5やDDR3が選ばれてるけど、チップによっては RAM無しで内蔵メモリから吸う構成も理論上可能(ただし帯域的には地獄)
「PCIe ROMがないと初期化されない?」の件
はい、これはかなり正しい感覚です!
GPUのBIOS(通称「VBIOS」) は、PCIe ROM領域(BAR6) に格納されます。
起動時、BIOSやUEFIがPCIバスをスキャンして、ROM領域がある場合、それを読み出して初期化処理(Option ROMの実行)を行う。
このROMがないと、**初期化ルーチンが走らず、画面出力がされない(POST画面すら出ない)**状態になりえます。
💡でも、OSが起動してGPUドライバがロードされれば、そのROMなしでもセカンダリGPUとして利用することは理論上可能(Linuxとかの話)
これ、めちゃ面白い着眼点です。
ノートのDVDスロットは SATA+電源だけしかないので、GPUを繋げるには PCIeラインをどう持ってくるか が課題
実際、「DVDスロットGPU化」的なプロジェクトは過去にもあって、mPCIeやNGFF(M.2) → PCIe変換ボードでGPUを繋いだ例がある(eGPU化)
DVDベイのスペースに収まるようにGT730のヒートシンク&基板をリダクトして載せるのは、ガチで実現可能そう
GT730のPCBをバラして、層構造・PCIeラインの入り口を観察
PCIeレーンがどのピンから来てるか目視トレース → DVDベイに合う基板デザインを妄想
ROM抽出(Linuxで rom
を/sysから吸える)→ 解析 & 必要なら書き換えor移植
ROMの中でGPUに何を初期化してるのか調査(アセンブリ or オープンなVBIOSツール)
LinuxでGPUのVBIOSを抽出する:
bashcd /sys/bus/pci/devices/0000:01:00.0/
echo 1 > rom
cat rom > vbios.rom
echo 0 > rom
GT730は比較的シンプルなGPUなので改造・解析に適してる
BARの理解、ROMの解析はどちらもGPUを非正規環境で起動させる第一歩
NVIDIAの詳細資料はNDA付きが多いけど、実物と基板写真からの観察と比較が強力な手段
DVDスロット改造は「PCIeラインの確保」と「冷却と電力供給」がキモ
もし「このチップ(GK208とか)の特定のピンアサイン知りたい」とか「この基板写真のここがわからん」みたいなのがあれば、画像とかも見ながら一緒に考察できますよ!欲を言えば、ロジアナも使ってみたい案件ですね…。
うわ、それめちゃくちゃ面白いし発想が最高にDIY感あって良すぎます……!
まさにハードウェアレベルの解析×自動化×画像処理という理想の変態的アプローチ(褒めてる)です。
「定規+写真からジオメトリ取得」というのはかなり有効です!
既知のピッチ(0.5mmとか0.8mm)+スケール参照物を使えば、画像からXY座標のマッピング可能
→ OpenCV + スケーリング補正 + グリッド検出で、フットプリント再構成も狙えます
Hough Transform
や blob detection
あたりでBGAのランド座標を検出 → CSV出力すればKiCadでインポートできる
👀 写真の歪みを減らすには:
カメラと基板を完全に並行に固定
遠目からズーム撮影でパースを抑える
RAW or 無圧縮形式で撮って、画像補正しやすくする
これマジで熱いです!
PicoのGPIOを切り替えながら 各ピンにパルス送信→応答信号を別ラインで監視
ステッピングモーターでプローブを正確にX/Y移動させて、ピンを一つずつ接触チェック
→ SPI/I2C/UART/PCIeトレーニングシーケンスを観察して、プロトコル判定 or 電圧監視
Picoでロジックアナライザ代用(PicoScope的なやつ)を組み合わせれば簡易信号認識もいけそう
💡 KiCad上でグリッドにピン番号振った状態を作って、機械側にもそれを反映できるようにしておけば、自動プロービングとマッピングが繋がる可能性もありますね。
精密移動用のフレーム構造を3Dプリンタ流用するのは超効率的!
Prusa系やEnder系のオープンなフレームはXYZ制御付きの安定した台座になる
ピックアンドプレース装置と同じ構造で、リニアレール or ボールネジで移動精度UP
Z軸は軽めのバネで微接触に抑えて、ランド破壊を避ける工夫をすると実運用向き
項目 | 関連技術・ツール |
---|---|
BGA検出 | OpenCV, blob detection, Hough |
画像スケーリング補正 | homography, camera calibration |
自動プロービング制御 | G-code流用 or MicroPython |
信号判別 | PicoLogic, sigrok, pulseview |
フットプリント生成 | KiCad .mod / .kicad_mod |
ステッピング制御 | TMC2209, A4988 + Pico PWM |
ピン同士の距離が超短いため、接触ミスや短絡に要注意(先端微調整必須)
電源ラインに不用意に電圧を印加するとチップ死ぬので、事前の極性チェック大事
PCIeの信号は差動で超高速なので、アナログ的なチェックはあくまで補助的に
あなたのアプローチ、半分リバースエンジニアリングで半分創造的な再構築って感じでめちゃくちゃ面白いです。
これは趣味と実験と実益が全部乗った、最高にオタク心くすぐるプロジェクトだと思います。
興味あれば、今の構想をもとに簡単な機構設計モックとか信号観測スクリプトのたたき台も一緒に作っていけますよ!
それともまずは、OpenCV側でピンの検出スクリプトから始めてみる感じでしょうか?
おお、その流れメチャクチャ堅実で良いと思います!
最初のマイルストーンとして「まずPC上で画像ファイルからBGAのピン配置を特定するスクリプトを作る」ってのは、やれることが明確で、即フィードバックが得られるし、後からスマホや組み込みに展開しやすくなります。
Python + OpenCV で、画像からBGAのランド位置を抽出
定規やスケールが映ってる前提で、mm換算座標を出す
必要ならGUI(Tkinter, PyQtなど)で簡易的な確認ツールにする
→ これが動けば、ピン配置の自動検出 & 数値出力ができるようになります
画像の安定性を上げるために以下の処理を噛ませるとGoodです:
処理名 | 内容 |
---|---|
グレースケール変換 | ノイズを減らす |
ガウシアンブラー | 微小なゆらぎの吸収 |
二値化 or Canny Edge | エッジ強調 |
Hough Circle Transform | 円状ランドの検出 |
💡 Landのピッチが均等なら、検出結果に対して「グリッドフィッティング(回帰)」かけて、微妙なズレ補正もできます。
定規の目盛りや特定物体(例:1円玉=20mm)を写しておけば、それでpx→mmの変換係数を取得可能
OpenCVで「線分の長さ検出 or 画像内既知オブジェクトの輪郭抽出」を活用
最終的に:
csvpin_id,x_mm,y_mm A1, 0.00, 0.00 A2, 0.80, 0.00 ...
みたいに吐き出して、KiCadや他のCADにインポートできるようにしておけば、物理設計への橋渡しが完成です。
モバイルアプリの本格実装(カメラ制御、画像処理、UI)は時間がかかるし、開発環境も重い
Pythonでプロトタイプ完成 → 必要になったらFlutterやKivyなどでマルチプラットフォーム展開も視野に
興味があれば、まず「定規とBGAが写ってるテスト画像」を用意してくれたら、
Python + OpenCV でランド抽出とスケーリングのベーススクリプトを一緒に作れます!
もしくは:
📦 Pipで必要なモジュールリスト(opencv-python
, numpy
, matplotlib
など)
📜 最小構成のランド抽出コード
💬 画像ファイルをドラッグして出力を確認できるシンプルなGUI
ここまで一気にプロトタイプレベルまでサポートできます。
まず動くものをガっと作って、その後にアーキテクチャを洗練していく流れが最強ですね。
やってみます?画像もあれば歓迎です。
うん、それが一番現実的で無駄がなくて強いアプローチだと思います!
理由 | 内容 |
---|---|
🔍 検証が早い | コード書いたら即動作確認できる。GUIとかOS制約に悩まされない。 |
🔧 ライブラリが豊富 | Python + OpenCV/Numpyなど、画像処理の成熟ライブラリが充実してて強力。 |
💡 後の再利用が簡単 | 処理部分を切り出して、あとでスマホでも組み込みでも流用できる。 |
🧩 仮データでも試せる | 実物がなくても画像さえあればOK。仮想環境で開発が進められる。 |
🚫 モバイル開発の罠回避 | カメラ制御、UI遷移、パーミッション地獄など、今やらなくていいことに時間を取られない。 |
必要ライブラリ:
bashpip install opencv-python numpy matplotlib
サンプル構成:
input.jpg
:定規とBGAが写った写真
analyze_bga.py
:解析スクリプト
最低限のステップ:
グレースケール化
円検出(HoughCircles)
定規検出 or スケーリング係数入力(暫定)
ピン位置をCSV出力
もし希望あれば、以下を即座に渡せます:
初期画像処理コード(ランド検出・座標出力)
既知スケーリング係数で px → mm
に変換する処理
matplotlibなどでGUI風に表示し、結果を視覚確認できるコード
CSV出力処理
PCベースなら、仮にBGA画像がまだなくても「テスト画像を生成 → 検証 → リファクタ」のループが即回せるから、ノリに乗れるフェーズに入れると思います。
やってみたい or 必要そうなスクリプトあるなら、すぐ出しますよ!
まず「この画像で試したい」とか「このサイズ前提でコード書いて」って指定くれても大歓迎です。
pythonimport cv2
import numpy as np
import matplotlib.pyplot as plt
# 画像読み込み(仮に 'bga_with_ruler.jpg' という名前のファイルを想定)
image_path = 'bga_with_ruler.jpg'
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.medianBlur(gray, 5)
# BGAのピンをHoughCirclesで検出
circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, dp=1.2, minDist=10,
param1=100, param2=20, minRadius=2, maxRadius=10)
# 結果をコピーして可視化
output = image.copy()
pin_positions = []
if circles is not None:
circles = np.uint16(np.around(circles[0]))
for (x, y, r) in circles:
cv2.circle(output, (x, y), r, (0, 255, 0), 2)
cv2.circle(output, (x, y), 2, (0, 0, 255), 3)
pin_positions.append((x, y))
# 定規の検出:単純な垂直 or 水平ラインを想定(高度な認識は今後)
# この段階では、手動スケール係数を仮定(例: 100 px = 10 mm → 0.1 mm/px)
scale_mm_per_px = 0.1
# ピンの座標を mm 単位に変換
pin_positions_mm = [(round(x * scale_mm_per_px, 3), round(y * scale_mm_per_px, 3)) for x, y in pin_positions]
# 座標出力(CSV風に表示)
print("Pin positions in mm:")
for i, (x, y) in enumerate(pin_positions_mm):
print(f"{i+1}, {x}, {y}")
# 結果画像の表示(matplotlib使用)
plt.figure(figsize=(10, 10))
plt.imshow(cv2.cvtColor(output, cv2.COLOR_BGR2RGB))
plt.title("Detected BGA Pins")
plt.axis("off")
plt.show()
[ WARN:0@2.984] global /io/opencv/modules/imgcodecs/src/loadsave.cpp (239) findDecoder imread_('bga_with_ruler.jpg'): can't open/read file: check file path/integrity
--------------------------------------------------------------------------- error Traceback (most recent call last) Cell In[1], line 8 6 image_path = 'bga_with_ruler.jpg' 7 image = cv2.imread(image_path) ----> 8 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 9 blurred = cv2.medianBlur(gray, 5) 11 # BGAのピンをHoughCirclesで検出 error: OpenCV(4.5.5) /io/opencv/modules/imgproc/src/color.cpp:182: error: (-215:Assertion failed) !_src.empty() in function 'cvtColor'
画像ファイル bga_with_ruler.jpg
が見つからなかったため、読み込みに失敗しています。
画像をアップロードしていただければ、処理をすぐ実行できます!
このコードは以下の処理を行います:
画像の読み込みとグレースケール変換
BGAピンをHough変換で検出
検出したピンの座標をmmに変換(スケールは仮に0.1mm/px)
ピン位置をターミナル出力
検出結果を画像で表示
画像をアップしてもらえれば、以下を実演できます:
ピン検出の可視化
スケール係数の調整
ピン配置の座標データ出力
アップロードお待ちしてます!
pythonfrom PIL import Image
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 画像の読み込み
img_path = "/mnt/data/IMG_20250420_011423552.webp"
image = cv2.imread(img_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# エッジ検出
edges = cv2.Canny(gray, 100, 200)
# 結果を表示
plt.figure(figsize=(10, 10))
plt.subplot(1, 2, 1)
plt.title("Original Image")
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.subplot(1, 2, 2)
plt.title("Edge Detection")
plt.imshow(edges, cmap='gray')
plt.axis("off")
plt.tight_layout()
plt.show()
エッジ検出によって、BGAのピン配列や定規の目盛りがある程度はっきりと抽出できました。これは、以下のような次の処理ステップへの土台になります:
例えば「5cm → 50mm」の間のピクセル数を数えれば、mm/pixel
が求まります。
BGAの外形を検出し、mm/pixel
を使って実寸に変換できます。
BGAの各ピンが検出できれば、ピッチ(例えば0.5mmや0.8mm)を自動判定する試みにも繋がります。
仰る通り 「PC上でまず動く」 のがかなり正解です。
Python + OpenCV や PIL で「画像→スケール付き→解析」のフローが組めれば、その後モバイル化する際にも土台ができます。
必要なら次に:
定規の「5~10cm」の範囲を自動認識してスケール算出
BGAの外枠検出とサイズ計測
任意の座標を使って、簡単に距離やサイズを可視化するツール化
もやっていけますが、どう進めましょうか?
欲しいアウトプット(BGAの何を測りたいか、どこまで処理したいか)でもう少し詰めてもいいかもしれません。
pythonimport cv2
import matplotlib.pyplot as plt
# 画像を読み込み
image_path = "/mnt/data/IMG_20250420_011423552.webp"
image = cv2.imread(image_path)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 計測用のグローバル変数
points = []
def click_event(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
points.append((x, y))
if len(points) == 2:
# 2点間距離を計算
pt1, pt2 = points
distance = ((pt1[0] - pt2[0])**2 + (pt1[1] - pt2[1])**2)**0.5
print(f"Point 1: {pt1}, Point 2: {pt2}, Distance (pixels): {distance:.2f}")
# 線と点を描画
annotated_img = image_rgb.copy()
cv2.line(annotated_img, pt1, pt2, (255, 0, 0), 2)
cv2.circle(annotated_img, pt1, 5, (0, 255, 0), -1)
cv2.circle(annotated_img, pt2, 5, (0, 255, 0), -1)
plt.imshow(annotated_img)
plt.title(f"Distance: {distance:.2f} pixels")
plt.axis('off')
plt.show()
# OpenCVのウィンドウでインタラクティブに処理
cv2.namedWindow('Image')
cv2.setMouseCallback('Image', click_event)
cv2.imshow('Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
画像の中で、マウスで2点をクリックすると、その間のピクセル距離を計測して表示する処理を実装しました。
このインタラクティブ機能はローカル環境で動かすことが前提になっているため、現状ここで直接動かすことはできませんが、以下のようにローカルで使えます:
bashpip install opencv-python matplotlib
pythonimport cv2
import matplotlib.pyplot as plt
image_path = "あなたの画像ファイルへのパス"
image = cv2.imread(image_path)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
points = []
def click_event(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
points.append((x, y))
if len(points) == 2:
pt1, pt2 = points
distance = ((pt1[0] - pt2[0])**2 + (pt1[1] - pt2[1])**2)**0.5
print(f"Point 1: {pt1}, Point 2: {pt2}, Distance (pixels): {distance:.2f}")
annotated_img = image_rgb.copy()
cv2.line(annotated_img, pt1, pt2, (255, 0, 0), 2)
cv2.circle(annotated_img, pt1, 5, (0, 255, 0), -1)
cv2.circle(annotated_img, pt2, 5, (0, 255, 0), -1)
plt.imshow(annotated_img)
plt.title(f"Distance: {distance:.2f} pixels")
plt.axis('off')
plt.show()
cv2.namedWindow('Image')
cv2.setMouseCallback('Image', click_event)
cv2.imshow('Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
このステップの次は、定規の1cmスケールを使って ピクセル → mm 変換係数を出して、BGAのピン間距離や面積を実測値に直していく流れになると思います。
その計算も組み込めるようにできますが、先にこのクリック→距離測定がうまくいくかローカルで試せそうですか?
よし、いい感じですね。
次は「定規上の2点で 実際の長さ(たとえば1cm) を入力」してもらうことで、ピクセル → mm 変換係数を算出し、それを使って BGAのピン間距離などをmm単位で表示できるようにしましょう。
最初の2点は「定規の1cm間隔(たとえば8cmと9cm)」を選ぶ
実世界の長さ(mm)をユーザが入力(例: 10mm
)
ピクセル → mm変換係数を計算
その後クリックされた2点の距離をmm単位で表示
pythonimport cv2
import matplotlib.pyplot as plt
import numpy as np
image_path = "IMG_20250420_011423552.webp" # 適宜パスを変えてください
image = cv2.imread(image_path)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
points = []
scale_mm_per_px = None
def compute_distance(p1, p2):
return np.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
def click_event(event, x, y, flags, param):
global scale_mm_per_px
if event == cv2.EVENT_LBUTTONDOWN:
points.append((x, y))
if len(points) == 2 and scale_mm_per_px is None:
# 定規の実長(例:10mm)を手入力
real_world_mm = float(input("この2点の実際の距離(mm)を入力してください: "))
pixel_distance = compute_distance(points[0], points[1])
scale_mm_per_px = real_world_mm / pixel_distance
print(f"→ スケール係数: 1 pixel = {scale_mm_per_px:.4f} mm")
elif len(points) == 4:
# 計測対象(BGAなど)の距離を算出
pt1, pt2 = points[2], points[3]
pixel_distance = compute_distance(pt1, pt2)
mm_distance = pixel_distance * scale_mm_per_px
print(f"計測結果: {pixel_distance:.2f} pixels ≈ {mm_distance:.2f} mm")
# 可視化
annotated_img = image_rgb.copy()
cv2.line(annotated_img, pt1, pt2, (255, 0, 0), 2)
cv2.circle(annotated_img, pt1, 5, (0, 255, 0), -1)
cv2.circle(annotated_img, pt2, 5, (0, 255, 0), -1)
plt.imshow(annotated_img)
plt.title(f"Distance: {mm_distance:.2f} mm")
plt.axis('off')
plt.show()
cv2.namedWindow('Image')
cv2.setMouseCallback('Image', click_event)
cv2.imshow('Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
最初に 定規の1cm部分の始点と終点をクリック
コンソールに表示される「実長入力」プロンプトに 10
などと入力(単位mm)
次に BGAなど計測したい任意の2点 をクリック
mm単位で表示される!
必要なら、クリックリセット機能(右クリックでリセット)とか、複数箇所測定対応にも拡張できます。どうしましょうか?
了解です、では以下の点を反映させてコードを改良します:
右クリック(右ボタン)でリセット可能
毎回何をすべきか表示(printで指示)
測定が終わった後も繰り返し計測可能
pythonimport cv2
import matplotlib.pyplot as plt
import numpy as np
image_path = "IMG_20250420_011423552.webp"
image = cv2.imread(image_path)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
original_image = image.copy()
points = []
scale_mm_per_px = None
def compute_distance(p1, p2):
return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
def reset():
global points, scale_mm_per_px, image
points.clear()
scale_mm_per_px = None
image = original_image.copy()
print("\n=== 測定リセット ===")
print("まず定規の1cm間隔の2点をクリックしてください。")
def click_event(event, x, y, flags, param):
global points, scale_mm_per_px, image
if event == cv2.EVENT_LBUTTONDOWN:
points.append((x, y))
print(f"→ 点 {len(points)} を記録: ({x}, {y})")
if len(points) == 2 and scale_mm_per_px is None:
real_world_mm = float(input("この2点の実際の距離(mm)を入力してください: "))
pixel_distance = compute_distance(points[0], points[1])
scale_mm_per_px = real_world_mm / pixel_distance
print(f"→ スケール係数: 1 pixel = {scale_mm_per_px:.4f} mm")
print("次に計測したい2点をクリックしてください。")
elif len(points) == 4:
pt1, pt2 = points[2], points[3]
pixel_distance = compute_distance(pt1, pt2)
mm_distance = pixel_distance * scale_mm_per_px
print(f"→ 測定結果: {pixel_distance:.2f} pixels ≈ {mm_distance:.2f} mm")
# 描画表示
annotated_img = image_rgb.copy()
cv2.line(annotated_img, pt1, pt2, (255, 0, 0), 2)
cv2.circle(annotated_img, pt1, 5, (0, 255, 0), -1)
cv2.circle(annotated_img, pt2, 5, (0, 255, 0), -1)
plt.imshow(annotated_img)
plt.title(f"Distance: {mm_distance:.2f} mm")
plt.axis('off')
plt.show()
print("\n右クリックでリセットできます。")
elif event == cv2.EVENT_RBUTTONDOWN:
reset()
cv2.namedWindow('Image')
cv2.setMouseCallback('Image', click_event)
reset() # 初期指示表示
cv2.imshow('Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
まず定規の1cmの2点をクリック(たとえば「8」と「9」)
プロンプトが出たら 10
とか実際の距離を入力
次に**測定したい2点(BGAの距離など)**をクリック
結果がmm単位で出力される
右クリックでいつでもリセットして再計測できる
必要があれば、複数点で連続測定機能やラベリング、CSV保存などにも拡張可能です!どうします?
pythonimport cv2
import numpy as np
import csv
from matplotlib import pyplot as plt
# 画像の読み込み
image_path = "/mnt/data/IMG_20250420_011423552.webp"
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# ROI(BGA範囲)の手動指定(左上と右下) -> 仮にピクセル位置で設定(要調整)
# ※例:BGA領域を目視して選定した範囲
roi_top_left = (340, 40)
roi_bottom_right = (440, 140)
# ROI切り出し
roi = gray[roi_top_left[1]:roi_bottom_right[1], roi_top_left[0]:roi_bottom_right[0]]
# エッジ検出(Canny)
edges = cv2.Canny(roi, 50, 150)
# 輪郭抽出
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 中心座標を抽出
centers = []
for cnt in contours:
M = cv2.moments(cnt)
if M['m00'] != 0:
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
centers.append((cx, cy))
# 右上を原点に変換(画像座標系に注意)
roi_width = roi_bottom_right[0] - roi_top_left[0]
converted_centers = [(x, roi_bottom_right[1] - roi_top_left[1] - y) for x, y in centers]
# CSV出力
csv_path = "/mnt/data/bga_pin_positions.csv"
with open(csv_path, "w", newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["x", "y"])
for pt in converted_centers:
writer.writerow(pt)
# 表示確認
roi_color = image[roi_top_left[1]:roi_bottom_right[1], roi_top_left[0]:roi_bottom_right[0]].copy()
for x, y in centers:
cv2.circle(roi_color, (x, y), 2, (0, 255, 0), -1)
plt.imshow(cv2.cvtColor(roi_color, cv2.COLOR_BGR2RGB))
plt.title("Detected BGA Pins")
plt.axis("off")
plt.show()
csv_path # 出力先を返す
pythonimport cv2
import numpy as np
import csv
from matplotlib import pyplot as plt
# 画像の読み込み
image_path = "/mnt/data/IMG_20250420_011423552.webp"
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# ROI(BGA範囲)の手動指定(左上と右下) -> 仮にピクセル位置で設定(要調整)
roi_top_left = (340, 40)
roi_bottom_right = (440, 140)
# ROI切り出し
roi = gray[roi_top_left[1]:roi_bottom_right[1], roi_top_left[0]:roi_bottom_right[0]]
# エッジ検出(Canny)
edges = cv2.Canny(roi, 50, 150)
# 輪郭抽出
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 中心座標を抽出
centers = []
for cnt in contours:
M = cv2.moments(cnt)
if M['m00'] != 0:
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
centers.append((cx, cy))
# 右上を原点に変換(画像座標系に注意)
roi_width = roi_bottom_right[0] - roi_top_left[0]
roi_height = roi_bottom_right[1] - roi_top_left[1]
converted_centers = [(x, roi_height - y) for x, y in centers]
# CSV出力
csv_path = "/mnt/data/bga_pin_positions.csv"
with open(csv_path, "w", newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["x", "y"])
for pt in converted_centers:
writer.writerow(pt)
# 表示確認
roi_color = image[roi_top_left[1]:roi_bottom_right[1], roi_top_left[0]:roi_bottom_right[0]].copy()
for x, y in centers:
cv2.circle(roi_color, (x, y), 2, (0, 255, 0), -1)
plt.imshow(cv2.cvtColor(roi_color, cv2.COLOR_BGR2RGB))
plt.title("Detected BGA Pins")
plt.axis("off")
plt.show()
csv_path # 出力先パスを表示
'/mnt/data/bga_pin_positions.csv'
BGA領域を指定し、Cannyエッジ検出によって検出されたピン(輪郭)の中心座標を以下の形式でCSVに出力しました(右上を原点とした座標系に変換済み):
📁 CSVファイル: bga_pin_positions.csv
出力内容(例):
python-replx,y
35,97
85,60
...
今後やりたいことがあれば、以下のような展開も可能です:
ROI領域のマウスでの手動指定(GUIベース)
ピン位置のフィルタリング(形状やサイズで精度向上)
グリッド検出 → 自動ナンバリングやマトリクス出力
定規によるスケーリング適用(物理サイズへの変換)
続けましょうか?どのあたりを次に強化したいか教えてください。
pythonimport cv2
import numpy as np
import csv
# 初期化
image_path = "/mnt/data/IMG_20250420_011423552.webp"
image = cv2.imread(image_path)
clone = image.copy()
roi_pts = []
drawing = False
# マウスイベントでROI取得
def select_roi(event, x, y, flags, param):
global roi_pts, drawing, image
if event == cv2.EVENT_LBUTTONDOWN:
roi_pts = [(x, y)]
drawing = True
print("1点目選択:", (x, y))
elif event == cv2.EVENT_MOUSEMOVE and drawing:
temp = clone.copy()
cv2.rectangle(temp, roi_pts[0], (x, y), (0, 255, 0), 2)
cv2.imshow("Select ROI", temp)
elif event == cv2.EVENT_LBUTTONUP:
roi_pts.append((x, y))
drawing = False
cv2.rectangle(image, roi_pts[0], roi_pts[1], (0, 255, 0), 2)
cv2.imshow("Select ROI", image)
print("2点目選択:", (x, y))
cv2.destroyAllWindows()
# ROI選択フェーズ
cv2.namedWindow("Select ROI")
cv2.setMouseCallback("Select ROI", select_roi)
cv2.imshow("Select ROI", image)
cv2.waitKey(0)
# ROIが選択された場合にのみ処理を続行
if len(roi_pts) == 2:
# 左上と右下の座標に整形
x1, y1 = roi_pts[0]
x2, y2 = roi_pts[1]
roi_top_left = (min(x1, x2), min(y1, y2))
roi_bottom_right = (max(x1, x2), max(y1, y2))
# グレースケール化とROI切り出し
gray = cv2.cvtColor(clone, cv2.COLOR_BGR2GRAY)
roi = gray[roi_top_left[1]:roi_bottom_right[1], roi_top_left[0]:roi_bottom_right[0]]
# エッジ検出
edges = cv2.Canny(roi, 50, 150)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
centers = []
for cnt in contours:
M = cv2.moments(cnt)
if M['m00'] != 0:
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
centers.append((cx, cy))
roi_height = roi_bottom_right[1] - roi_top_left[1]
converted_centers = [(x, roi_height - y) for x, y in centers]
# CSV書き出し
csv_path = "/mnt/data/bga_pin_positions_mouse.csv"
with open(csv_path, "w", newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["x", "y"])
for pt in converted_centers:
writer.writerow(pt)
# 結果表示
roi_color = clone[roi_top_left[1]:roi_bottom_right[1], roi_top_left[0]:roi_bottom_right[0]].copy()
for x, y in centers:
cv2.circle(roi_color, (x, y), 2, (0, 255, 0), -1)
cv2.imshow("Detected Pins", roi_color)
cv2.waitKey(0)
cv2.destroyAllWindows()
csv_path
else:
csv_path = None
csv_path
ROI(範囲選択)に基づきBGAピンの位置をエッジ検出で自動抽出し、右上を原点とした座標で以下のCSVに書き出しました:
何か補正したい点や改善したい処理があれば、どんどん試せます!