Monthly Archives: September 2015

Xojo: Sketching with Real-time Contrast Preserving Decolorization and Potrace

Sketcher

Always looking for some ways to learn the computer to ‘see’, I came accross two very interesting algorithms: ‘Real-time Contrast Preserving Decolorization’ by Cewu Lu, Li Xu and Jiaya Jia. And Potrace by Peter Selinger

Decolorization – the process to transform a color image to a grayscale one – is a basic tool in digital printing, stylized black-and-white photography, and in many single channel image processing applications. RTCPD is aiming at maximally preserving the original color contrast alleviate a strict order constraint for color mapping based on human vision system.

An example can be seen here:
RTCPD

The second algorithm Potrace(TM) is a tool for tracing a bitmap, which means, transforming a bitmap into a smooth, scalable image.

Example:
potrace

Playing around with both algorithms, I came up with the idea to use them for a project I had in mind for some time: Sketching pictures on my wall using a Pico projector.

First I had to implement both algorithms in Xojo. The result of RTCPR can be found in the attached source code in the RTCPRGB2Gray() method:

Private Sub RTCPRGB2Gray(pic as Picture)
  dim imcols as Integer = pic.Width
  dim imrows as Integer = pic.Height
  dim sRGB as RGBSurface = pic.RGBSurface

  dim Mat(-1,-1) as ABDoubleVector
  redim Mat(imrows-1,imcols-1)
  dim c as Color
  for y as Integer = 0 to  imrows - 1
    for x as Integer = 0 to imcols - 1
      c = sRGB.Pixel(x,y)
      mat(y,x) = new ABDoubleVector(c.Blue, c.Green, c.Red) ' switch to BRG
    next
  next

  dim s as Double = 64.0 / sqrt(imcols*imrows)
  dim cols as Integer = (s * imcols) + 0.5
  dim rows as Integer = (s * imrows) + 0.5

  dim sigma as Double = 0.05
  dim W() as ABDoubleVector = wei()

  dim P() as ABDoubleVector
  dim det() as Double

  dim pos0() as ABDoublePoint
  dim pos1() as ABDoublePoint
  for i as Integer = 0 to cols - 1
    for j as Integer = 0 to rows - 1
      dim x as Integer = (i+0.5) * imcols/cols
      dim y as Integer = (j+0.5) * imrows/rows
      pos0.Append new ABDoublePoint(x,y)
      pos1.Append new ABDoublePoint(x,y)
    next
  next

  pos1.Shuffle

  for i as Integer = 0 to pos0.Ubound
    dim c0 as ABDoubleVector = mat(pos0(i).y, pos0(i).x)
    dim c1 as ABDoubleVector = mat(pos1(i).y, pos1(i).x)

    add c0,c1, P, det
  next

  cols = cols / 2
  rows = rows / 2

  for i as Integer = 0 to cols - 2
    for j as integer = 0 to rows - 1
      dim x0 as integer = (i + 0.5) * imcols / cols
      dim x1 as integer = (i + 1.5) * imcols / cols
      dim y as integer = (j + 0.5) * imrows / rows

      dim c0 as ABDoubleVector = mat(y, x0)
      dim c1 as ABDoubleVector = mat(y, x1)

      add(c0, c1, P, det)
    next
  next

  for i as integer = 0 to cols - 1
    for j as Integer = 0 to rows - 2
      dim x as integer = (i + 0.5) * imcols / cols
      dim y0 as integer = (j + 0.5) * imrows / rows
      dim y1 as integer = (j + 1.5) * imrows / rows

      dim c0 as ABDoubleVector = mat(y0, x)
      dim c1 as ABDoubleVector = mat(y1, x)

      add(c0, c1, P, det)
    next
  next

  dim maxES as Double = -99999999999
  dim bw as Integer
  for i as integer = 0 to w.Ubound
    dim es as Double
    for j as integer = 0 to p.Ubound
      dim l as Double = P(j).x * W(i).x + P(j).y * W(i).y + P(j).z * W(i).z
      dim detM as Double = det(j)

      dim a as double = (L + detM) / sigma
      dim b as double = (L - detM) / sigma

      es = es + log(exp(-a * a) + exp(-b * b))
    next
    es = es / (p.Ubound+1)
    if eS > maxEs then
      maxEs = Es
      bw = i
    end if
  next

  dim result(-1,-1) as Double
  redim result(imrows-1,imcols-1)

  for y as Integer = 0 to  imrows - 1
    for x as Integer = 0 to imcols - 1
      result(y,x) = mat(y,x).z*W(bw).x + mat(y,x).y*w(bw).y+0
      result(y,x) = mat(y,x).x*W(bw).z + result(y,x)*1+0
      sRGB.Pixel(x,y) = rgb(result(y,x),result(y,x),result(y,x))
    next
  next

  bw = 0
End Sub

Potrace is a complete framework to trace an image and converting it to Xojo did take some time. While writing it, I also made some changes to it to optimize the result. One of it was doing my own thresholding using the surrounding pixels to smoothen the edge detection.

Snippet:

  dim v, v1, v2, v3, v4, v5, v6, v7, v8 as Integer
  dim ex,ey, weight, weighttotal, total as Double

  'SISThreshold2 alain
  for y = 1 to H - 2
    for x = 1 to W - 2
      v = sDebugPicRGB.Pixel(x, y).red
      v1 = sDebugPicRGB.Pixel(x+1, y).red
      v2 = sDebugPicRGB.Pixel(x-1, y).red
      v3 = sDebugPicRGB.Pixel(x, y+1).red
      v4 = sDebugPicRGB.Pixel(x, y-1).red
      v5 = sDebugPicRGB.Pixel(x+1, y-1).red
      v6 = sDebugPicRGB.Pixel(x-1, y-1).red
      v7 = sDebugPicRGB.Pixel(x+1, y+1).red
      v8 = sDebugPicRGB.Pixel(x-1, y+1).red
      ex = (v1+v2+v5+v6)/4
      ey = (v3+v4+v7+v8)/4
      if ex > ey then
        weight = ex
      else
        weight = ey
      end if
      weighttotal = weighttotal + weight
      total = total + weight * (v)
    next
  next

  dim  Threshold as Integer = 128

  if weighttotal <> 0 then
    Threshold = total/weighttotal
  end if

  dim ret as Boolean = (sDebugPicRGB.Pixel(0, 0).Red >= Threshold)

  for y = 0 to H
    for x = 0 to W
      c = sDebugPicRGB.Pixel(x, y)
      if c.red >= Threshold then
        Matrix(x+1, y + 1) =true
      else
        Matrix(x+1, y + 1) = false
      end if
    next
  next 

  for x = 0 to MatrixW
    Matrix(x,0) = true
    Matrix(x, MatrixH) = true
  next
  for y = 0 to MatrixH
    Matrix(0,y) = true
    Matrix(MatrixW, y) = true
  next

This gave me all the tools to start tracing an image. But for my project I wanted to have a sort of ‘sketchy’ feeling. For this I needed a couple of things because I wanted the drawing and painting to be done gradually. I had to build it with delays, repetition, randomization and color fading. I wanted to start from a picture and create something like this:
jobs

DrawImage() does all that. It first draws the beziers with some little random mistakes in grey. A gradual painting is then used to fill in the polygons. Finaly it repeats the process with the correct lines in white.

You may notice that a lot of code is redundant, but this is intended because I needed to have enough delays to mimic a more natural feeling.

Here is a demo video showing the result of this project on my wall. It takes about ten seconds before it starts drawing as I had to grab my camera :-). Five different images are drawn.

The project with full source code can be downloaded here

Greetz!

Alwaysbusy

Click here to Donation if you like my work


%d bloggers like this: