プログラムを実行すると実行画面上に元画像が表示され、マウスカーソルに合わせて赤枠 (画面サイズの1/5) が表示される。
さらに実行画面上でクリックすると、dataフォルダに4つの画像が作られる。
コンソールに「完了」が表示されてからキーボードの0~4のキーを押すとこれらの画像が画面に表示される。
(元画像は実行画面の大きさ800x600にリサイズされる)
(クリックだけですべての画像が作成されるので、キーボードを使うのは結果確認のためだけ)
キー |
画像 |
意味 |
特徴 |
0 |
元.jpg |
元画像 |
|
1 |
選択範囲.jpg |
Processingの実行画面のスクリーンショット |
元画像にクリック時の赤枠がついたもの |
2 |
1描画機能.jpg |
Processingの描画機能で選択範囲を画面のサイズに拡大した画像 |
なめらかに拡大される |
3 |
2最近傍補間.jpg |
最近傍補間を使って選択範囲を画面のサイズに拡大した画像 |
元の1ピクセルがそのまま拡大されたような粗いドットが見える |
4 |
3双一次補間.jpg |
双一次補間を使って選択範囲を画面のサイズに拡大した画像 |
なめらかに拡大される |
完了時点でdataフォルダにあるファイル
実行例

2キー, 4キーで表示される画像はどちらも滑らかになっている。
3キーで表示される画像は元画像の1ピクセルを5×5ピクセルにそのまま拡大しているためブロック状の塊が見える。
Processingでは、translate関数で原点を平行移動させ、scale関数で倍率を変更できる。
原点を右に cx, 下に cy だけ移動させる命令は
スケールを s 倍にする命令は
である。
(cx, cy) の点を中心にして画像を s倍に拡大したければ、
- 原点を (cx, cy) の所に移動させる
- 原点を中心に sだけ倍率をかける
- 原点を最初の逆方向に移動させる
という手順を実行すればよい。
ベースのプログラム
(drawToImage, nearestNeighbor, bilinearはどれも元画像をs倍に拡大したものをn番目の画像に保存する関数)
(課題1でdrawToImage関数を完成させる)
(課題2でnearestNeighbor関数, 課題3でbilinear関数を作るが、この時点では未実装)
// 画像用の変数
PImage[] img = new PImage[5];
// 出力ファイル名
String[] fName = {"元", "選択範囲", "1描画機能", "2最近傍補間", "3双一次補間"};
int w, h;
boolean created = false; // 画像を作ったかどうかのフラグ
float cx, cy; // 拡大の基準位置
float s = 5; // 拡大の倍率
void setup() {
size(800, 600);
img[0] = loadImage("元.jpg");
img[0].resize(width, height);
w = width;
h = height;
// 1~4番を元画像と同じサイズの黒画像にする
for (int i=1; i<=4; i++){
img[i] = img[0].get();
}
textFont(createFont("MS Pゴシック", 48));
noFill(); // 図形の中を塗りつぶさない設定にする
strokeWeight(2*s); // 図形の枠の太さを実質2ptにする
stroke(255, 0, 0); // 図形の枠の色を赤にする
}
void draw() {
cx = mouseX; // 拡大基準の横位置の更新
cy = mouseY; // 拡大基準の縦位置の更新
// 画像作成前 (選択範囲に枠を描画する)
if (!created) {
background(0);
image(img[0], 0, 0, width, height);// 画像を表示
translate(cx, cy); // 原点をカーソル位置に移動
scale(1/s); // 拡大率の逆数のスケールをかける
translate(-cx, -cy); // 枠内での中心位置移動
rect(0, 0, width, height); // 元画像サイズの長方形を描く
}
}
// 実行画面のスクリーンショットを保存する
void saveScreenshot(int n) {
loadPixels(); // 実行画面の情報をpixelsにロード
img[n].pixels = pixels; // pixelsの情報をimg[n]にコピー
img[n].save("data/" + fName[n] + ".jpg");
}
// Processingの描画機能を使って拡大(課題1)
void drawToImage(float s, int n) {
PGraphics pg = createGraphics(w, h);
pg.beginDraw();
// (pg)原点を(cx, cy)だけ平行移動
// (pg)sだけスケールをかける
// (pg)原点を(-cx, -cy)だけ平行移動
pg.image(img[0], 0, 0);
pg.endDraw();
img[n].pixels = pg.pixels;
img[n].save("data/"+ fName[n] +".jpg");
}
// 最近傍補間(課題2)
void nearestNeighbor(float s, int n) {
}
// 双一次補間(課題3)
void bilinear(float s, int n) {
}
// cを中心として画像を1/s倍したときのベクトルfの移動先のベクトルを返す(課題2, 3で使用)
PVector getScaledPosition(PVector c, PVector f, float s) {
f.sub(c); // fからcを引く
f.div(s); // fを1/s倍する
f.add(c); // fにcを加える
return f;
}
// 拡大処理を実行
void mousePressed(){
saveScreenshot(1); // 実行画面をimg[1]に保存
drawToImage(s, 2); // 描画機能でs倍に拡大した画像をimg[2]に保存
nearestNeighbor(s, 3); // 最近傍補間でs倍に拡大した画像をimg[3]に保存
bilinear(s, 4); // 双一次補間でs倍に拡大した画像をimg[4]に保存
println("完了");
created = true;
}
void keyPressed() {
int k = key-'0';
if (k>=0 && k<=4) {
background(0);
image(img[k], 0, 0, width, height);
fill(255, 0, 0);
text(fName[k], 30, height-30);
fill(255);
}
}
- Processingのエディタに上のコードをコピー&ペーストする。
- 「ファイル」→「名前を付けて保存」で、適当な場所に「img01」という名前で保存する。
- 適当に画像検索してサンプル用の画像を用意する (どこを拡大したかがわかるように、例えば文字のような細かい構造がどこかに含まれるものにする)。
- 画像の形式に応じて以下の変更を加える。
- JPG形式の場合→名前を元.jpgに変更
- それ以外の場合→ペイントで開き、形式をJPGに指定して「元」という名前で保存
- 「元.jpg」をProcessingのウインドウにドラッグ&ドロップする。
- drawToImage関数の「// (pg)原点を(cx, cy)だけ平行移動」のところに、対応するコードを入れる。
- 概要の説明にある translate関数を使う。
- ただし、仮想的描画対象「pg」に適用させるため、先頭に「pg.」をつける。
- drawToImage関数の「// (pg)sだけスケールをかける」のところに、対応するコードを入れる。
- 概要の説明にある scale関数を使う。
- ただし、仮想的描画対象「pg」に適用させるため、先頭に「pg.」をつける。
- drawToImage関数の「// (pg)原点を(-cx, -cy)だけ平行移動」のところに、対応するコードを入れる。
- プログラムを実行し、拡大したい場所をクリックする。
- そのあとで0キーを押すと元画像が表示される。
- 1キーを押すとクリック時の状態 (赤枠つき) が表示される。
- 2キーを押すと赤枠部分が拡大された画像が表示される。
今度は出力画像のピクセルの色を自前で計算する方法で拡大してみる。しかし、たとえば出力画像の左からi番目、上からj番目のピクセルに対応する元画像の点は、(特別な場合を除けば)どのピクセルにも対応しない半端な点になる。「元画像の左から1.7番目、上から1.8番目」のようなピクセルは存在しないので、なんらかの工夫が必要になる。

最近傍補間 (Nearest neighbor) 法と呼ばれる方法では、元画像で
この対応点にもっとも近いピクセルの色を出力画像のピクセルの色として採用する。つまり、出力画像の求めたいピクセルに対応する元画像の座標
が、例えば下の図のように(16.8, 24.3)だった場合に、この周りの4つのピクセルのうち、最も近い(17, 24)のピクセルの色を使う方法である。使う座標は単純に対応点の座標をそれぞれ四捨五入するだけで求められる。
最近傍補間で画像を拡大すると、拡大率が大きいときはジャギーが目立ってしまう。
対応点を囲むピクセルの色の情報を組み合わせて使うと、出力側の色の変化をもっと滑らかにできる。その際に、対応点を囲む4つのピクセルからの縦横の距離に応じて重みを付ける方法を双一次補間 (Bilinear)
法という。
例えば対応点の座標が (16.7, 24.3) の場合は図のような配置になる。
この場合はその点に近い右上の点の影響が大きく、遠い位置にある左上、左下、右下の点の影響はそれよりも小さくなるようにしたい。

これを一般的に考えるため、下の図のように対応点の位置を変数 x, y (いずれも0以上1未満) で表わす。

まず、左上と右上のピクセルの色を混ぜて点Aの色、左下と右下のピクセルの色を混ぜて点Bの色を決める。
どちらも左右の色を「1-x : x」の比で混ぜる。
「点Aの色」 = (1-x)「左上の色」 + x「右上の色」
「点Bの色」 = (1-x)「左下の色」 + x「右下の色」
(xが0ならA, Bの色は左側の点と同じになり、xが1なら右側の点と同じになる)

さらに、点Aと点Bのピクセルの色を混ぜて点Cの色を決める。
この場合は上下の色を「1-y : y」で混ぜる。
「点Cの色」 = (1-y)「点Aの色」 + y「点Bの色」
(yが0ならCの色は点Aと同じになり、yが1なら点Bと同じになる)

つまり、このピクセルの色は
(1-y){(1-x)(左上の色) + x(右上の色)} + y{(1-x)(左下の色) + x(右下の色)}
となる。
締切後提出用フォーム
※ 点数はつきますが、欠席だった場合は出席にはなりません。
※ コードはTeamsのClass Notebookに保存してください。