重心の計算方法

重心とは、重さの中心で、重心の位置で、力がつり合います。

 

重心の計算方法は、言葉で表すと

$$重心=\frac{(重さ\times位置)の合計}{重さの合計}$$

となります。

 

具体的な計算例を示すと、

のデータに関して、重心を計算すると、

$$重心=\frac{2\times0+5\times1+13\times2+15\times3+5\times4}{2+5+13+15+5}
=2.4$$

 

となります。

 

さらに二次元的な座標においても、各座標軸に関して、それぞれ計算を行います。

こちらも具体的に、以下のようなデータの場合、

$$重心のx座標=\frac{3\times0+10\times1+7\times2+5\times0+20\times1+7\times2+2\times0+9\times1+3\times2}{3+10+7+5+20+7+2+9+3}$$

$$= 1.1$$

$$重心のy座標=\frac{3\times0+5\times1+2\times2+10\times0+20\times1+9\times2+7\times0+7\times1+3\times2}{3+5+2+10+20+9+7+7+3}$$

$$= 0.9$$

となります。(分母の重さの合計は、計算の順番が分かりやすいように、順番を入れ替えています。)

 

画像処理において重心を計算する場合は、重さの値に画像の輝度値を用いますが、二値化した画像に対して重心の計算を行い、重さを考慮しない場合もありますが、この場合は、重心の座標は、しきい値以上となる画素の座標の平均と一致します。

ハフ変換

ハフ変換そのものは座標の変換処理なのですが、画像処理では、ハフ変換を用いて画像中の直線部分を抽出するのに用いられます。

ハフ変換と言うだけで、あんに直線検出を指している事が多くあります。

また、ハフ変換を拡張して、円の検出に用いられる場合もあります。

ハフ変換で出来ること

画像の中から、直線が途切れていても、直線らしき部分を抽出したり、前処理でエッジ抽出を行い、輪郭部分を検出する事もできます。

(元画像)

(直線検出)

(元画像)

(輪郭検出)

直線検出のしくみ

まず初めに直線として検出したい場所を二値化して抽出します。

被写体の輪郭を検出したい場合は、SobelCannyなどの前処理を行います。

二値化された座標(XY座標)に関して、X座標をいくつかに分割し、Y軸方向に二値化された座標をカウントします。

このカウントを点を原点に基点に回転させながら、繰り返しカウントを行います。

すると、点が直線的に並んだ時にカウント数が最大となります。

直線なので、逆向き(回転角度が180度反対)の時もカウント数が最大となります。

このカウント数を横軸を回転角度、縦軸をX座標にして、二次元的に表示すると、下図のようになり、カウント値がピークとなるXとθの値から、直線を検出することができます。

ハフ変換のアルゴリズム

点を回転して特定方向に積算することで、直線を見つける事もできるのですが、ハフ変換では、各点を通る直線を回転させ、直線が重なりあう場所をみつける事で、直線を検出します。

点(x, y)を通る線を、下図のようにρθで表すと

$$\rho=x cos\theta+y sin\theta$$

となります。

これは、(x, y)座標が決まっていて、θを与えると、ρが計算できる事になります。

点(x,y)を通る直線をθを0~360°の範囲で刻むと下図のようになります。

この直線をρとθで表すと、下図のようにsinカーブとなります。

 

この処理を各点に関して行うと、点が直線的に並んでいる部分では特定のρとθに集中します。

このρとθの値が集中している点を抽出し、ρとθの値から直線を検出します。

実際のハフ変換

ハフ変換の角度は0~360°で計算すると、必ず180°離れた2か所でρとθが集中するので、0~180°までを計算します。

実際のハフ変換では、エッジ上のxy座標から、θの値を例えば1°おきに0~180°までに対応したρの値を計算し、ρの値の分解能を例えば1などに落として、ρーθの座標系へ投票し、投票した値のカウント数が高い部分が直線となる部分のρ、θとなります。

OpenCV(Python)のサンプルプログラム

import cv2
import math
import numpy as np

# 画像読込
src = cv2.imread('keyboard.jpg', cv2.IMREAD_GRAYSCALE);
# 二値化
_, src = cv2.threshold(src, 200, 255, cv2.THRESH_BINARY)

cv2.imshow("edge image", src)

# ハフ変換
lines = cv2.HoughLines(src, 3, np.pi / 180, 400)

# 結果表示用の画像を作成
dst = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR);

# 直線を描画
line_length = 1000
for line in lines:
    rho = line[0][0]
    theta = line[0][1]
    a = math.cos(theta)
    b = math.sin(theta)
    x0 = a * rho
    y0 = b * rho
    cv2.line(
        dst, 
        (int(x0 - line_length * b), int(y0 + line_length * a)), 
        (int(x0 + line_length * b), int(y0 - line_length * a)), 
        (0, 0, 255), thickness=2, lineType=cv2.LINE_4 )

cv2.imshow("result", dst)

cv2.waitKey()

(処理前の画像)

(処理後画像)

注意事項

ハフ変換を行うと、直線らしい部分が抽出できますが、この直線の位置の精度を高めようと、ハフ変換時のθとρの分解能を高め過ぎると、直線検出の安定性が悪くなります。

そのため、直線の位置精度を高めるには、ハフ変換で直線付近の座標を抽出し、その座標から回帰直線などを求めるようにします。

画像の拡大

例えば、下図のように2x2画素の画像を4x4の画像に拡大する場合、アフィン変換を使えばいいんでしょ!と、安易に考えていると、思わぬ落とし穴があったりもします。

大事なポイントとして、

●画像の座標の原点は左上の画素の中心が原点(0.0、0.0)となる。(例外もあります)

●アフィン変換の拡大縮小は原点を基準として拡大縮小される。

 

となります。

これを気にせず、ただ、アフィン変換で画像を2倍に拡大すると、左上の画素の中心を基点に画像が拡大されます。

これで、一見良さそうにも感じるのですが、拡大後の画像において、画素の中心が原点であることから、4x4画素の領域は下図の四角で示した領域であり、画像全体が左上に0.5画素ズレた状態になっていまいます。

 

 

アフィン変換で画像を拡大する時の変換前と変換後の状態は、以下のようになるのが正解です。

 

この変換をアフィン変換で実現するには以下のように行います。

 

変換前の状態

 

①画像全体を右下に(+0.5,+0.5)画素移動
$$\begin{pmatrix} 1 & 0 & 0.5 \\ 0 & 1 & 0.5 \\ 0 & 0 & 1 \end{pmatrix}$$
 

②画像を2倍に拡大

$$\begin{pmatrix} 2 & 0 & 0 \\ 0 & 2 & 0 \\ 0 & 0 & 1 \end{pmatrix}$$
 

③画像全体を左上に(-0.5,-0.5)画素移動

$$\begin{pmatrix} 1 & 0 & -0.5 \\ 0 & 1 & -0.5 \\ 0 & 0 & 1 \end{pmatrix}$$

 

となります。

この一連の変換をアフィン変換行列であらわすと

$$\begin{pmatrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{pmatrix}=\begin{pmatrix} 1 & 0 & -0.5 \\ 0 & 1 & -0.5 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} 2 & 0 & 0 \\ 0 & 2 & 0 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} 1 & 0 & 0.5 \\ 0 & 1 & 0.5 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}\\ \\ $$

$$\begin{pmatrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{pmatrix}=\begin{pmatrix} 2 & 0 & 0.5 \\ 0 & 2 & 0.5 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}\\ \\ $$

 

となり、単に拡大のアフィン変換行列だけを掛ければOKでは無いことが分かります。

 

ちなみに、C#で2x2画素の画像をPictureBoxのSizeModeプロパティをZoomにして、ImageプロパティにBitmapを設定すると、このようになります。

 

なんとなく、画像が左上にズレているようで、なんか怪しい!!

 

【関連記事】

【C#】画像の座標系

アフィン変換(平行移動、拡大縮小、回転、スキュー行列)

画素の補間(Nearest neighbor,Bilinear,Bicubic)の計算方法

画像の回転

 

画像処理のためのC#へ戻る

移動平均フィルタ VS ガウシアンフィルタ

いろいろと検索していたら、2008年の国家試験に以下のような問題があったらしい。


画像が最も平滑化される空間フィルタはどれか。

ただし、数字は重み係数を示す。

(参考)http://www.clg.niigata-u.ac.jp/~lee/jyugyou/img_processing/medical_image_processing_03_press.pdf


 

この問題、

1.ラプラシアンフィルタ

2.3x3の移動平均フィルタ

3.3x3のガウシアンフィルタ

4.5x5の移動平均フィルタ

5.5x5のσが小さめのガウシアンフィルタ

なので、正解は4番だとは思いますが、そもそも平滑化ってなに?

というところに引っかかった。。

 平滑化 = 画像をボヤかす  なら、4番が正解

 平滑化 = ノイズを除去する なら、3番か5番はどうだろう??

 

そもそも「ガウシアンフィルタは中心の画素からの距離に応じてσを小さくすると、平滑化効果が弱まり、σを大きくすると平滑化効果が強くなる」というのが教科書的な説明で、昔の私もそのように書いちゃってますね。。

ガウシアンフィルタの処理アルゴリズムとその効果

 

ただ、ガウシアンフィルタには ローパスフィルタの特性もある というのも大事な特徴だと思います。

 

試しにガウシアンフィルタを画像にかけてみると、

 

オリジナル画像
3×3移動平均フィルタ 3×3ガウシアンフィルタ

 

これだと、差が分かりにくいので、やや意図的なデータではあるのですが、1次元データに対して、1/3, 1/3, 1/3 という移動平均と 1/4, 2/4, 1/4  のガウシアンフィルタとで比べると、

 

オリジナルデータ
移動平均フィルタ
ガウシアンフィルタ

 

となって、ガウシアンフィルタの方がノイズ除去ができています。

上記データはあまりにも意図的なので、いまいち納得できないような部分もあるかと思いますが、連続する3点のデータにおいて、ノイズの成分がどのように加わっているのか?を考えると、理論値からのズレは

+-+

ー+-

++-

ー++

+--

ーー+

のように「+側のノイズが2個、-側のノイズ1個」もしくは「+側のノイズが1個、-側のノイズ2個」となるので、平均すると、+1/3 もしくは -1/3 余ってしまいます。

ガウシアンフィルタでは、少なくとも

+-+

ー+-

のパターンでは、+1/4-2/4+1/4 もしくは ー1/4+2/4ー1/4 となるので、誤差が打ち消しあってくれます。

その他の

++-

ー++

+--

ーー+

パターンでは+1/2 もしくは -1/2 となるので、移動平均よりもダメ!と思ったりもするのですが、この場合は5点以上のガウシアンフィルタを用いると誤差が消えてくれる可能性が高くなります。

 

...と、何が言いたかったかというと、ガウシアンフィルタでは、ローパスフィルタの効果があるため、高周波のノイズには移動平均フィルタよりガウシアンフィルタを用いた方が効果的な場合があります。

私の本職では検査装置などで使われる工業用のカメラを用いる事が多いのですが、工業用のカメラで撮影した画像のノイズと言えば、まずは高周波なノイズなので、ノイズを消したい場合は、移動平均フィルタよりも、まずはガウシアンフィルタを検討するようにしています。

ガウシアンフィルタの方が画像がボケないですし。

 

画像処理アルゴリズムへ戻る

Canny edge detectionの処理アルゴリズム

Canny edge detectionは画像の輪郭部分を抽出するのに、よく用いられるのですが、詳細なアルゴリズムを理解しないまま使われている事も多いのではないでしょうか?(それ、私)

特に、ヒステリシスしきい値の部分などは、詳細な説明も少ないので、今回、まとめてみたいと思います。

 

まずは処理結果から。

 

処理前画像 Canny edge detection処理後

 

大まかな流れとしては

1.ノイズ除去

ガウシアンフィルタなどでノイズを除去します

 

2.輪郭抽出

ソーベルフィルタで輪郭を抽出します

 

3.非極大抑制

エッジの強さが極大となる部分以外を除外します。

詳細は後述

 

4.ヒステリシスしきい値処理

詳細は後述

 

完成!!

 

以上の4ステップで成り立っていますが、非極大抑制とヒステリシスしきい値処理がちょっとわかりづらいので、詳細は以下の通りです。

 

非極大抑制

ソーベルフィルタ後のエッジの強さを3D表示すると、こんな感じ↓になります。

 

この山の尾根のみを抽出するのが、非極大抑制となります。

 

具体的には、まず、ソーベルフィルタでエッジの強さを求めて、エッジの角度を求めます。

横方向

縦方向

エッジの強さ

エッジの角度(実際にはエッジの線に直行する向きが求まります)

 

エッジの向き(エッジの法線方向)が求まったら、

0°(-22.5°~22.5°)

45°(22.5°~47.5°)

90°(47.5°~112.5°)

135°(112.5°~157.5°)

の4つに分類します。

 

エッジの法線方向の3画素のエッジの強さを用いて、中央のエッジの強さが、残す2つの良さよりも大きければ、その部分が極大点ということになり、中央のエッジの強さが最大とならない部分を除外する処理が非極大抑制となります。

エッジの強さ 非極大抑制

 

この処理によりエッジの部分の細線化が行われます。

 

ヒステリシスしきい値処理

ヒステリシスしきい値の処理については、Webで探してもあまりわかりやすいのが無いような気がしますが、以下の通りです。

 

まず、非極大抑制処理で細線化された画像に対して、2つのしきい値を用いて二値化します。

2つのしきい値は、なんとなくエッジっぽい部分(しきい値「小」)と、確実にエッジな部分(しきい値「大」)となるしきい値を設定します。

 

非極大抑制処理画像

 

しきい値「小」 しきい値「大」

 

 

この2つの画像を用いて、しきい値「小」の中から、しきい値「大」につながっている部分のみを残します。

分かりやすいように、しきい値「小」の画像にしきい値「大」のエッジを赤くして重ね合わせて表示すると↓

 

 

この赤い線につながっていない部分を除外します。

 

この処理がまさにCanny edge detectionとなります。

 

(参考)

OpenCV 3.0.0-dev documentation Canny Edge Detection

 

関連記事

【OpenCV-Python】Canny(Canny edge detection)

 

画像処理アルゴリズムへ戻る

カラー光切断法を画像機器展2014で公開

以前、公開しましたカラー光切断法ですが、今回は回転テーブルに対応して2014.12.3~12.5にパシフィコ横浜で開催されている国際画像機器展で展示しています。

 

この回転対応は、まだまだ参考出品状態なのですが、ご興味のある方は来場頂けると幸いです。

 

こちらは比較的撮影しやすい雪だるまの置き物

 

こちらは形状がゴツゴツしていて撮影が難しい↓

 

この手のワークは何回か撮影の向きを変えて合成したいところですが、まだ、そこまでの対応はできていません。

 

バイラテラルフィルタ

ガウシアンフィルタなどのフィルタでは、ノイズをできるだけ除去しようとすると、輪郭もボケてしまうという欠点がありました。
この欠点を解決しようとした処理アルゴリズムがバイラテラルフィルタ(bilateral filter)です。

 

バイラテラルフィルタは処理前の画像データの配列をf(i, j)、処理後の画像データの配列をg(i, j)とすると

 

 

となります。

 

ただし、がカーネルのサイズ、σがガウシアンフィルタを制御、σが輝度差を制御しています。

と言われても、何だか式が難しくて良く分かりません。

 

でも、分母分子に出てくる最初のexpの部分はガウシアンフィルタで見たことがあるな~

という事に気が付けば、突破口が開けます。

 

2つ目のexpの部分が良く分からないので、とりあえず取っちゃってみて、

 

 

とすると、分母の部分がガウシアンフィルタと少し違うけど、Σの範囲が-W~Wなので、(2W+1)×(2W+1)のカーネルサイズのガウシアンフィルタになっています。

 

結局、分母はカーネルの値の合計なので、やっぱりガウシアンフィルタそのものだという事に気が付きます。

 

ここで、ガウシアンフィルタを使って、どうやれば輪郭をぼやかさず、ノイズだけを除去できるか?を考えると、カーネルの中心の輝度値と差の少ないところだけをガウシアンフィルタで平滑化すればいいのではないか?という発想が浮かびます。

 

例えば次のような画像において、

 

四角で囲まれた部分を拡大し注目すると、5x5のガウシアンフィルタの場合、中心の輝度値に近い部分を重み「1」、輝度差が大きい部分は重みが「0」になるようにして、

 

 

一般的な5x5のガウシアンフィルタの係数↓、

 

 

に重みをかけると、それぞれのカーネルの値は

 

カーネルの合計の169で割る カーネルの合計の209で割る

 

となります。

このようにして、場所、場所のカーネルの値を画像に合わせて変えて行くと、輪郭を残しつつノイズだけを除去できそうな感じがします。

 

しかし、輪郭付近の重みは「1」にするべきか?「0」にするべきか?少し悩みます。

そこで、カーネルの中心の輝度値との差に基づいて、重みを「0.3」や「0.8」のようなグレーゾーンを設けるために、輝度差を横軸にした正規分布を用いてみます。

 

正規分布のグラフはこんな感じ↓

 

になっていて、中心の0付近ほど値が大きく、外側(+方向、-方向)へ行くに従って値が小さくなります。

この性質を使ってカーネルの中心の輝度値との差「f(i, j)- f(i + m, j + n) 」を横軸にとった正規分布の式は

 

 

となり、これを重みに使うと、「1」「0」だった重みが、輝度差が小さいと重みが大きく、輝度差が大きいと重みが小さくなるように、なだらかに変化します。

 

この正規分布を重みとしたのが、まさにバイラテラルフィルタなのです。

つまりバイラテラルフィルタは

 

正規分布の重み付きガウシアンフィルタ

 

なのです。

 

ここで、最初に示したバイラテラルフィルタの式を見てみると、

分子は(2W+1)×(2W+1)サイズのカーネルの範囲内の輝度値に、ガウシアンフィルタの係数をかけ、さらにカーネルの中心との輝度差を用いた正規分布の値を掛け合わせた値、

分母はカーネルの合計値で、カーネルの合計値が1になるように調整しています。

 

Wはカーネルの大きさ、σは通常のガウシアンフィルタの係数と同じ。

σの値が、カーネルの中心の輝度値との差をどの程度許容するか?を制御している事が分かります。

そのため、σの値を大きくしていくと、ただのガウシアンフィルタの処理に近づき、輪郭もぼやけてしまいます。逆にσの値を小さくし過ぎると、ノイズ除去効果が弱くなります。

 

そこで、実際にはエッジを保持しつつノイズをできるだけ除去したい場合は、1回のバイラテラルフィルタでσ1、σの値を調整しようとするよりも、バイラテラルフィルタを何回か繰り返した方が効果的です。

 

原画 1回目 2回目
3回目 4回目 5回目

 

このように、バイラテラルフィルタはガウシアンフィルタのカーネルに輝度差に基づいて重みを付けている訳ですが、この、×××に基づいて重みを付ける処理という考え方は、処理を安定させるポイントになったりします。

 

例えばロバスト推定法なんかも近い感じ。

 

画像処理アルゴリズムへ戻る

ガンマ補正(ルックアップテーブルの例)

ルックアップテーブルLookup Table【略LUT】)は、ある値の答えが必ず1つの値となる場合、あらかじめ答えを計算しておき、配列に格納しておくことで、毎回の計算をすることなく、 配列(テーブル)を参照することで効率的に処理を行う手法です。

 

画像処理では、コントラスト調整やガンマ補正などによく用いられます。

 

例えば、ガンマ補正の場合、ガンマ補正値をγ、補正前の輝度値をsrc
補正後の輝度値をdstとすると、補正式は

 

 

となりますが、この計算を全ての画素に対して処理を行うのは非常に非効率です。
そこで、例えばLUTという配列に、ガンマ補正の計算結果を以下のように格納しておきます。

 

src 0 1 2 3 ・・・ 252 253 254 255
dst 0 16 23 28 ・・・ 253 254 254 255

 

 

あとは、各画素に対して、

 

dst = LUT[src]

 

という変換をすればよいだけなので、高速に処理を行うことができます。
こうする事でルックアップテーブルを用いない場合は、画素数分のガンマ補正計算をしないといけないのが、ルックアップテールを用いる事で、この例の場合では、たかだか256回の計算だけで済みます。

 

ガンマ補正例

  • ガンマ補正前

 

  • ルックアップテーブル

 

  • ガンマ補正後

 

他にも二値化処理や、輝度値を0~255の範囲内に収めるための if 文の代わりなどにも応用できる場合があるので、処理を高速にしたい場合は、このルックアップテーブルが使えるかどうか?検討してみるのも良いと思います。

 

画像処理アルゴリズムへ戻る

 

標準画像データベースSIDBA(Standard Image Data-BAse)

画像処理をしていると、一度は見た事があるであろう、マンドリルの画像↓

ですが、これは画像処理用の  標準画像データベースSIDBA(Standard Image Data-BAse)から用いられています。このSIDBAの画像データ入手先ですが、SIDBAで検索すると、あちこちで画像が公開されているので、どこが本家なのか?良く分かりませんが、比較的入手しやすいホームページは以下のページでしょうか...

 

※女性の画像(レナ)も有名なのですが、本人の希望により、使って欲しくないとのことなので、使用は避けましょう。
(参考)https://www.zaikei.co.jp/article/20200717/576594.html

神奈川工科大学

http://www.ess.ic.kanagawa-it.ac.jp/app_images_j.html

南カリフォルニア大学

http://sipi.usc.edu/database/

 

画像処理アルゴリズムへ戻る

 

テンプレートマッチング(template matching)

画像の中から指定した画像(テンプレート)と似ている位置を探すことを
テンプレートマッチング(template matching)と言います。

 

テンプレート 画像

 

このとき、テンプレートと画像データがどれだけ似ているか?という評価値(類似度または相違度)にはいくつかあり、次に示すような値を用います。

 

以下の式において、テンプレートの輝度値の値をT(i,j)、画像の輝度値の値をI(i,j)とします。
座標の(i,j)はテンプレートの幅をm画素、高さをn画素としたとき、左上(もしくは左下)を(0,0)、右下(もしくは右上)を(m-1、n-1)とします。

 

SSD(Sum of Squared Difference)

SSDはテンプレートをラスタスキャンし、同じ位置の画素の輝度値の差の2乗の合計が用いられます。
SSDの値が小さいほど、似ている位置となります。

 

SAD(Sum of Absolute Difference)

SADはテンプレートをラスタスキャンし、同じ位置の画素の輝度値の差の絶対値の合計が用いられます。
SADの値が小さいほど、似ている位置となります。

 

正規化相互相関【NCC:Normalized Cross-Correlation】

テンプレートと画像との類似度として、以下の正規化相互相関を用いられる場合もあります。類似度が1に近いほど、似ている位置となります。

 

ちなみに、この計算式は内積の式をCosθ = の式に変形した式と同じ事にお気づきでしょうか?上式を以下のように変形すると、M×N次元のIのベクトルとTのベクトルの内積に見えてきませんか?

 

 

正規化相互相関【ZNCC:Zero-mean Normalized Cross-Correlation】

上記NCCの相互相関係数ではテンプレートや画像の明るさが変動すると、NCCの値もふらついてしまうので、テンプレートおよび画像の輝度値の平均値をそれぞれの値から引いて計算することで、明るさの変動があっても安定的に類似度を計算することができます。

ここで、この式はテンプレートの領域内の輝度値の平均値を計算してから、さらに輝度値から平均値を引くため、標準偏差の時にも説明したように、この式でそのままプログラミングすると効率の悪いプログラムとなってしまいます。そのため、今回も同様にRZNCCの式を変形します。

 

テンプレートの平均輝度値およびテンプレートと同じ領域の画像の輝度値の平均は

 

 

で求まることから、この値をRZNCCの式に代入して整理すると、

となります。この式を用いると、プログラム的には1パスで済むでの計算効率が良くなります。

 

ΣΣ×××の部分がなんだか難しく見える方は、C言語に置き換えて

 

Sum = 0;
for(j = 0; j < N; j++){
for(i = 0; i < M; i++){
Sum += ×××;
}
}

 

と置き換えて読んで下さい。

 

ここで説明しておきながら、最近の工業用途では、この正規化相関の方法よりもパターンの輪郭に基づき、回転やスケール変動などにも対応したマッチング(輪郭サーチ)が一般的になってきています。

最近では、さらにカメラが動いてもマッチングする三次元マッチングなども登場してきています。
正規化相関では明るさの変動にも強く優れたアルゴリズムのように聞こえるかもしれませんが、パターンが重なった場合や欠けた場合に意外と弱かったりもします。

 

また、このテンプレートマッチングでは、テンプレートと画像を重ね合わせたとき、同じ画素の位置の輝度値が明暗がどれだけ似ているか?という事を評価しています。
そのため、例えば顔写真を登録しておいて、撮影した画像の中から似ている人を見つけ出す!みたいな使い方はできません。
人間の感性での似ている事とはズレがある事に注意して下さい。

 

画像処理アルゴリズムへ戻る

 

疑似カラー(Pseudo-color)

三次元データやサーモグラフィのようにデータを画像にした場合には、モノクロで表示するよりも、色を付けて表示した方が見やすい場合があります。

 

モノクロ表示 疑似カラー表示

 

このグレーの色に疑似的に色を付ける方法を疑似カラー(Pseudo-color)と言います。この色の付け方は色相を使って青~緑~赤へと変化させてもいいのですが、もう少し簡単な方法を紹介します。
青~緑~赤~青へと色を変化させるには以下のようなパターンでR,G,Bの値を変化させます。

 

 

このパターンは青~緑~赤~青の色相で言うと一周分なので、一般的に用いられるのは青~緑~赤ぐらいまでなので、上図の0~240°部分を8Bitの輝度値に割り当て

 

 

のようにすると、このようなグレースケール↓に

 

疑似カラーを割りつけると、このように↓なります。

 

このようにクレースケールにカラーを割り当てることで、より画像を見やすくしています。モノクロ8Bitの画像データの場合は、画像データはそのままに、カラーテーブルを変更するだけで、疑似カラー表示する事が出来ます。

 

画像処理アルゴリズムへ戻る

 

色相、彩度、明度の計算方法

色相Hue:色合い)、彩度Saturation:鮮やかさ)、明度Brightness,Lightness,Intensity,Value:明るさ)については、以前、変換式には色相、彩度、明度ほかのページにまとめたのですが、実は訳も分からず公式だけをまとめていました。

 

で、なんだか気持ちが悪かったので色相、彩度、明度について、よ~く調べてみました。
私なりの理解ですが、以下にまとめました。

 

R、G、Bの色空間については、下図のようにRGBをXYZのように三次元座標で表すと、一辺の長さが255で表される立方体の範囲内で全ての色を表す事が出来ます。(R,G,B各8bitの場合)

 

この立方体を白(255、255、255)に位置から黒(0、0、0)の方向へ見て、R軸を右側に取ると、

 

 

のように、正六角形となります。
この時、の方向を0°として、反時計回りにの位置が120°240°色相(0~360°(2π))を定めます。
彩度は一番外側の六角形に対して、どの割合の位置に配されているかを0~1.0で表したものが彩度となります。

 

詳細は後述しますが、色相彩度はカメラやパソコンなどの性能評価(使いやすさ、価格、処理速度など)を表す時に用いるレーダーチャート(クモの巣グラフ)もどきみたいな物?!と思うと、自分の中で少し整理ができました。

 

さらに、この六角形の高さ方法に明度を割り振ると、HSV(六角錐モデル)やHLS(双六角錐モデル)となります。
それぞれの違いは明度の定義が異なり、R、G、Bの最大輝度値をImax、最小輝度値をIminとしたときに

明度V = Imax

としたものがHSV

明度L = ( Imax + Imin ) / 2

としたものがHLSとなり、明度の値は0~1.0で表されます。

これを立体で表すと

 

HSV(六角錐モデル)

HLS(双六角錐モデル)

となります。
このHSV、HLSともに、六角錐の斜面の部分が彩度が1.0となります。

 

以下、色相、彩度、明度の詳細な計算方法です。

 

HSVの計算方法

はじめにR、G、Bの輝度値の範囲を0~255から0~1.0となるように変換します。
(R、G、Bのそれぞれの値を255で割ります。)

 

【色相Hの求め方】
下図のように、0°方向にR、120°方向にG、240°方向にBだけ進み、最後の点の位置のR軸に対する角度が色相となります。

 

 

この最後の点の座標は中心を(x、y) = (0、0) とすると、R、G、Bの方向のなす角度から

 

 

となり、xとyより色相Hが求まります。

 

 

ただし、アークタンジェントの計算が出来ない場合など、この方法とは別に、近似的に求める方法もあります。(こちらの方が一般的)

 

下図を見ても分かる?ようにR、G、Bの成分の比を比べ、

Rが最大の場合、色相は-60°(300°)~60° (R方向の0°±60°)
Gが最大の場合、色相は 60°~180° (G方向の120°±60°)
Bが最大の場合、色相は 180°~300° (B方向の240°±60°)

の範囲内に色相は収まります。

 

 

以下、Rの値が最大の場合を例に取って説明したいと思います。

 

下図のように、2つの矢印の長さが分かれば、その矢印の比で角度60°を分割することで、角度(色相)を近似することが出来ます。

 

 

R、G、Bの大きさがR≧G≧Bの場合

 

色相H = 60° × (G – B) / (R – B)

R、G、Bの大きさがR≧B≧Gの場合

 

色相H = 60° × (G – B) / (R – G)

 

となります。
ただし、この場合、色相の値が負となるので、

 

色相H = 60° × (G – B) / (R – G) + 360°

 

とします。

 

と、なる理屈を理解するのに苦労しました...
図中に書いてある黄色い正三角形がポイント!
正三角形なので、三辺の長さが等しい分けで。
R以外のGやBが最大となる場合も理屈は同じです。
120°づつ回転させて考えてみると分かります。

 

この式を一般的に書くと、R、G、Bの成分のうち、最大の成分をImax、最小の成分をIminとすると

ImaxがRのとき

ImaxがGのとき

ImaxがBのとき

となります。

 

【明度Vの求め方】

明度は、もともとHSVの定義よりR、G、Bの成分のうち、最大の成分をImaxとすると

明度V = Imax

 

とします。
明度Vの範囲は0~1.0となります。

 

【彩度Sの求め方】
R、G、Bの成分のうち、最大の成分をImax、最小の成分をIminとすると

 

彩度S = (Imax – Imin) / Imax

 

となります。
彩度Sの範囲は0~1.0となります。

 

 

HLSの計算方法

【色相Hの求め方】
色相HはHSVの色相Hの求め方と同じです。

 

【明度Lの求め方】
明度Lは、もともとHLSの定義よりR、G、Bの成分のうち、最大の成分をImaxとすると

 

明度L = ( Imax + Imin ) / 2

 

とします。
明度Lの範囲は0~1.0となります。

 

【彩度Sの求め方】
R、G、Bの成分のうち、最大の成分をImax、最小の成分をIminとすると

 

L≦0.5のとき

彩度S = (Imax – Imin) / (Imax + Imin)

L>0.5のとき

彩度S = (Imax – Imin) / (2 – Imax – Imin)

 

となります。
彩度Sの範囲は0~1.0となります。

 

以下、補足説明です。
彩度Sは下図の外側の六角形に対して、内側の六角形の大きさの割合で求められます。

 

 

この六角形の大きさはR,G,Bの輝度値が最大となる軸上で考えると比較的分かりやすいと思います。
今回はRの値が最大となる場合とします。

 

HLSは双六角錐モデルであるため、明度Lが0.5以下の場合、外側の六角形の大きさ(上図のE’の位置)は

 

E’ = Imax + Imin

 

で求まります。

 

 

明度Lが0.5より大きい場合、外側の六角形の大きさ(上図のE’の位置)は

 

E’ = 2 – Imax – Imin

 

となります。

 

 

以上のことから、最初の彩度Sの式が求まります。

 

色相、彩度、明度を使った色判別時の注意点

色相および彩度を用いると、画像の明るさ(明度)が変動しても似た色の領域を抽出する事が可能となりますが、彩度の値が小さい場合、つまりR、G、Bの値がそれぞれ近い場合は色相の値が不安定になります。

 

例えば、
(R、G、B) = (121、120、120)の場合、 色相H = 0°
(R、G、B) = (120、121、120)の場合、 色相H = 120°
(R、G、B) = (120、120、121)の場合、 色相H = 240°

 

と、ほんの少しのR、G、Bの値の違いでも色相の値は大きく異なります。

 

また、色相Hは角度で表されるので、例えば1°も359°も値こそ離れていますが、どちらも0°±1°の範囲内で角度的には近いので、色相Hの値で単純に二値化処理することで色の領域を抽出する場合は注意して下さい。

 

画像処理アルゴリズムへ戻る

 

色相、彩度、明度の公式

カラーの画像処理をする時には、これら色相などの知識は必須となります。
Windows標準で付いてくるペイントで、色の作成の表示をすると、雰囲気が分かると思います。

 

 

基本的に以下の色相、彩度、明度を用いて色を表すのですが、変換式にいくつかの種類があります。

 

色相(Hue)

色合いを表します。
赤や緑、青などに色を0~360°(0~2π)の角度を用いて表します。

彩度(Saturation)

鮮やかさを表します。
と一般的に言われるのですが、鮮やかさ?と言われても、いまいちピンと来ませんが、下記に示した式から見ても分かるように、R,G,Bの値にどれだけ開きがあるか?を示しています。
このことは逆にいうと、R,G,Bの値に開きが無い場合は、グレーに近い事から、彩度は如何にグレーっぽく無いか?、という事から、どれだけ純色(赤、緑、青、黄、シアン、紫など)に近いか?を表しています。

明度(Brightness,Lightness,Intensity,Value)

色の明るさを表します。

 

HSV変換

6角錐モデルとも言います。

 

【RGB⇒HSV変換】

Imax = Max(R,G,B)
Imin = Min(R,G,B)
とすると

 

R = Imaxのとき

H = 60×(G – B) / (Imax – Imin)

G = Imaxのとき

H = 60×(B – R) / (Imax – Imin) + 120

B = Imaxのとき

H = 60×(R – G) / (Imax – Imin) + 240


S = (Imax – Imin) / Imax


V = Imax


【HSV⇒RGB変換】

h = floor(H / 60)     floor()は切り捨て処理
P = V × (1 – S)
Q = V × (1 – S × (H / 60 – h))
T = V × (1 – S × (1 – H / 60 + h))
とすると

 

h= 0のとき

R = V, G = T, B = P

h= 1のとき

R = Q, G = V, B = P

h= 2のとき

R = P, G = V, B = T

h= 3のとき

R = P, G = Q, B = V

h= 4のとき

R = T, G = P, B = V

h= 5のとき

R = V, G = P, B = Q


HLS変換

双6角錐モデルとも言います。

 

【RGB⇒HLS変換】

Imax = Max(R,G,B)
Imin = Min(R,G,B)
とすると

 

R = Imaxのとき

H = 60×(G – B) / (Imax – Imin)

G = Imaxのとき

H = 60×(B – R) / (Imax – Imin) + 120

R = Imaxのとき

H = 60×(R – G) / (Imax – Imin) + 240


L = (Imax + Imin) / 2

L ≦ 0.5のとき

S = (Imax – Imin) / (Imax + Imin)

L > 0.5のとき

S = (Imax – Imin) / (2 – Imax – Imin)


【HLS⇒RGB変換】

h < 0のとき

h’ = h  + 360

h ≧ 360のとき

h’ = h  – 360

その他

h’ = h

L ≦ 0.5のとき

M2 = L × (1 + S)

L > 0.5のとき

M2 = L + S – L × S


M1 = 2 × L – M2


h’ < 60のとき

X = M1 + (M2 – M1) × h’ / 60

60 ≦ h’ < 180のとき

X = M2

180 ≦ h’ < 240のとき

X = M1 + (M2 – M1) × (240 – h’ ) / 60

240 ≦ h’ ≦ 360のとき

X = M1

 

とすると

 

R = X ただし、h = H + 120とする

G = X ただし、h = Hとする

B = X ただし、h = H – 120とする

 

カラー変換用関数

【Win32APIの場合】 VBの表記例
‘HLS変換(Windows 2000以降、またはInternet Explorer 5.0がインストールされてある環境。(SHLWAPI.DLL Version 5.00以上)
‘h (色相)
‘赤(0)、黄(40)、緑(80)、シアン(120)、青(160)、マゼンダ(200)の順に定義0~239まで設定可
‘L (明度)
‘色の明るさをあらわす。0~240まで設定可。0が黒、240が白になる。
‘s (彩度)
‘0~240まで設定可。240が純色になる。
Public Declare Sub ColorRGBToHLS Lib “SHLWAPI.DLL” _

(ByVal clrRGB As Long, _
pwHue As Integer, _
pwLuminance As Integer, _
pwSaturation As Integer)

Public Declare Function ColorHLSToRGB Lib “SHLWAPI.DLL” _

(ByVal wHue As Integer, _
ByVal wLuminance As Integer, _
ByVal wSaturation As Integer) As Long

 

最初に紹介したペイントの色の作成では、この関数と同様の変換(設定値を含めて同じ)をしています。

 

【.NET Frameworkの場合】
System.Drawing.Color構造体にて

GetHueメソッド      HSBのH(色相)を取得
GetSaturationメソッド   HSBのS(彩度)を取得
GetBrightnessメソッド   HSBのB(明度)を取得

RGB⇒HSB変換はなし?

 

【OpenCVの場合】
cvCvtColor関数にて、以下の変換に対応
XYZ, YCrCb(YCC), HSV, HLS, L*a*b, L*u*v
その他 同じcvCvtColor関数で Bayer変換に対応

 

カラー画像処理例

そのカラー画像を色相、彩度、明度に分解し、それぞれの値(主に色相)でフィルタリング処理(バンドパスフィルタ)を行い、カラー画像に逆変換する事により、特定の色だけを抽出する事が可能になります。これにより、色の位置や個数などの検査をする事が可能となります。

処理前 処理後

ただし、彩度の値の小さな色(白やグレーに近い色)は彩度の値が不安的になりがちなので、カラー画像処理には不向きです。

 

画像処理アルゴリズムへ戻る

 

外周画像の処理

平滑化フィルタやメディアンフィルタなどの注目画素の周辺画素を用いた画像フィルタ処理では、画像の外周部分が下図のように画像の外側を参照してしまうため、処理ができなくなります。

 

 

この外周部分を処理する方法はいくつかあるのですが、代表的な方法を紹介します。
以下、5×5サイズのカーネルを用いた場合の処理を例にとって紹介します。

 

■外周部分の輝度値を画像の外側にコピーして補間する方法

5×5サイズのカーネルの場合、画像の外側に2画素分、画像の輝度値を参照
してしまうので、この2画素分の輝度値を画像の外周部分の輝度値をコピーして
輝度値を参照します。

 

おそらく?この手法が一般的だと思います。

 

■外周部分を中心にして対称の位置にある輝度値を外側にコピーして補間する方法

例えば、カーネルが座標(-1、-2)の画素を参照する場合は座標(1、2)の画素の輝度値を参照するようにします。

 

 

他にも、単純に外周部分は処理をしないで黒(輝度値=0)でマスクしたり、カーネルが画像の外側を参照する場合にカーネルを形を変えて画像の外側を参照しないようにしたりする方法などもあります。

 

画像処理アルゴリズムへ戻る

 

画像の回転

画像を回転する場合、任意点周りの回転移動でも紹介したように回転行列を使って、例えば、画像の中心周りに画像を回転させると、下図のように回転後の画像が虫食い状態になってしまいます。

 

回転前の画像 回転後の画像

 

こうならないようにするためには、回転前の画像の座標を回転行列を使って回転後の座標を計算するのではなく、回転後の座標が回転前の画像のどの座標を参照しているのかを計算し、画像を変換します。

つまり、画素を置きに行くのではなく、拾いに行くようにします。

 

【回転前の画像を回転行列を使って変換】

 

【回転後の座標が回転前のどの座標を参照しているかを計算して変換】

 

実際の変換処理は以下のように行います。

 

回転前の画像の座標を(x,y)、回転後の画像の座標を(X,Y)、画像の中心座標を(Cx,Cy)とすると単純に画像の中心周りに座標を回転すると以下のような行列で表されます。

 

 

この行列を回転前の画像の座標(x、y)に関して解けばいいので、行列の式の両辺にそれぞれ逆行列をかければいいので、以下のような手順で行列を解いていきます。

 

これで、回転前の座標(x、y)に関して解くことができます。
でも、逆行列を解くのは面倒と思う事なかれ。
(+Cx,+Cy)の平行移動の逆行列は(-Cx、-Cy)方向への移動と同じ、+θ方向への回転の逆行列は-θ方向への回転と同じなので、

 

 

とすれば、逆行列を解くことなく回転後の座標(x、y)に関して座標を解くことができます。
このようにして画像の回転処理を行うと、このようになります。

 

回転前の画像 回転後の画像

 

でも、回転後の画像はなんかギザギザしてしまっていますが、これは回転前の画像の座標を計算するとほとんどの場合、画素の画素の間の座標となってしまいますが、上図の例ではこの座標を四捨五入して輝度値を参照しているためで、bilinearやbicubicなどの補間を使うと少しは滑らかな画像となります。

今回は画像の回転について紹介していますが、画像の拡大縮小についても同様の考え方で処理することができます。

 

画像処理アルゴリズムへ戻る

 

関連記事

アフィン変換(平行移動、拡大縮小、回転、スキュー行列)

任意点周りの回転移動(アフィン変換)

画素の補間(Nearest neighbor,Bilinear,Bicubic)の計算方法

アフィン変換(平行移動、拡大縮小、回転、スキュー行列)

画像の拡大縮小、回転、平行移動などを行列を使って座標を変換する事をアフィン変換と呼びます。

X,Y座標の二次元データをアフィン変換するには、変換前の座標を(x, y)、変換後の座標を(x’,y’)とすると回転や拡大縮小用の2行2列の行列と、平行移動用に2行1列の行列を使って

$$\left(\begin{array}{c}x^{‘}\\ y^{‘}\end{array}\right)=
\left(\begin{array}{c}a & b\\ c & d\end{array}\right)
\left(\begin{array}{c}x\\ y\end{array}\right)
+ \left(\begin{array}{c}T_{x}\\ T_{y}\end{array}\right)$$

のように表現される場合もありますが、回転、拡大縮小、平行移動を1つの3x3の行列にまとめて

$$\left(\begin{array}{c}x^{‘}\\ y^{‘} \\ 1\end{array}\right)=
\left(\begin{array}{c}a & b & c\\ d & e & f\\ 0 & 0 & 1\end{array}\right)
\left(\begin{array}{c}x\\ y\\ 1\end{array}\right)$$

と3x3の行列で表現する場合もあります。

この表現を同次座標系と呼びます。

同次座標系では一見、3行目は無駄なようにも見えるのですが、この意味の無いような1行を追加する事で、平行移動も同じ行列の積で表現でき、逆行列を使う事で、アフィン変換後の座標からアフィン変換前の座標も簡単に求める事ができるようになります。

私は3行3列の行列を用いた同次座標系のアフィン変換しかしていない、というか断然おススメなので、同次座標系で説明したいと思います。

後半でアフィン変換の実用例を示しているので、その部分で、同次座標系の恩恵を感じてもらえると嬉しいです。

 

変換前の画像を以下のようにすると、

各種変換は以下の通りとなります。

 

拡大縮小

X軸方向の拡大率をSx、Y軸方向の拡大率をSyとすると拡大縮小のアフィン変換は

 

 

と表されます。

 

例)X軸方向に2倍

 

例)Y軸方向に2倍

 

例)X軸、Y軸方向に2倍

 

例)Y軸方向に-1倍

このように、ある軸(上記の例ではX軸)に対して反転する処理の事を鏡映と呼びます。

 

平行移動

X軸方向にTx、Y軸方向にTyだけ移動するアフィン変換は

 

 

のように表されます。

 

 

回転

原点を中心に反時計回りにθ°回転する時のアフィン変換は

 

 

のように表されます。

 

 

スキュー(せん断)

四角形の画像を平行四辺形に変形する処理をスキューまたはせん断といいます。
このアフィン変換は

 

 

アフィン変換の実用方法

画像処理で使われるアフィン変換は、下図のようにピクチャボックスなどの左上が原点[座標が(0, 0)]で右方向が+X、下方向が+Y、時計周りが+θ方向となる場合が多いかと思います。

また、平行移動、拡大縮小、回転などの行列を紹介しましたが、これらの行列を1回だけで処理する事はまれで、それぞれの行列を組み合わせてアフィン変換を行います。
さらに、拡大縮小、回転、スキューのアフィン変換行列は、あくまでも原点を基点として、変換される事に注意が必要です。

例えば、画像が原点の位置に無い場合、画像をX,Y方向に2倍の大きさにしようとしたとき、単に拡大縮小のアフィン変換行列で座標を計算すると、表示位置も2倍、原点の位置から離れた位置に移動します。

これらの事を踏まえ、画像を幅方向に2倍、高さ方向に3倍し、画像を反時計方向に90°回転、さらに、画像の左上の座標が(50, 50)から(30,180)へ移動するアフィン変換を例題にとって考えたいと思います。

この変換は、一発で変換するアフィン変換行列を考えるのではなく、平行移動拡大縮小回転に分けて、アフィン変換の順番を考えます。


拡大縮小と回転が原点を基点とするため、画像を原点の位置へ移動するため、x方向に-50、y方向に-50の平行移動します。

平行移動のアフィン変換行列は

$$\begin{pmatrix} 1 & 0 & -50 \\ 0 & 1 & -50 \\ 0 & 0 & 1 \end{pmatrix}$$


拡大縮小か回転のどちらからでも構いませんが、x方向に2倍、y方向に3倍の拡大縮小を行います。

拡大縮小のアフィン変換行列は

$$\begin{pmatrix} 2 & 0 & 0 \\ 0 & 3 & 0 \\ 0 & 0 & 1 \end{pmatrix}$$


反時計周りに90°回転(ー90°回転)を行います。

回転のアフィン変換行列は

$$\begin{pmatrix} cos(-90) & -sin(-90) & 0 \\ sin(-90) & cos(-90) & 0 \\ 0 & 0 & 1 \end{pmatrix}$$


最後に目的の位置へ移動するのに、x方向に+30、y方向に+180の平行移動します。

平行移動のアフィン変換行列は

$$\begin{pmatrix} 1 & 0 & 30 \\ 0 & 1 & 180 \\ 0 & 0 & 1 \end{pmatrix}$$


このようにアフィン変換を平行移動、拡大縮小、回転に分解して、変換の手順を考える事が大事です。
今回の場合は

変換前 → 平行移動 → 拡大縮小 → 回転 → 平行移動 → 変換後

としています。

画像に対してアフィン変換を行う場合、考え方としては、平行移動や拡大縮小などに分解して考えますが、画像データを毎回変換するのではなく、アフィン変換行列をまとめて計算し、一発でアフィン変換処理を行います。

具体的には、今回のアフィン変換処理を行列で表すと

$$\begin{pmatrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{pmatrix}=\begin{pmatrix} 1 & 0 & 30 \\ 0 & 1 & 180 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} cos(-90) & -sin(-90) & 0 \\ sin(-90) & cos(-90) & 0 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} 2 & 0 & 0 \\ 0 & 3 & 0 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} 1 & 0 & -50 \\ 0 & 1 & -50 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}$$

行列部分をまとめて計算し

$$\begin{pmatrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{pmatrix}=\begin{pmatrix} 0 & 3 & -120 \\ -2 & 0 & 280 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}$$

となり、アフィン変換行列を一発で処理する事ができます。

さらに、変換後の座標(x’, y’)から、変換前の座標(x, y)を求める場合があるのですが、その時は、行列の左側からアフィン変換行列の逆行列を掛けて、

$$\begin{pmatrix} 0 & 3 & -120 \\ -2 & 0 & 280 \\ 0 & 0 & 1 \end{pmatrix}^{ -1 }\begin{pmatrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{pmatrix}=\begin{pmatrix} 0 & 3 & -120 \\ -2 & 0 & 280 \\ 0 & 0 & 1 \end{pmatrix}^{ -1 }\begin{pmatrix} 0 & 3 & -120 \\ -2 & 0 & 280 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}$$

$$\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}=\begin{pmatrix} 0 & 3 & -120 \\ -2 & 0 & 280 \\ 0 & 0 & 1 \end{pmatrix}^{ -1 }\begin{pmatrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{pmatrix}$$

$$\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}=\begin{pmatrix} 0 & -0.5 & 140 \\ 0.333 & 0 & 40 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{pmatrix}$$

として、変換前の座標を求める事ができます。

まとめ

  • アフィン変換は平行移動、拡大縮小、回転、スキューに分けて、変換の順番を考える
  • 実際のアフィン変換は、アフィン変換行列をまとめて計算し、一発で処理を行う
  • 変換前の座標は逆行列を使うと、求めることができる

この順番の考え方は、ほとんどの場合、画像を原点へ平行移動し、拡大縮小、回転を行ってから、目的の位置へ移動すると、アフィン変換行列が求まりますが、他にも、特定の点を基準に拡大縮小や回転を行う場合は、その点を原点へ移動すればアフィン変換行列が求まります。

(参考)

 

注意点

アフィン変換では任意の3×3(2×3)の行列で表す事ができるので、任意形状に変換できそうにも思えるのですが、四角形が平行四辺形にまでは変形できるものの、台形には変形できないのでご注意願います。
この台形に変形できる処理は射影変換(ホモグラフィ)と呼びます。

アフィン変換は今回の説明のように、画像を移動、変形させるための手法として説明されますが、もう少し汎用的に座標変換として捉えると応用範囲が広がります。

例えば、データのグラフを表示する時に、横方向、縦方向に拡大/縮小した時に、表示するデータの範囲を求める場合などに応用すると、少し便利です。

 

画像処理アルゴリズムへ戻る

 

関連記事

実際にOpenCVを使って行うアフィン変換については、こちらのページ↓にまとめました。

【OpenCV-Python】アフィン変換(同次座標系を使用)

 

マイクロソフトの.NETやDirectXで扱うアフィン変換行列は、行と列が逆になり、行列を掛ける順番も逆(右側から掛ける)になります。
この仕様については、下記ページ↓にまとめました。

【C#.NET】マイクロソフト仕様のアフィン変換