2019年12月19日木曜日

3点を通る平面を同時座標(零空間)で求める

問題

3点$(2, 1, 1), (1, 0, 2), (3, 2, 1)$を通る平面の方程式を求めよ.

同次座標で点,面を表す

3点を$x,y,z\in\mathbb{P}^3$と平面を$\pi\in\mathbb{P}^3$とすると同時座標で次のように表すことができる.

\[ x=\left[ \begin{array}{c} 2\\ 1\\ 1\\ 1 \end{array} \right],\ y= \left[ \begin{array}{c} 1\\ 0\\ 2\\ 1 \end{array} \right],\ z= \left[ \begin{array}{c} 3\\ 2\\ 1\\ 1 \end{array} \right],\ \pi=\left[ \begin{array}{c} a\\ b\\ c\\ d \end{array} \right] \]

これらの3点$x,x',x''$が$\pi$を通るということは同時座標表現では次のように内積で表すことができる.

\[ x^T\pi=0 \] \[ y^T\pi=0 \] \[ z^T\pi=0 \]

3つの式を行列を用いてまとめて成分表示すると次のようになる.

\[ \left[ \begin{array}{c} 2 & 1 & 1 & 1\\ 1 & 0 & 2 & 1\\ 3 & 2 & 1 & 1 \end{array} \right] \left[ \begin{array}{c} a\\ b\\ c\\ d \end{array} \right] = \left[ \begin{array}{c} 0\\ 0\\ 0\\ 0 \end{array} \right] \]

このように左辺の行列を$X$とすると$X\pi=0$を満たす$\pi=0$以外の解$\pi$を求める問題になる. これは線形代数で習う零空間を求める問題と同じ.線形独立な行だけ係数行列をごちゃごちゃ変形する.

係数行列を基本変形で解きやすい形にする

上の線型方程式は,2つの行を入れ替えたり,1行を何倍かしたり, 何倍かしたものを別の行に足したり引いたりしても(基本変形という),方程式自体の本質は変わらない. 係数だけとりだして,この基本変形で解きやすい形にするのが常套手段. 解きやすい形っていうのは,下の行に行けば行くほど0が左側の列から0で埋め尽くされていく形.この場合は3$\times$4行列なので,2行目が0一つ,3行目が0二つになって,0の要素だけみると三角形になっていればいい.

\[ \begin{array}{rrrrrl} (1)& 2 & 1 & 1 & 1&\\ (2)& 1 & 0 & 2 & 1&\\ (3)& 3 & 2 & 1 & 1&\\ \hline (4)& 1 & 0 & 2 & 1& \cdots (2)\\ (5)& 0 & 1 & -3 & -1& \cdots (1) - 2\times(2)\\ (6)& 0 & 1 & -2 & -1& \cdots (3) - (1) - (2)\\ \hline (7)& 1 & 0 & 2 & 1& \cdots (4)\\ (8)& 0 & 1 & -3 & -1& \cdots (5)\\ (9)& 0 & 0 & 1 & 0& \cdots (6) - (5)\\ \end{array} \]

零空間を表現する

$X\pi=0$を満たす必要十分条件は

\[ x^T\pi=0,\ \ \ y^T\pi=0,\ \ \ z^T\pi=0 \]

係数行列を代入すると

\[ \begin{array}{c} a\cdot 1 + c\cdot 2 + d\cdot 1 = 0\\ b\cdot 1 - c\cdot 3 - d\cdot 1 = 0\\ c\cdot 1 = 0\\ \end{array} \]

これをみると$c=0$で確定で,あとは$d$が決まれば$a$も$b$も決まる.つまり一次元の自由度のみ残るので$d=t$と置くと

\[ \begin{array}{c} a = - t\\ b = t\\ c = 0\\ d = t \end{array} \]

なので解$\pi$は次のように表せる.

\[ \pi= \left[ \begin{array}{c} -1\\ 1\\ 0\\ 1 \end{array} \right]t \]

これが求めたい平面の方程式

つまり$\pi$は$[-1,1,0,1]$を何倍かしたものなら何でもいいという意味.それじゃ1倍の$[-1,1,0,1]$だっていいはずなので,求めたかった平面の方程式はこうなる.

\[ -x + y + 1 = 0 \]

上で求めた零空間の正確な答えは,$[-1,1,0,1]$を何倍かした任意のベクトルなら何でもいいというものだった. この方程式の係数に$t$倍した$[-1,1,0,1]$を代入しても,右辺が同じ平面の方程式を表すことは簡単に分かる. 同次座標では,平面は4次元のベクトル$[-1,1,0,1]$そのものであり, 同次座標では$[-1,1,0,1]$も$[-t,t,0,t]$も同じ面と見なすので,同次座標的な解釈でもやはりこの答えは正しい. つまり方程式の係数を余すところなく全部教えてくれるというところが,数学ってすごいと思う.

3点を代入して確認する

上の方程式に,3点$(2, 1, 1), (1, 0, 2), (3, 2, 1)$を代入してみて,方程式が成り立つか確認する.

\[ \begin{array}{c} (2, 1, 3) & \cdots & -2 + 1 + 1 = 0\\ (1, 0, 2) & \cdots & -1 + 0 + 1 = 0\\ (3, 2, 1) & \cdots & -3 + 2 + 1 = 0\\ \end{array} \]

となり,確かに3点とも方程式が成立する.なので答えは正しい.

2019年10月25日金曜日

フリーの光学シミュレータ

oPhysics

https://ophysics.com/l.html

光学だけでなく,波動やその他の様々な問題のシミュレータが準備されている. 特定の簡単な問題のパラメータを変えて可視化するだけで自由に光学素子を配置できる訳ではない.インストール不要.

Ray Optics Simulation

https://ricktu288.github.io/ray-optics/

ミラー,レンズ,遮蔽,観察者などを設置して幾何光学的にシミュレーションできる.波動光学には対応していない. ローカルのファイルに保存もできる.インストール不要.

LightXLab

http://lightxlab.zense.co.in/

ぱっとみはRay Optics Simulationを改造して作ったような見た目だけど,光線を色分けできるのが良い. ミラー,遮蔽はあるけどレンズがない.Flashが使われているので今後使えなくなるかも.インストール不要.

Algodoo

http://www.algodoo.com/

教材作成用のソフト?かなり高機能で光学だけでなく物理のシミュレーションもできる. ただし,光学専用ではないので複雑なシミュレーションをするのはかなり面倒.インストールが必要.

RayLab

http://www.raymak.com/wp/

かなり品質の高い光学シミュレーションのiOS用アプリ. 光学専用.無料版でもそこそこ使えるけど光学素子の種類は限られている. iPad Proあたりの大きな画面がないとちょっと使いづらいけど一番洗練されているかも.インストールが必要.

PhET Intractive Simulations

https://phet.colorado.edu/en/simulation/wave-interference

物理や数学など色んな教材用の見やすいシミュレータがあり確認したのはwave-interferenceという回折光学のシミュレータ. 波やレーザがスリットを通過するとどのようなパターンになるかを可視化してくれる. 複雑なことはできない代わりに操作がしやすい.インストール不要.

2019年9月7日土曜日

同一視点の2つのカメラ画像間の変換

変換の公式

\[ \left[ \begin{array}{c} u_a\\ 1 \end{array} \right] \propto K_a R_a R_b^{-1} K_b^{-1}\left[ \begin{array}{c} u_b\\ 1 \end{array} \right] \]

記号$\propto$は,両辺が相似(両辺が平行なベクトルもしくはスケール倍)であることを意味するので,実際には右辺を計算して得られたベクトルが$[u',v',w']$だとすると次のようにして$[u,v]$を計算する. \[ u = u'/w',\ \ v = v'/w' \]

応用先

ステレオカメラの画像を平行化する

2つのカメラの内一方を標準ステレオのカメラにすれば,非標準ステレオのカメラから標準への変換が得られる.

カメラ画像を没入型ディスプレイに表示する

HMDや大型のスクリーンにカメラで取得した画像を提示して,カメラの視点で見た風景を再現する場合, そのカメラをカメラ$a$,CG描画に使用する仮想カメラをカメラ$b$として考えると同じ問題になる.

カメラが回転して得られる画像を元の画像から生成する

回転前後のカメラをカメラ$a$およびカメラ$b$とすれば同じ問題になる. 1台のカメラが回転しただけなのでこの場合は内部パラメータは$K_a=K_b$として考える.

透視投影モデルから導出してみる

必要な式は2種類だけ

2つのカメラの透視投影変換は次の通り.

\[ \left[ \begin{array}{c} u_a\\ 1 \end{array} \right] \propto K_a\left[ R_a | t_a \right] \left[ \begin{array}{c} x\\ 1 \end{array} \right] \] \[ \left[ \begin{array}{c} u_b\\ 1 \end{array} \right] \propto K_b\left[ R_b | t_b \right] \left[ \begin{array}{c} x\\ 1 \end{array} \right] \]

2つのカメラが同一視点上にあるというのは.

\[ -R_a^{T}t_a = -R_b^{T}t_b \]

$x$を消して$u_a$から$u_b$の変換にする

\[ K_a^{-1}\left[ \begin{array}{c} u_a\\ 1 \end{array} \right] \propto R_a x + t_a \] \[ K_b^{-1}\left[ \begin{array}{c} u_b\\ 1 \end{array} \right] \propto R_b x + t_b \]

$t$と$R$を左辺に移項する.

\[ R_a^{-1}\left( K_a^{-1}\left[ \begin{array}{c} u_a\\ 1 \end{array} \right] - t_a\right) \propto x \] \[ R_b^{-1}\left( K_b^{-1}\left[ \begin{array}{c} u_b\\ 1 \end{array} \right] - t_b\right) \propto x \]

$x$を消去する.

\[ R_a^{-1}\left( K_a^{-1}\left[ \begin{array}{c} u_a\\ 1 \end{array} \right] - t_a\right) \propto R_b^{-1}\left( K_b^{-1}\left[ \begin{array}{c} u_b\\ 1 \end{array} \right] - t_b\right) \]

移項した変数を順に元に戻していく.

\[ R_a^{-1} K_a^{-1}\left[ \begin{array}{c} u_a\\ 1 \end{array} \right] - R_a^{-1} t_a \propto R_b^{-1} K_b^{-1}\left[ \begin{array}{c} u_b\\ 1 \end{array} \right] - R_b^{-1} t_b \]

$-R_a^{T}t_a = -R_b^{T}t_b$なので

\[ R_a^{-1} K_a^{-1}\left[ \begin{array}{c} u_a\\ 1 \end{array} \right] \propto R_b^{-1} K_b^{-1}\left[ \begin{array}{c} u_b\\ 1 \end{array} \right] \]

移項した変数を順に元に戻す.

\[ \left[ \begin{array}{c} u_a\\ 1 \end{array} \right] \propto K_a R_a R_b^{-1} K_b^{-1}\left[ \begin{array}{c} u_b\\ 1 \end{array} \right] \]

2019年8月7日水曜日

画像サイズから2の冪乗のテクスチャサイズを計算

最近はOpenGLでも任意のサイズのテクスチャを扱えるらしいけど,テクスチャを2の冪乗にするとパフォーマンス的に良いらしい. そこで任意の画像サイズから2の冪乗のテクスチャサイズを計算する関数をメモっておく.もっとシンプルに書けそうだけど一応正常に動作する.

inline unsigned texture_size(unsigned s){ return pow(2.0, unsigned(ceil(log(double(s))/log(2.0)))); }

2019年8月6日火曜日

Visual Studio + WSLでGUIプログラミング

Windows Subsystem for Linux (WSL)があればOpenCVなどのプログラミングで WindowsとLinuxの良いところ取りができるんじゃないかと思って試してみた. つまり,Visual StudioからLinux側のGDB Serverに接続してデバッグができ, Windows用のXサーバがあればLinux側で実行した結果のグラフィカルな表示はWindows側で行う. この機能を使えば,ライブラリはaptでインストールして,コーディングはVisual Studioを使って, ウィンドウ操作は使い慣れたWindows上でやる. でもファイルやスクリプトの操作は今更覚えたくないコマンドプロンプトじゃなくてLinuxのターミナルで出来るはず.

Ubuntuの開発環境の導入

Ubuntuの開発環境の導入手順は次のとおり.

  1. WSLの有効化
  2. Ubuntu 18.04 LTS on WSLの導入
  3. GDB Serverのインストール

WSLの有効化

WSLの導入方法は2019/5/30現在,ググれば大量に見つかる. わかりやすい記事は例えばここ. ただ一点注意するのは,WSLは2017年秋頃にベータ版から正式版に変わっったので導入方法が少し変わった. ベータ版の時点の解説記事を間違って読まないように.Windows Insider Programがどうこう書いてあれば古い記事と思えばいい.

具体的な導入方法は簡単で,「Windows の機能の有効化または無効化」で「Windows Subsystem for Linux」のチェックをONにするか, PowerShellで以下のコマンドを打つだけらしい.

PS> Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

Ubuntuの導入

Ubuntuの導入はクソ簡単になってる.Windows StoreでUbuntu 18.04を探してインストールのボタンをクリックするだけ. ただ,これも後々自動化するかもしれないのでコマンドをメモっておく. Ubuntuを起動したら最初だけアカウント名とパスワードを設定しないといけない.

PS> Invoke-WebRequest -Uri https://aka.ms/wsl-ubuntu-1804 -OutFile ~/Ubuntu.appx -UseBasicParsing
PS> Add-AppxPackage -Path ~/Ubuntu.appx
PS> Ubuntu.exe

GDB Serverの導入

Ubuntuがインストールされてしまえば標準的なパッケージのインストールはaptつかって簡単にできる.

sudo apt update
sudo apt install openssh-server build-essential gdb gdbserver zip

SSHサーバの起動

SSHサーバを起動するには設定ファイルをsshd_configをエディタで開き...

sudo nano /etc/ssh/sshd_config

パスワードの設定を許可するために次の行がコメントアウトされていれば,コメント記号を外し...

PasswordAuthentication yes

サーバを起動する.(WSLでこのサーバを自動的に起動する方法が分からない.)

sudo service ssh start

参考

X Serverの導入

Windows用のXサーバをインストールするとWSLでもGUIが利用できる. ここではVcXsrvというサーバをインストールし

VcXsrvの導入

環境変数DISPLAYの設定

echo 'export DISPLAY=localhost:0.0' >> ~/.bashrc
source ~/.bashrc

日本語化

sudo apt install fonts-takao

Visual StudioをGDB Serverに接続する

(未完成)

参考

2019年7月24日水曜日

ガウス関数曲線のベクトル画像

任意のガウス関数(正規分布)の曲線は,標準正規分布を水平,垂直方向に拡大・縮小することで得られる. パワーポイント等の発表資料に利用でしやすいようにPython(Jupyter Notebook)でベクトル画像を作成したので掲載しておく.

ここからSVG画像をダウンロードできる.
https://github.com/r168xr169/plot_normal_curve

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# 平均と標準偏差と定義域
mu = 0.0
sigma = 1.0
x_min = -4
x_max = 4

# ガウス関数のデータ作成
x = np.arange(x_min, x_max, 0.001)
y = norm.pdf(x)

# build the plot
fig, ax = plt.subplots(figsize=(9,6))
plt.style.use('fivethirtyeight')
ax.plot(x,y, color='black')

# グリッドと軸非表示
plt.axis('off')
ax.grid(False)

# 平均,標準偏差の線
plt.hlines(norm.pdf(sigma), 0, 1, "red", linestyles='dashed')
plt.vlines(0, 0, norm.pdf(mu), "blue", linestyles='dashed')

# x軸
plt.hlines(0, x_min, x_max, "green")

# SVG画像の保存
plt.savefig('normal_curve.svg',format="svg",dpi=600,bbox_inches='tight',transparent=True)
plt.show()

2019年6月21日金曜日

統計学のオンライン教材

講義で使える統計素材」シリーズ. 今回は,ネット上に転がっている統計学のオンライン教材を集めたリンク集. これだけ充実していればもう教科書は要らないようにも思う.

Web教材

全人類が分かる統計学 tokei.net

https://to-kei.net/basic/history/

統計の歴史からベイズ統計まで非常に詳しく分かりやすいページが揃っている.

統計Web ― 統計学の時間

https://bellcurve.jp/statistics/course/

学習項目ごとに具体的な数値を用いた練習問題までついているので勉強教材として素晴らしい. 大学での基礎統計を勉強したいならStep1の「9. 確率と期待値」から始めると丁度良い.

高校数学の備忘録 ― 統計学と誤差論

https://physnotes.jp/stat/

基礎統計で登場する話が一通り掲載されているのでこれだけで1冊の教科書くらいの価値がある. Rのソースコードが掲載されているページもあり,実際にプログラムを動かして実感できる. 掲載されている確率分布や検定の種類が豊富.二元配置分散分析については触れられていない. 機械学習法に関する説明も最後にある.

データ科学便覧 ― 理論関連事項

https://data-science.gr.jp/theory.html

基礎統計で登場する話が一通り掲載されているのでこれだけで1冊の教科書くらいの価値がある. Rのソースコードが掲載されているページもあり,実際にプログラムを動かして実感できる. 掲載されている確率分布や検定の種類が豊富.二元配置分散分析については触れられていない. 機械学習法に関する説明も最後にある.

統計検定を理解せずに使っている人のためにI~III

統計の基礎から出発して最短経路で検定の理解へと進む. 細かい証明よりも直感的でかつ正しい理解を目的としているので,統計初心者は最初にこれを読むと良いかも. 2群の検定.パラメトリックとノンパラメトリックの直感的理解を解説.ノンパラメトリックの例としてウィルコクソンの順位和検定の解説は分かりやすい. 1要因および2要因の分散分析もしっかり説明されている.

動画教材

予備校のノリで学ぶ「大学の数学・物理」― 確率統計

https://www.youtube.com/watch?v=Zz1sgYxrA-k&list=PLDJfzGjtVLHmx7qMP410-9gx0weC9d90X

分かりやすい動画教材. 最小二乗法から区間推定まで,各動画で前提となる知識が最小限で済むように分かりやすく解説されている. 推定・検定入門①から⑥だけを見てもある程度理解できる. ただし,「検定」という単語がタイトルにあるが,本記事投稿時点では区間推定までしかアップロードされておらず,検定に関しては触れられていない. この再生リストには,ベイジアンネットワークまで含まれているが,基礎統計の学習ではスキップしてもよい.

ざっくりと学ぶ統計学

https://www.youtube.com/watch?v=U8W_ttdEF-o&list=PLivuoH_rdyRDL2riiOvSyjvnd-uoB-NN_

合成音声が単調なので少し眠くなるけど,一番重要な部分がコンパクトにまとまっている. No.2から見始めても良い. ちょっとスピードが速い場合は再生スピードを遅くして聞くと良い.

その他のオンライン資料

こんにちは統計学

https://www.m-sugaya.jp/python/?fbclid=IwAR3ZAvOARJ5gOvCPy6uyLj4yhRtXDKPa4Tgq7YLAJCyPZETtLtedYVIPwtA

「PythonをCGIに使った」と書かれているけど,別にPythonのコードがある訳ではなく,データを貼り付ければ解析した結果がCGIの結果として得られるページ.

主観評価の理論と実際

https://www.jstage.jst.go.jp/article/itej1954/31/5/31_5_369/_article/-char/ja/

一対比較法のケース3とケース5の説明が書かれている. 最小二乗法で心理量の母平均を推定する理論については書かれておらず計算方法の話にスキップしている. その代わり適合度の検定については詳しく書かれている.

官能評価におけるデータ解析の基礎

http://www.mac.or.jp/mail/120701/02.shtml

官能評価と二項検定の解説.

2019年1月8日火曜日

中学生でもできる論文中の表現チェック

論文の書き方を解説する書物やWebページは世の中に溢れているけど,正直言ってどれも実際の教育の現場ではあまり役に立たない. というのも,その文章ををしっかり読み解き,理解できるだけの読解力,想像力,経験値がある人なら,ある程度作文力もあるはずだから. 論文執筆は難しい.自分にとっても未だに難しい.そんな難しい文章作成のコツが,文章を読むだけで理解できる,なんて甘い話ではない.

そのような本を読むんでも抽象的すぎてピンとこないという人向けに,中学生でもできるはずの簡単なものだけを厳選して, 実際の大学院生が論文中に書いた文(一部改変)を例を挙げ,どのように修正したかを紹介する.

「の」を3つ以上連続させない

× 物体の形状の大きさの違いの推定誤差への影響を・・・
○ 物体の大きさに対する推定誤差の変化を・・・
(2013年12月,M2)

「の」でつながる修飾語句が多いと,各修飾語句がどの単語を修飾しているのか,定まらないという曖昧性が生じるので分かりにくくなる.

無駄な「~について」「~を行う」に注意

× 実験では,提案手法による○○推定について,14回の試行を行った.
○ 実験では,提案手法による○○推定を14回の試行した.
(2013年12月,M2)

「ついて」とか「として」を付けてしまうとぼやかした表現になってしまう. 上の場合,「○○推定について~する」と書くと「○○推定についての△△を~する」のように, 「~する」直接の対象である△△が省略されていることになり,結局何をしたのか分からなくなる.

無駄な「~しまう」「~という」を使わない

× 美観を損ねてしまうという問題がある.
× 美観を損ねてしまう問題がある.
○ 美観を損ねる問題がある.
(2017年7月,M2)

「しまう」はネガティブな印象を与える情緒的な言葉.上の文では「問題」という単語がすでにネガティブなので, そこにさらにネガティブな単語を足しても,結局具体的に何が問題なのかの理解が深まる訳でない. 論文は,本来第三者を納得させるものなので,なるべく第三者が同意しない可能性のある著者の主観(気持ち)を排除する.

「~という」は,「として」とか「ついて」と同じで,

主語と述語が噛み合うようにする

× 本論文では,○○を開発する.
○ 本研究では,○○を開発する.

論文の中で開発はできない.

同じ意味の言葉が重複しないようにする

× 提案手法では,各フレーム毎に○○を検出する.
○ 提案手法では,フレーム毎に○○を検出する.

「Mt. 富士山山」や「馬から落馬」みたいな表現.

省略されている主語を一致させる

× ○○の精度が向上することでシステムを安定化させる.
○ ○○精度を改善することでシステムを安定化させる.
(2019年3月,B4)

「向上する」の主語は「精度」で,この「向上する」は自動詞. 「安定化させる」の主語は「開発者」とか「我々」であり,「システム」は目的語なので,「安定化させる」は他動詞. 「○○することで△△する」の○○と△△の主語が違うからおかしくなる.

まとめ

  • 修正後文字数が減っている場合が多いことが分かる.情報量を減らさずに文字数を減らすように試行錯誤すると自然と良くなる事が多い.
  • 大学で習うような高度な知識は全く必要ない簡単なことばかりだと分かる.自分を含めて殆どの人が,研究室配属されて個別に添削指導を受けるまではこの程度のことができなかった. いかに日本の教育が間違っているかということじゃないだろうか.
  • ここに書いたのは表面上の誰でもできるチェックばかりだけど,構成や内容にかかわることは,もう少し抽象的な考えが必要で,やっぱり伝えるのは難しい.
  • まだまだ,こうした例はあると思う.引き続き良い例が見つかれば追記していく.

2019年1月1日火曜日

Visual Studioでのリンカエラー解決方法

Visual C++でプログラムをビルドしていて次のようなエラーはリンカが出力したエラーメッセージ. これまでは,このエラーが出ると,もうどうしていいか分からなくて人生終わりみたい感じてしまってたけど,最近ようやく対処法が分かってきたのでまとめとく.

Test.obj : error LNK2001: 外部シンボル ""__declspec(dllimport) public: __cdecl google::LogMessageVoidify::LogMessageVoidify(void)" (__imp_??0LogMessageVoidify@google@@QEAA@XZ)" は未解決です。

ちなみにこのエラーはglogというGoogleが開発したログ出力用ライブラリを用いたC++のプロジェクトをビルドする際に表示されたエラー.

エラーの意味を確認する

error LNK2001をググると例えばこんな文章が出てくる. この文章を読んで意味が分かる人は,たぶんこの記事を読む必要はない. 正確に理解しようとすると話が長く難しくなって意味不明になるので,何がエラーの原因なのかを推測するときの自分の連想のしかたを,ざっくりとメモ.

  1. 「Test.obj」ってのは,たぶんtest.cppとかtest.cとかをコンパイルしたときにできる中間生成ファイル. リンカのエラーだからコンパイルは通ってるってことで,たぶんTest.cppなどのソースコードにエラーはない. DLLが見つからないというエラーなら実行時に出るのでそれでもない.
  2. 「error LNK2001」ってのは,test.cppとかtest.cをコンパイルして出来た中間生成ファイルであるobjファイル(Test.obj)から 呼び出されている関数が見つからないというエラー.
  3. 「外部シンボル」は要するに関数名のこと. それがTest.objから呼び出されてるけど,リンカに入力されたlibファイルなどの中にはそんな関数無いという意味.
  4. 「__declspec(dllimport) public: __cdecl google::LogMessageVoidify::LogMessageVoidify(void)」はその無いと言ってる関数の名前. 長すぎて意味不明だけど「LogMessageVoidify::LogMessageVoidify」という文字があるからLogMessageVoidifyクラスの コンストラクタLogMessageVoidify()が見つからないっていってるんだろうなと想像する. ここで「__declspec(dllimport)」はDLL内の関数という意味.ただし,DLL化された関数でもコンパイル時にエラーにならないように, DLLとlibはセットになっていて,libにはヘッダファイル内の関数プロトタイプのような,入出力の変数の型と関数名,名前空間だけが書かれている.
  5. つまり,たぶん何かのlibファイルが足らないんだろうから「LogMessageVoidify lib」などとググってみて, 必要なlibファイルを探す.今回の場合は,glogというライブラリglog.libが足らないということが分かった.

ちなみに,この「?」や「@」を含む関数名へんてこなの表記は名前の装飾と呼ばれていおりundnameコマンドで元の関数の表記に戻すことができるらしい.この表記でも元の関数が想像できるのでundnameは使ったことがない.

ライブラリの指定の有無とパスを確認する

ここで次の一手はlibファイル(ここではglog.lib)を正しくリンカに渡せているのか確認したいところだけど, glog.libというファイル自体が見つからないということだったらこのようなエラーがでるはずなので今回は違う. そもそもglog.libをリンクせよという指定が正しくできていないか,そのような指定はできているがglog.libの中にLogMessageVoidifyという関数が見つからないのかどちらかということになる. まずは,glog.libをリンクしろという指定が正しくないのではと疑ってみる.

LINK : fatal error LNK1104: ファイル 'glog.lib' を開くことができません。

#pragmaを使う

そもそも必要なライブラリがglog.libであるということをリンカに伝えられていないということだから, ソースコード内にこのようなマクロを追加すると解消されるかもしれない.

#pragma comment(lib,"glog.lib")

ソースコードは直接触りたくないなら次のVisual Studioの設定を確かめてみる.

Visual Studioの設定を確認する

  1. 「プロジェクト」メニューをクリック
  2. プロパティをクリック
  3. 左の「構成プロパティ」内の「リンカー」を選択
  4. 「入力」項目を選択
  5. 「追加の依存ファイル」内にglog.libがないか確認

ここにライブラリ名glog.libを追加すればたいてい問題は解決するか, 上記のファイルが見つからないというエラーに変わるので「リンカー」→「全般」→「追加のライブラリディレクトリ」にlibファイルのあるディレクトリへのパスを設定すればいい.

msbuildコマンドでリンカに渡されるオプションを確認する

Visual Studioはライブラリの設定手段が複数ありややこしい.結局,どのライブラリがリンカに渡されたのか結果を知りたいところ. そんなときに便利なのは,コマンドラインからプロジェクトをビルドする方法(Visual StudioのGUIから確認する方法があるかもしれないけど知らない). 例えば,ALL_BUILD.vcxprojというプロジェクトをビルドする場合, このファイルがあるフォルダまでカレントディレクトリを移動して, 次のコマンドをたたけばいい.

msbuild ALL_BUILD.vcxproj /p:Configuration=Release

このコマンドは,すでにVisual StudioのGUIでプロジェクトを作成していても問題なく実行できる. そうすると次のようにリンカに渡されるライブラリ名(*.lib)やオブジェクトファイル名(*.obj)が確認できる.

Link:
  C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x64\link.exe /ERRORREPORT:QUEUE /OUT:"C:\Library\ceres-solver\bld\bin\Release\simple_bundle_adjuster.exe" /INCREMENTAL:NO /NOLOGO ..\lib\Release\ceres.lib C:\Library\glog\bld\Release\glog.lib dbghelp.lib c:\library\suitesparse\inst\lib64\lapack_blas_windows\liblapack.lib c:\library\suitesparse\inst\lib64\lapack_blas_windows\libblas.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embed /PDB:"C:/Library/ceres-solver/bld/bin/Release/simple_bundle_adjuster.pdb" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"C:/Library/ceres-solver/bld/lib/Release/simple_bundle_adjuster.lib" /machine:x64 /ignore:4049 simple_bundle_adjuster.dir\Release\simple_bundle_adjuster.obj

この場合では,C:\Library\glog\bld\Release\glog.libがリンカの入力に指定されていることが分かる. kernel32.libのような基本的なライブラリもここに表示されているということは, ここになければ目的のlibファイルがリンカに渡されていないということになる(たぶん).

dumpbinでlibファイルの中身を確認する

これまでの方法で確かにlibファイルは存在しリンカの引数に正しく渡されているのに, それでも最初のエラーが消えない場合は,そもそもそのlibファイルの中に目的の関数(今回でいえばLogMessageVoidify::LogMessageVoidify)が入っていない可能性もある. libファイルの中身を確認するにはlibファイルがある場所にカレントディレクトリを移して次のコマンドを打てばいい.

dumpbin /LINKERMEMBER glog.lib > glog.txt

これで次のような内容のテキストファイル(glog.txt)が生成される.

Microsoft (R) COFF/PE Dumper Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\Library\boost_1_60_0\lib\libboost_atomic-vc140-mt-1_60.lib

File Type: LIBRARY

Archive member name at 8: /               
568FB871 time/date Fri Jan  8 22:24:01 2016
         uid
         gid
       0 mode
     53D size
correct header end

    16 public symbols

      B74 ??0scoped_lock@lockpool@detail@atomics@boost@@QAE@PDX@Z
      B74 ??1scoped_lock@lockpool@detail@atomics@boost@@QAE@XZ
      B74 ?clear@?$msvc_x86_operations@EU?$operations@$00$0A@@detail@atomics@boost@@@detail@atomics@boost@@SAXACEW4memory_order@4@@Z
      B74 ?exchange@?$operations@$00$0A@@detail@atomics@boost@@SAEACEEW4memory_order@4@@Z

(つづく)

このファイル内に目的の関数名が含まれるかどうか検索すればいい. ただし,関数名は同じでも引数などが異なる場合に注意.

ソースコード確認する

上記の方法でlibファイルを中身を覗いてもその中に目的の関数がなかった場合. ライブラリ自体のコンパイル時にその関数がエクスポート(外から参照できる形で定義)されていない可能性がある. その場合はもうライブラリのソースを修正するしかない(?).

今回glogを例に上げているが実際にglogではインライン化された関数がエクスポートされていなかった. logging.hには以下のコードが書かれており,インライン関数はインライン展開された場合,__declspec(dllimport)が指定されていてもlibファイル内に関数名が残らない場合があるらしい.

#   define GOOGLE_GLOG_DLL_DECL  __declspec(dllimport)
class GOOGLE_GLOG_DLL_DECL LogMessageVoidify {
 public:
  LogMessageVoidify() { }

この関数は少し特別であえて何もしない関数が定義されている.何もしないけど,コンパイラには認識させてプログラムの行番号やファイル名などをマクロを正しく表示させたいという意図なのかな. そこでヘッダ内で関数定義を完結させずに,ヘッダ内はプロトタイプのみにして,cppファイル内に何もしない関数を定義する.

.hファイル内の定義から「{}」を削除して,関数プロトタイプ宣言のみにする.

class GOOGLE_GLOG_DLL_DECL LogMessageVoidify {
 public:
  LogMessageVoidify();

.cppファイルには関数本体を記述する.

LogMessageVoidify::LogMessageVoidify(){}

このソースコードの修正でようやくビルド時のリンカエラーが消えた.

まとめ

まとめるとリンカエラーに対しては次の手順でエラーの原因を確認したらいい.

  1. エラーの意味を確認する
  2. libファイル指定の有無を確認
  3. ライブラリファイルへのパスを確認
  4. libファイル内の関数名を確認
  5. libファイルのソースを確認する

参考