10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

日本情報クリエイト Engineer'sAdvent Calendar 2017

Day 18

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

Last updated at Posted at 2017-12-17

#はじめに

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

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

#事前準備
今回使用する画像
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コードの座標の割り出しを使ったコードを幾つか紹介してみましたが、どうでしたか?
これは画像処理でも基本中の基本ではありますが、何事も基本が一番大事です。
しっかりマスターして、皆さんも画像処理の世界に入ってみましょう!

10
8
0

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
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?