レンズレスカメラ

twitter見てたらこんな記事が流れてきまして。

http://www.gizmodo.jp/2013/06/post_12461.html
ベル研究所、レンズのない1画素カメラ実現。ピンぼけもない」

ランダムなフィルタを通して一画素カメラでたくさんデータを取って、最後に合わせてやるとちゃんとした画像ができ上がるというお話らしい。

なんとなく感覚的には合点が行くけど、実際に試してみるとどんなもんなんじゃろと思ったのでやってみました。これでいいのかどうかよく分からないけど。

# ...前略...

shot_image = Square.new
real_image = Image.new

# 10万回試行
100000.times do
    # ランダムフィルタ生成
    filter = Filter.new

    # valはフィルタがオープンになっている部分の明度の平均値
    # 元記事の言うところの「一画素カメラ」の値
    val = filter.filter real_image

    filter.filtered_each do |x, y|
        # フィルタがオープンになっていた部分の値をvalだけ増やす
        # 本物はこの辺できっといろいろ工夫してる
        shot_image[x, y] += val
    end 
end

# 各画素を8ビット長に正規化
shot_image.normalize! 0..255

# 画像生成
shot_image.to_png 'shot.png'


で、結果。

元画像

復元画像

ほほう。なかなかうまくいくもんですね。ちなみに試行回数を変えるとこんな感じ。今回の単に積算するだけのやり方だとたかが10×10の画像でも10万回くらいは繰り返さないとダメみたい。

しかし、結局何度も撮るなら一画素カメラで例えば左上から右下に順に走査して行った方が楽な気がするけど、そういう話でもないんですかねー。


ソースコード全文

require 'chunky_png'

class Square
  attr_reader :width, :height

  def initialize(x=10, y=10)
    @width = x
    @height = y
    @values = Array.new y do
      Array.new x, 0
    end
  end

  def min
    @values.flatten.sort.first
  end

  def max
    @values.flatten.sort.last
  end

  def normalize_pixel(x, y, my_min, my_max, min_value, max_value)
    ((self[x, y] - my_min) / (my_max - my_min) * max_value).floor + min_value
  end

  def normalize! range
    my_min = min
    my_max = max
    min_value = range.begin
    max_value = range.end
    each do |x, y|
      self[x, y] = normalize_pixel x, y, my_min, my_max, min_value, max_value
    end
    self
  end

  def [](x, y)
    @values[y][x]
  end

  def []=(x, y, v)
    @values[y][x] = v
  end

  def each(&block)
    @height.times do |y|
      @width.times do |x|
        block.call x, y
      end
    end
  end

  def to_png(filename='filename.png')
    png = ChunkyPNG::Image.new(@width, @height, ChunkyPNG::Color::TRANSPARENT)
    each do |x, y|
      color = self[x, y]
      png[x, y] = ChunkyPNG::Color.rgba(color, color, color, 255)
    end
    png.save(filename, :interlace => true)
  end

  def to_s
    @values.to_s
  end
end

class Image < Square

  def initialize(x=10, y=10)
    super
    each do |x, y|
      self[x, y] = x < y ? 255 : 0
    end
  end
end

class Filter < Square
  def initialize(x=10, y=10)
    super
    each do |x, y|
      self[x, y] = Random.rand(2)
    end
  end

  def filter(image)
    count = 0
    sum = 0
    filtered_each do |x, y|
      count += 1
      sum += image[x, y]
    end
    count == 0 ? 0 : sum.to_f / count
  end

  def filtered_each(&block)
    each do |x, y|
      block.call(x, y) unless self[x, y] == 0
    end
  end
end

shot_image = Square.new
real_image = Image.new

100000.times do
  filter = Filter.new
  val = filter.filter real_image
  filter.filtered_each do |x, y|
    shot_image[x, y] += val
  end
end
shot_image.normalize! 0..255
shot_image.to_png 'shot.png'