Apache Groovyで画像処理の基礎
本記事はG* Advent Calendar 2017の21日目の記事です。
ピクセル
Apache Groovyでも、Javaが用意しているBufferedImage
とImageIO
を利用することで、ピクセル単位で画像を取り扱うことが出来ます。
各ピクセルの正体は単なるInteger(32bit)です。
そしてその32bitの数値を8bitずつの合計4つに区切って、先頭ビットからそれぞれAlpha
、Red
、Green
、Blue
の値として扱います。
つまり,
11111111 00000000 10101010 11001100
というIntegerは、
11111111
(Alpha)
00000000
(Red)
10101010
(Green)
11001100
(Blue)
となります。
ただし、単純にInteger(10進数)の計算では上記の値をそれぞれ取得するのは難しいので、ビット演算(右シフト)で求めてあげます。
ついでに、読み込みと書き込みに必要なリソースを提供してくれるメソッドと保存に利用するメソッドを追加して、以下のようなクラスを用意しました。
import java.awt.image.BufferedImage import javax.imageio.ImageIO class ImageUtil { static Integer alpha(Integer v) {v >>> 24} static Integer red(Integer v) {v >> 16&0xff} static Integer green(Integer v) {v >> 8&0xff} static Integer blue(Integer v) {v&0xff} static Integer rgb(Integer red, Integer green, Integer blue) { 0xff000000 | red << 16 | green << 8 | blue } static Integer rgb(Integer alpha, Integer red, Integer green, Integer blue) { alpha << 24| red << 16 | green << 8 | blue } static withStreams(File rawFile, Closure clj) { BufferedImage bufReader = ImageIO.read(rawFile) BufferedImage bufWriter = new BufferedImage(bufReader.width, bufReader.height, BufferedImage.TYPE_INT_RGB) clj(bufReader, bufWriter) } static save(BufferedImage buf, String fileName) { File file = new File("${fileName}") ImageIO.write(buf, "jpg", file) } }
上記のコードを利用しつつ、以下の画像を加工してみます。
ネガポジ変換
String dir = "/home/koji/work/groovy/image-processing" String fileName = "test.jpg" File file = new File("${dir}/${fileName}") // ネガポジ変換 ImageUtil.withStreams(file) { BufferedImage bufReader, BufferedImage bufWriter -> Integer width = bufReader.width Integer height = bufReader.height // for each line(y) (0..<height).each {Integer y -> // for each columns(x) (0..<width).each {Integer x -> Integer colorInfo = bufReader.getRGB(x, y) Integer red = 255 - ImageUtil.red(colorInfo) Integer green = 255 - ImageUtil.green(colorInfo) Integer blue = 255 - ImageUtil.blue(colorInfo) Integer rgb = ImageUtil.rgb(red, green, blue) bufWriter.setRGB(x, y, rgb) } } ImageUtil.save(bufWriter, "${dir}/negaposi.jpg") }
左右反転
String dir = "/home/koji/work/groovy/image-processing" String fileName = "test.jpg" File file = new File("${dir}/${fileName}") // 左右反転 ImageUtil.withStreams(file) { BufferedImage bufReader, BufferedImage bufWriter -> Integer width = bufReader.width Integer height = bufReader.height Integer maxIndexX = width - 1 // for each line(y) (0..<height).each {Integer y -> // for each columns(x) (maxIndexX..0).each {Integer x -> Integer colorInfo = bufReader.getRGB(x, y) bufWriter.setRGB(maxIndexX - x, y, colorInfo) } } ImageUtil.save(bufWriter, "${dir}/reversal.jpg") }
縦に分割
画像を縦に5つに分割して、最後の分割分から順番に左から描画します。
String dir = "/home/koji/work/groovy/image-processing" String fileName = "test.jpg" File file = new File("${dir}/${fileName}") ImageUtil.withStreams(file) { BufferedImage bufReader, BufferedImage bufWriter -> Integer width = bufReader.width Integer height = bufReader.height Integer splitCount = 5 List <List<Integer>>splited = [] Integer splitedWidth = width / splitCount Integer remainingPixels = width % splitCount Closure getIndexes = {Integer rowIndex -> Integer startIndex = (splitedWidth - 1) * rowIndex + rowIndex Integer endIndex = startIndex + (splitedWidth - 1) [startIndex, endIndex] } splitCount.times {Integer rowIndex -> List<Integer> rowPixcels = [] def (startIndex, endIndex) = getIndexes(rowIndex) (0..<height).each {Integer y -> (startIndex..endIndex).each {Integer x -> rowPixcels << bufReader.getRGB(x, y) } } splited << rowPixcels } // 分割した時に、余っている部分があれば救済 // 今回はとりあえず最後の分割画像に余りを単純に追加 if (remainingPixels > 0) { Integer maxIndexX = width - 1 Integer remainIndexX = remainingPixels -1 (0..<height).each {Integer y -> (0..remainIndexX).each {Integer x -> splited.last() << bufReader.getRGB(width -1 - x, y) } } } // リスト内容を反転させて描画 splited.reverse().eachWithIndex {List<Integer> column, rowIndex -> def (startIndex, endIndex) = getIndexes(rowIndex) (0..<height).each {Integer y -> (startIndex..endIndex).eachWithIndex {Integer x, Integer index -> def path = index + ((splitedWidth) * y) bufWriter.setRGB(x, y, column[path]) } } } ImageUtil.save(bufWriter, "${dir}/split.jpg") }
以上です。
画像を扱うコード自体はなんだかオールドスクールな感じになりました。
AIだ機械学習だという流れの昨今、画像処理に関する知識も求められるようになっていますので、最も基礎的なピクセル単位での画像の取り扱いをまとめてみました。