Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
17
Help us understand the problem. What is going on with this article?
@kurehajime

画像処理で写真をアニメ風に変換する

More than 5 years have passed since last update.

「三次元には興味がない…」と食わず嫌いして視野を狭めるのもなんなので、「鳴かぬなら鳴かせて見せようホトトギス」の精神で 写真をアニメっぽく変換するプログラムを書いてみた。

conv.png

使った言語はGo言語。今回は勉強がてら気の利いたライブラリを使わず地道に1ドットずつ変換してるので、アルゴリズムを流用すれば他の言語版も簡単に作れると思う。

仕組み

こんな感じのアルゴリズムで画像を変換している。

  1. あらかじめ使える色を決める(今回は基本16色から灰色系を除いた14色)
  2. 変換元となる画像を1ドットずつ色を調べる
  3. 使える色の中から一番近い色に変換する
  4. 完成!

ようは画像を劣化させている。
色を絞ればグラディエーションが平坦になってアニメっぽくなるという算段。

一番近い色とは?

「限りなく黒に近いグレー」や「限りなく透明に近いブルー」など、世の中たくさんの色で溢れている。このような中途半端な色を白黒はっきりさせてカテゴライズするにはどうすればいいだろうか。

今回はRGB空間を使う。
rgbCube.gif

R(レッド),G(グリーン),B(ブルー)の三原色のレベルをX,Y,Z座標に置き換えて、3次元空間上の二点間の距離で色の近さを判断する。例えば赤色は(255, 0, 0)なので、X=255,Y=0,Z=0の座標になる。

中学校で習った平面上の二点間の距離の公式はこれ。これは自分も覚えてる。
平面上の二点間の距離=√((X1-Y1)^2+(X2-Y2)^2)

たぶん習ったと思うんだけどググるまで忘れてた三次元空間上の二点間の距離の公式はこれ。
三次元上の二点間の距離=√((X1-X2)^2+(Y1-Y2))^2+(Z1-Z2)^2)

あらかじめ用意した代表色との距離を求め、もっとも距離が近い代表色に置き換える。

今回は実装しなかったけど、「限りなく透明に近いブルー」のような透明度のある画像を考慮するにはalpha値の座標を加えて四次元空間の距離を求める必要がある。四次元空間の距離・・・なんだか急にオカルトっぽくなってきた。

ソース

具体的なソースはこちら。ジャスト100行。意外と短い。

imgTest.go

package main
import (
    "fmt"
    "image"
    "image/color"
    _ "image/jpeg"
    "image/png"
    "os"
    "strconv"
    "time"
)
var COLOR_SET = [...]string{
    "000000", "FFFFFF", //"808080", "c0c0c0",
    "800000", "FF0000", "808000", "FFFF00",
    "008000", "00FF00", "008080", "00FFFF",
    "000080", "0000FF", "800080", "FF00FF",
}
func main() {
    path := ""
    start := time.Now()
    if len(os.Args) >= 2 {
        path = os.Args[1]
    } else {
        fmt.Println("コマンドライン引数に画像ファイルを指定してください")
        return
    }
    img := getIMG(path)
    newImg := convertColor(img)
    saveImage(newImg)
    end := time.Now()
    fmt.Printf("complete! (%fs)\n", (end.Sub(start)).Seconds())
}
//画像を読み込む
func getIMG(path string) image.Image {
    file, err := os.Open(path)
    defer file.Close()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    img, _, err := image.Decode(file)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    return img
}
//画像を保存する
func saveImage(img image.Image) {
    out, err := os.Create("output.png")
    defer out.Close()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    err = png.Encode(out, img)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}
//画像を変換する
func convertColor(img image.Image) image.Image {
    rect := img.Bounds()
    rgba := image.NewRGBA(rect)
    conv_colors := make([][3]uint8, len(COLOR_SET))
    for i := 0; i < len(COLOR_SET); i++ {
        r, _ := (strconv.ParseUint(COLOR_SET[i][0:2], 16, 0))
        g, _ := (strconv.ParseUint(COLOR_SET[i][2:4], 16, 0))
        b, _ := (strconv.ParseUint(COLOR_SET[i][4:6], 16, 0))
        conv_colors[i] = [3]uint8{uint8(r), uint8(g), uint8(b)}
    }
    for y := 0; y < rect.Size().Y; y++ {
        for x := 0; x < rect.Size().X; x++ {
            r0, g0, b0, _ := img.At(x, y).RGBA()
            r, g, b := uint8(r0), uint8(g0), uint8(b0)
            r, g, b = nearColor(r, g, b, conv_colors)
            rgba.Set(x, y, color.RGBA{r, g, b, 255})
        }
    }
    return rgba
}
//近い色を返す
func nearColor(r0, g0, b0 uint8, conv_colors [][3]uint8) (uint8, uint8, uint8) {
    sel_r, sel_g, sel_b := uint8(0), uint8(0), uint8(0)
    sel_d := 999999.0
    for i := 0; i < len(conv_colors); i++ {
        rx := conv_colors[i][0]
        gx := conv_colors[i][1]
        bx := conv_colors[i][2]
        rd := (float64(r0) - float64(rx)) * (float64(r0) - float64(rx))
        gd := (float64(g0) - float64(gx)) * (float64(g0) - float64(gx))
        bd := (float64(b0) - float64(bx)) * (float64(b0) - float64(bx))
        d := (rd + gd + bd)
        if d <= sel_d {
            sel_r, sel_g, sel_b,sel_d = rx, gx, bx,d
        }
    }
    return sel_r, sel_g, sel_b
}

Githubにもあげた
https://github.com/kurehajime/imgTest/tree/qiita

応用

おまけ。

複雑な色を単純な色に変換するという処理でアニメっぽくなることはわかった。
では逆に単純な色を複雑な色に変換するとしてみたらどうだろう。
つまりはこう。

  1. 画像を複数用意する(変換元画像、テクスチャ画像)
  2. あらかじめ使える色を決める(今回は基本16色から灰色系を除いた14色)
  3. 変換元となる画像を1ドットずつ色を調べる
  4. 使える色の中から一番近い色に変換する
  5. 特定の色のある座標に、テクスチャ画像から色を移す

これを実際にやってみると・・・
red.png

こうなる!かっこいい!!!ファイヤーバード!
output.png

ソースはこちら
https://github.com/kurehajime/imgTest/tree/qiita2

17
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
kurehajime
こころの中のゴロリに「ワクワクさん、プログラミングって面白いね」と喜んでもらうために頑張ります。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
17
Help us understand the problem. What is going on with this article?