[Go to “teaching” in Morimoto Lab]
①画素へのアクセス(画像処理)
②メタボール
①は画像処理の基本で、OpenCVのようなライブラリに任せてしまうのではなく、そのような画像処理の中身を理解し、自分で1から実装できるようにしておくのに不可欠です。
②はCGの手法の一つで、滑らかな境界を作るために用いられます。
今回は簡易版です。
※この回は講義2コマ分になるかも。
画像処理の便利な関数はいくつかProcessingに用意されているが、自分でそのような画像処理を実装する場合を見て行く。
Images and Pixelsという公式チュートリアルの “Pixels, pixels, and more pixels”の一部分を以下で説明する。
↑画素の実際の並びは図上段のようになっていますが、
画素は1次元配列で図下段のように格納されています。
↓Processingの実行画面の画素を扱う最もシンプルな例
http://learningprocessing.com/examples/chp15/example-15-05-PixelArray
size(200, 200);
// ↓画素を扱う前に必ず記述しましょう
loadPixels();
// 画素配列のlengthなので、画素の全てを見て行きます
for (int i = 0; i < pixels.length; i++) {
// 0から255の乱数
float rand = random(255);
// 乱数でグレイスケールの色を決めます。color型については調べてください。
color c = color(rand);
// ↓画素の色を設定
pixels[i] = c;
}
// ↓画素の処理を終わるときに必ず記述する
updatePixels();
loadPixels()
allows us to capture the window.
pixels[width * height]
allows us to change pixel colors one by one.
updataPixels()
allows us to reflect the change. Put this when finishing dealing with pixels.
↓任意の位置の画素にアクセス、および、RGBを扱う例
loadPixels();
for (int x= 0; x<width; x++) {
for (int y= 0; y<height; y++) {
int pixCol = pixels[x + y * width];
float r = red(pixCol); //(pixCol >> 16) & 0xff;
float g = green(pixCol); //(pixCol >> 8 ) & 0xff;
float b = blue(pixCol); //(pixCol & 0xff);
//r=0;//値を変えたい場合
//ウィンドウのピクセルに当てはめる
pixels[y * width + x] = color(r, g, b);
}
}
updatePixels();
red(), green(), blue()
これらの関数によってRGB成分を個別に取り出せる。
このほか、hue(), saturation(), brightness(), alpha()
などもある
x+y*width
番目のpixels[]の要素が(x,y)の画素です。
この場合、実行画面が画像なので、ウィンドウの横幅であるwidthを使っていますが、もしPImageで定義した画像を使う場合は、画像の横幅をwidthに入れてください。(PImageクラスにもwidthがあります)
Note: 配列のインデックス番号からxy座標を求める方法
for (int i = 0; i < pixels.length; i++){ }
のとき、iからx,yを求めることもできます。
x=i%width, y=i/widthですね。
Note:ビットシフトでRGBを取り出す
上記サンプル内のコメントアウト部分は、これらの関数と同じ働きをする別のやり方。
ビットシフトという演算を使っている。
intは4byte(=32bit)なので、1byteずつ各色の成分を割り当てられる。
色を表す場合の順番は(A)RGBとなっており、1画素を1つのintで表すと以下のような構造になっている。
00000000 00000000 00000000 00000000
↑透明 ↑赤 ↑緑 ↑青
ビットシフトで取り出したい色を右側8桁に移動し、
0xffつまり、
11111111でその部分だけを取り出す(ビットANDの演算)と、該当する色の値を取り出すことができる。[Processingのright shift参照]
point()などで描画するのではなく、pixels配列を用いて画像を生成してください。
色を付けても面白いですよ。
Hint:
簡単なものなら、グレイスケール化など。
1画素のRGBの値を平均してその画素に代入する
できそうな人は、8近傍の平均化フィルタなどをやってみましょう。
まずはグレイスケール画像を使うのがおすすめです。
その場合、周囲含めた3x3の9画素の平均値を中央の画素に代入する。
保存用のPImageを用意しておかないと正しい結果にならないので注意!
処理中に画素値が変更しないようにする
Note : PImageによる画素の処理
PImage
も実行画面の処理の時に使った以下の関数と配列を持っています。
loadPixels()
pixels[]
updatePixels()
PImageを使った画素の処理の例はこちらを見てください。
前半の方にPImageを使った簡単な画像処理の方法があります。
後半は画素を使った画像処理について説明があります。
大事な豆知識:オーバーフロー
↓以下は2022年度の自由課題としてHiragaさんが提出してくれたものです。
![]()
このようなグラデーションは0-255まで使用する色の情報のところにそれ以上の値を入れることで起こっていると思われます。
これはオーバーフローという挙動に関係しています。 以下のYouTube動画の5分30秒あたりでその解説を話しています。
よかったら見てみてください。 https://www.youtube.com/watch?v=z981JGWkYeg
メタボールとは?
滑らかな線・面を作るためのCGの手法。
点を空間に配置し、その点からの距離が離れると濃度が低くなる空間を作る。
点が複数ある場合、濃度は加算され、濃度の等高線は滑らかなものになる。
この等高線を得るために、濃度の同じピクセルをmarching cube法で面を張る。
(2Dの場合はmarching square)
以上がメタボールだが、ここでは最後の面をはったり、境界線を作るmarching cube / squareを省略し、簡易的にメタボールを作成する。
メタボールを実装するためには、ピクセル毎に濃度を計算する必要があるため、実行画面の画素を取り出し、書き換えを行う必要がある。
つまり、画像処理を行う必要がある。
※loadPixels(), updatePixels(), pixels[]の使用例は前半のテキストを見てください。
簡易メタボールのアルゴリズム
ボールの中心となる複数の点を配置する(最初は2つで実装する)
画素毎にすべての点に対する距離を計算し、足して持っておく
足した値は適当に調整する
値を2値化すれば境界ができるので、簡易メタボールの完成
今回は上記に加えて、N値化とアニメーションを行い、面白い絵を作りましょう。
メタボールのための最低限のプログラム1:ボールを作る
ArrayList<Metaball> metaballs;
void setup(){
size(500,500);
metaballs = new ArrayList<Metaball>();
metaballs.add(new Metaball(new PVector(150,200), 200));
metaballs.add(new Metaball(new PVector(300,200), 200));
}
void draw(){
background(0);
for(int i=0 ; i<metaballs.size(); i++){
Metaball b = metaballs.get(i);
ellipse(b.pos.x, b.pos.y, b.radi, b.radi);
}
}
class Metaball
{
PVector pos;
float radi;
Metaball(PVector pos0, float radi0){
pos = pos0;
radi = radi0;
}
}
メタボールのための最低限のプログラム2:単純な距離の可視化
上記プログラムのdraw()のところを、以下のように変更する。
コード内のコメントアウトに説明があります。
void draw(){
background(0);
loadPixels();
//↓全ての画素を見るfor分
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int index = x + y * width;
float sum = 0;
//↓一つの画素に対し、すべてのmetaballからの距離を積算している。
//for (Blob b : blobs) {//←以下2行の代わりにこのようにも書ける
for (int i=0; i < metaballs.size(); i++) {
Metaball b = metaballs.get(i);
//↓画素からボール中心への距離
float d = dist(x, y, b.pos.x, b.pos.y);
sum += d;//Ex.Dの箇所
}
//Ex.E, Fの箇所
pixels[index] = color(sum);
//pixels[index] = color(sum, 255, 255);
}
}
updatePixels();
}
以下は一例:Tweakでここの値を変えてみるとよい。
sum += 50 * b.radi / d;
ifで場合分けをする方法が単純である。
ちなみに:
Processingではcolorへの値の指定時、0未満や256以上の値は勝手に0に切り上げたり、255に切り捨てられる。しかし、他の言語ではそうとは限らないので、曖昧なプログラムにならないようにif文などで切り上げや切り捨ての処理を自分で行う。
こんなところにバグが潜んでいる場合もある。
↑4値化した結果。
N値化というのは、
int N;
を用意し、このNにどんな値を入れてもN値化されるようにする、というものです。
少し難しいかもしれません。
まずは2値化、3値化の場合で考えてみて、次にN値化について考えてみるとよいでしょう。
Hint 1:
forや、breakを使って各画素がどの値域に入っているか確認していく方法がストレートな考え方ですが少し実装は面倒です。一方で、少しトリッキーな方法も紹介します。
一旦決まった画素値を正規化(ここでは255で割って0-1に直すこと)し、
N倍し、
int型に変換(キャストという)し、
255/Nを乗算する、
という方法もあり、こちらは実装上は非常に楽です。Hint 2:
グレイスケールで実装したとき、ちゃんと黒(0)と白(255)が出ていますか?
Coding Challenge #28: Metaballs
ProcessingをYoutubeで学ぶ内容のうちの一つ。
今回のメタボールの内容を書くのに参考にしました。
N値化は課題なのでしてほしいですが、グラデーションのままでもきれいだと思います。
Marching Squaresアルゴリズム
3次元のメタボールならMarching cubesというのが有名ですが、その2次元版になります。
わざわざ等高線を作成するのはひと手間かかります。
本学出身の高山先生の作品
ここにある作品のいくつかはメタボールを使って作っているようです。
みなさんも工夫してきれいなアニメーションを作ってみましょう!?