Edited at

画像処理:イメージを色々弄ってみよう

More than 1 year has passed since last update.


はじめに

 ※ この記事は「画像処理:プログラムが読み込むイメージの基本構造」をベースに進めていきます。お読みでない方は先にこちらの記事を読んで下さい。

 前回はプログラムがイメージをどうやって保存し、それを表示するのかを実際のコードと共に見てみました。今回はそれをベースにして実際にイメージを色々イジメて(?)みましょう。


事前準備

今回使用する画像

image.png

基本となるJAVAコード


ImageProcessing02.java

package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02 {
public static void main(String[] args) {
BufferedImage original = null;
try {
original = ImageIO.read(new File("image.png")); // 編集する画像を持ってくる
} catch (IOException e) {
e.printStackTrace();
}
int iWidth = original.getWidth(); // 画像の横幅
int iHeight = original.getHeight(); // 画像の縦幅
int[] iColor = new int[iWidth * iHeight]; // 横幅と縦幅を元に色情報を保存する配列作成
int[] iChangeColor = new int[iWidth * iHeight]; // 同じサイズで修正したデータを保存するための配列

// 修正するBufferedImage変数を予め作成する。
BufferedImage img = new BufferedImage(iWidth, iHeight, BufferedImage.TYPE_4BYTE_ABGR);

// 画像の色情報をgetRGBで持ってくる
original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);

/**********************
* ここにコードを書きます。 *
**********************/

// 修正した色情報を修正用のBufferedImageにセット
img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);

try {
ImageIO.write(img, "png", new File("保存する画像ファイルの経路")); // 画像を保存
} catch (IOException e) {
e.printStackTrace();
}
}
}


途中の宣言文の方を変更するケースもありますが、基本はこんな感じ。


イメージ回転

今回は360°自由な回転ではなく、直角(90°)の回転のみとします。

(自由に回転させるには三角関数がどうしても必要になるため)

※ 変更されたところだけコメントを残します


ImageProcessing02_1.java

package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_1 {
public static void main(String[] args) {
BufferedImage original = null;
try {
original = ImageIO.read(new File("image.png"));
} catch (IOException e) {
e.printStackTrace();
}
int iWidth = original.getWidth();
int iHeight = original.getHeight();
int[] iColor = new int[iWidth * iHeight];
int[] iChangeColor = new int[iWidth * iHeight];

original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);

// 状況によって横幅と縦幅が変わるので、とりあえず宣言だけ
BufferedImage img = null;

/**Image Rotate(Rotate 90)**/
int iRotate = 90;                                                      // 回す角度を設定
int iRotateS = iRotate / 90;
int iChangeWidth = 0, iChangeHeight = 0;

// 横幅と縦幅の設定 (0, 180, 360度 = 0 / 90, 270度 = 1)
switch(iRotateS % 2) {
case 0:
iChangeWidth = iWidth;
iChangeHeight = iHeight;
break;
case 1:
iChangeWidth = iHeight;
iChangeHeight = iWidth;
break;
}
img = new BufferedImage(iChangeWidth, iChangeHeight, BufferedImage.TYPE_4BYTE_ABGR);
int iTargetRow = 0, iTargetCol = 0;
for(int i = 0; i < iChangeHeight; i++) {
for(int j = 0; j < iChangeWidth; j++) {
// 360°以上も対応出来るように残りで確認する
switch(iRotateS % 4) {
case 0: // そのまま
iTargetRow = i;
iTargetCol = j;
break;
case 1: // 90°、画像の左下から上に
iTargetRow = iChangeWidth - (j + 1);
iTargetCol = i;
break;
case 2: // 180°、画像の右下から左に
iTargetRow = iChangeHeight - (i + 1);
iTargetCol = iChangeWidth - (j + 1);
break;
case 3: // 270°、画像の右上から下に
iTargetRow = j;
iTargetCol = iChangeHeight - (i + 1);
break;
}

int iTargetIndex = (iTargetRow * iWidth) + iTargetCol;
int iChangeIndex = (i * iChangeWidth) + j;
iChangeColor[iChangeIndex] = iColor[iTargetIndex];
}
}

img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);

try {
ImageIO.write(img, "png", new File("ImageRotate.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}


座標の割り出し式は前回の記事にあったのと同じです。

今回の記事で一番の肝は座標計算です。元画像のどの色をどこに置くのかが重要です。

直角での回転の場合はどこから初めて、どの方向に読んでいくのかが重要です。

実際の画像を見てみましょう。

元の画像

image.png

90°回転

ImageRotate.png

元の画像と、90°回転させた画像ですが。

ここで重要になるのは一番右上、画像の0番目の色がどこから持ってきた色かです。

90°の場合は右下から右上までの色がそのまま、直線上に並べられています。

他の角度も同じですが、このように角度が変わった時の色の座標を割り出して、

それを変更される座標に合わせて入れればOKです。


イメージ反転


ImageProcessing02_2.java

package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_2 {
public static void main(String[] args) {
BufferedImage original = null;
try {
original = ImageIO.read(new File("image.png"));
} catch (IOException e) {
e.printStackTrace();
}
int iWidth = original.getWidth();
int iHeight = original.getHeight();
int[] iColor = new int[iWidth * iHeight];
int[] iChangeColor = new int[iWidth * iHeight];

BufferedImage img = new BufferedImage(iWidth, iHeight, BufferedImage.TYPE_4BYTE_ABGR);
original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);

boolean isVerticalChange = true; // 上下反転
boolean isHorizonChange = true; // 左右反転
for(int i = 0; i < iHeight; i++) {
for(int j = 0; j < iWidth; j++) {
int iChangeRow = (isVerticalChange) ? iHeight - ( i + 1 ) : i; // 上下反転なら一番下から上に
int iChangeCol = (isHorizonChange) ? iWidth - ( j + 1 ) : j; // 左右反転なら一番右から左に
int iChangeIndex = (iChangeRow * iWidth) + iChangeCol;

int iTargetIndex = (i * iWidth) + j;

iChangeColor[iChangeIndex] = iColor[iTargetIndex];
}
}
img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);

try {
ImageIO.write(img, "png", new File("ImageReverse.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}


先程とほぼ同じです。Booleanで上下反転か左右反転かをチェックして、それによって座標を変えてます。

両方trueにすれば180°回転と動作は一緒です。


イメージの一部を切り取る


ImageProcessing02_3.java

package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_3 {
public static void main(String[] args) {
BufferedImage original = null;
try {
original = ImageIO.read(new File("image.png"));
} catch (IOException e) {
e.printStackTrace();
}
int iWidth = original.getWidth();
int iHeight = original.getHeight();
int[] iColor = new int[iWidth * iHeight];
original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);

// 切り取り始める座標
int iChangeY = 50;
int iChangeX = 50;
// 切り取るサイズ
int iChangeWidth = 100;
int iChangeHeight = 100;

// 切り取るサイズを元に配列とBufferedImage変数を宣言する。
int[] iChangeColor = new int[iChangeWidth * iChangeHeight];
BufferedImage img = new BufferedImage(iChangeWidth, iChangeHeight, BufferedImage.TYPE_4BYTE_ABGR);

// 切り取るサイズを元にループ
for(int i = 0; i < iChangeHeight; i++) {
for(int j = 0; j < iChangeWidth; j++) {
// 切り取り始める座標から色情報の座標を割り出す。
int iTargetRow = iChangeY + i;
int iTargetCol = iChangeX + j;
int iTargetIndex = (iTargetRow * iWidth) + iTargetCol;

int iChangeIndex = (i * iChangeWidth) + j;

iChangeColor[iChangeIndex] = iColor[iTargetIndex];
}
}

img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);

try {
ImageIO.write(img, "png", new File("ImageSlice.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}


画像の一部を切り取りたい場合には、始まる座標を切り取りたい場所の一番左上から

切り取るサイズ分まわせばOKです。

ただ、このままだと画像のサイズを超える範囲までループが回る可能性があるので、それは直してみてください。


パズルみたいに分割する


ImageProcessing02_4.java

package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_4 {
public static void main(String[] args) {
BufferedImage original = null;
try {
original = ImageIO.read(new File("image.png"));
} catch (IOException e) {
e.printStackTrace();
}
int iWidth = original.getWidth();
int iHeight = original.getHeight();
int[] iColor = new int[iWidth * iHeight];
original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);

int iPieceW = 3; // 横ピース数
int iPieceH = 4; // 縦ピース数

// ピース数を元にサイズ設定
int iChangeWidth = iWidth / iPieceW;
int iChangeHeight = iHeight / iPieceH;

// サイズとピース数を元に二次元配列で色情報用配列を宣言
int[][] iChangeColor = new int[iPieceW * iPieceH][iChangeWidth * iChangeHeight];

// BufferedImageも配列で宣言
BufferedImage[] img = new BufferedImage[iPieceW * iPieceH];
for(int i = 0; i < img.length; i++) {
img[i] = new BufferedImage(iChangeWidth, iChangeHeight, BufferedImage.TYPE_4BYTE_ABGR);
}

for(int i = 0; i < iHeight; i++) {
int iNowY = i / iChangeHeight; // ピースが何行目か
if(iNowY >= iPieceH) break;     // 残りがあって、設定した数以上の座標になった時は抜け出す
for(int j = 0; j < iWidth; j++) {
int iNowX = j / iChangeWidth; // ピースが何列目か
if(iNowX >= iPieceW) continue;     // 残りがあって、設定した数以上の座標になった時は無視する

int iChangeRow = i % iChangeHeight;
int iChangeCol = j % iChangeWidth;

int iTargetIndex = (i * iWidth) + j;
int iChangeIndex = (iChangeRow * iChangeWidth) + iChangeCol;
int iChangeKey = iNowY * iPieceW + iNowX;

iChangeColor[iChangeKey][iChangeIndex] = iColor[iTargetIndex];
}
}

img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);

try {
for(int i = 0; i < img.length; i++) {
ImageIO.write(img[i], "png", new File("ImageSlice" + String.format("%03d", i) + ".png"));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}


イメージパズルのように画像を均等に分割するコードです。

BufferedImageを配列で宣言して座標をもう一回分けて、分割するサイズに合わせて

Keyを変更するようにできればあとは簡単ですね。


getGraphics()を利用したイメージ編集


ImageProcessing02_5.java

package ImageProcessing;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_5 {
public static void main(String[] args) {
BufferedImage original = null;
try {
original = ImageIO.read(new File("image.png"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// イメージのGraphicsを持ってくる
Graphics g = original.getGraphics();

// イメージに色んな色の円を描く。
g.setColor(Color.RED);
g.fillOval(70, 70, 100, 100);
g.setColor(Color.GREEN);
g.fillOval(20, 120, 100, 100);
g.setColor(Color.BLUE);
g.fillOval(70, 170, 100, 100);
g.setColor(Color.BLACK);
g.fillOval(120, 120, 100, 100);

try {
ImageIO.write(original, "png", new File("ImageEdit.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}


結果

ImageEdit.png

JAVAで支援するGraphicsライブラリーを使って

ライブラリー内のメソットを利用して画像を修正するコードです。

これはRGBコードとか座標とかそう難しく考える必要はないですね。

ただ、複雑な画像処理の機能はついていないので

Graphicsだけではどうしても足りないです。

なので、前のメソットのようにRGBコードや座標を切り替えるだけで色々作れる機能は多いので、

ぜひ、直接作ってみてください。


終わりに

今回は前の記事で書いた中で一番理解しにくい、

RGBコードの座標の割り出しを使ったコードを幾つか紹介してみましたが、どうでしたか?

これは画像処理でも基本中の基本ではありますが、何事も基本が一番大事です。

しっかりマスターして、皆さんも画像処理の世界に入ってみましょう!