LoginSignup
1
1

More than 1 year has passed since last update.

画像処理のリピート(折り返し)処理を条件分岐なしで数式にまとめてみた

Last updated at Posted at 2022-11-08

単純にリピート処理を書きたければ、条件分岐を使って簡単に書くことができます。
例えば簡単にC言語で書くと以下のようなプログラムが書けます。

#include <math.h>

// val : x,yなどの座標値
// max : 画像の幅や高さ
int repeat(int val, int max) {
    int a = abs(val);
    return a < max ? a : repeat(((max - 1) << 1) - a, max);
}

多少最適化してはいるものの、やはり条件分岐では処理速度が大幅に落ちてしまいます。
特に画像処理のような膨大な計算を要する際に、処理速度が遅いプログラムを書くのはナンセンス。

そこで数学的にこれらを処理できないかと考えたところ、第一号が完成したので紹介します。

そもそもリピート処理とは

取得したい画素座標がソース画像の領域からはみ出しているとき、ソース画像を折り返して座標を取得するといった処理ですが...
言葉で説明してもわかりづらいので画像で説明します。

下のようにLenna画像を右に100pxずらすと画像領域から飛び出る部分があるので、それらを補完する処理をここではリピート処理と呼ぶことにします。
lenna.jpg lenna_moved.jpg

また、少し今回の処理の仕組みとは異なりますが、OpenGLのテクスチャのラッピングについてこちらの記事で紹介されているので参考にしてみてください。

数学的なアプローチ・導出

上で紹介した様に、以降$x$横軸方向)を処理対象とします。
また、画像の横幅を$w$とします。
したがって、これ以降扱う文字は以下のように定めます。

\begin{align}
x \in \mathbb{ Z }\\
w \in \mathbb{ N }
\end{align}

$x$を$w$で割ると、商が$x$が画像何枚分先なのかを示し、余りが画像領域内の$x$座標を示します。
また、この時商が負の値だと厄介なので$x$の絶対値を割ることにします。
そうすることで$x$軸負の向きにも対応できます。

したがって、商を$\alpha$、余りを$\beta$とする式は

\alpha = |x| \div (w - 1)
\beta = |x| \bigl( mod(w) \bigr)

$w - 1$としているのは、実際のプログラム上では画素配列が$0$から始まるので$w-1$番目で割る必要があるのに合わせるためです。
(数学的に正しいのか怪しいですが、とりあえず余りを示すのに合同式の$mod$を使いました)
($\beta$は$|x|$を$w$で割った余りという意味合いで捉えていただければ大丈夫です)

また、$\beta$が$0$になるごと($|x|$が$w$で割り切れるとき)に区切って商を画像に割り振ると以下のようになります。
lenna_mirror.jpg
この画像から見てわかる通り、商が偶数の時画像を横方向に反転しています。
これらより、
商が奇数の時は画素座標{$0, 1, 2, \cdots, (w-3), (w-2), (w-1)$}のように取得していき、
逆に商が偶数の時は{$(w-1), (w-2), (w-3), \cdots, 2, 1, 0$}のように取得していくような操作をする方針が立てられます。

よって

P(\alpha) = |\Bigl( \alpha \bigl(mod(2)\bigr) \Bigr) - 1|
P(\alpha) \cdot w

関数$P(\alpha)$は$\alpha$を$2$で割った余りを$1$引き、その絶対値をとっています。
つまり$\alpha$が偶数の時$1$になり、反対に$\alpha$が奇数の時$0$になります。
これに対して$w-1$をかけることで上で説明した偶数パターンが完成しました。

さらに、この式に対して奇数パターンを加えていきます。
先ほど説明したものを整理すると、
$\alpha$が奇数の時上の式に対して$1$ずつ足していき、
偶数の時は$1$引いていくような操作をすればよさそうです。

よって

Q(\alpha) = i^{2P(\alpha)}
Q(\alpha) \cdot \beta

※$i$は虚数単位

一目見ても何がしたいのかわかりづらいので解説します。
最終的に$\alpha$が偶数の時$\beta$を負にしたいので、
関数$Q(\alpha)$は$P(\alpha)$が偶数の時$1$、奇数の時$0$になる性質を利用できるものがないかと模索したところ
虚数単位への累乗がよさそうという結論に至りました。
(多分他にも様々な導き方はあると思いますが...)

よって$\alpha$が偶数の時$i$を$2$乗して$-1$に、
奇数の時$0$乗して$1$にしたいので、
$P(\alpha)$に$2$をかければ求めたい形になります。

以上より式をまとめて

P(\alpha) = |\Bigl( \alpha \bigl(mod(2)\bigr) \Bigr) - 1|
Q(\alpha) = i^{2P(\alpha)}
f(x) = P\bigl(|x| \div (w - 1)\bigr) \cdot w + Q\bigl(|x| \div (w - 1)\bigr) \cdot \Bigl(|x| \bigl( mod(w) \bigr)\Bigr)

完成しました!
これこそが条件分岐(場合分け)を使わないリピート処理の関数であります!!!
学校の授業中暇な時間にこれらを考えてたことは秘密

ちなみに縦軸にもこの関数を当てはめることで適応できます。
具体的には、先ほどまでの$w$を画像の縦幅の長さ($h$など)にし、
$x$を$y$に置き換えるだけです。

実際に数式をコードにしてみる

さて、本題の関数が完成したところでソースコードを書いてみます。
今回はC++言語で書いていきます。

#include <math.h>
#include <iostream>
#include <complex>
using namespace std;

int repeat(int x, int w) {

  int w_1 = w - 1;
  int a = abs(x) / w_1;
  int p = a % 2;
  complex<double> i(0, 1);
  
  return p * w_1 + static_cast<int>(real(pow(i, p << 1))) * (abs(x) % w_1);

}

これにてやりたかったことはすべてできました。
結局処理速度は鬼遅かったなんて絶対に言えない(恐らく虚数計算が処理速度を大幅に下げていると予想)

今回解説したことが何かのお役に立てれば幸いです。
また新たに第二号などでき次第記事にしたいと思います。

1
1
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1