OpenCV update, Heaviside Step Function, deriving parametric functions of shapes, Sobel filter

I found this video. I hadn’t watched it yet. Very good; I like Freeman’s acting. Recently I watched “Seven”. Also good, of the same genre.

I still believe that the Wolfram people first tracing the image, using posterize technique etc. and then simplify the beziers, to obtain the points/ needed in their equations. So I immersed myself in the study of Bézier curves/splines, and found a good paper here. ( I have read in the comment there that you are planning to write a 2D/3D Bezier library in Java. Looking forward to that! ) So I have tried to write a code to transform a bitmap shape into a vectorized one, with the least points possible. I started to write a rasterizing code, but then I read a question about the detection of a shape where @jb4x solved it by using the OpenCV lib, which does the job perfectly. Then I minimized the number of points by just skipping points, but with poor results (42 ). Optimizing using Ramer Douglas Peucker algorithm gave a more consistent result. (I’m so happy that Processing has so many resources to study, like Daniel Shiffman’s and yours as well). Although the code can be useful as a tool for converting bitmaps to vector shapes, it’s not efficient enough for me. The twitter shape uses 13 points/curves (13 circle arcs), meaning 52 floats; the same amount of quotients that the twitter Wolfram function uses. I’ve tried to optimize further, by calculating the difference of the angle of the two lines attached to each point, storing them in an array, and by keeping track of their indexes of the maximum values, only removing points at the widest angles, preserving the sharper. But without success. Now, I’ve run out of ideas and would appreciate suggestions on how to go further on my quest to write functions of irregular shapes.

// Ramer Douglas Peucker algorithm by teachings Daniel Shiffman  https://youtu.be/nSYw9GrakjY

import gab.opencv.*;
import java.awt.Point;
import java.util.Queue;
import java.util.LinkedList;
import java.util.*;

float[][] cc = {{210, 168}, {430, 30}, {493, -35}, {410, 10}, {450, -75}, {400, 148}, {292, -75}, {185, 135}, {130, 110}, {180, 216}, {130, 215}, {210, 280}, {70, 185}}; // circle center
float[][] ca = {{-.15, .5}, {.18, .3}, {0.02, .12}, {.22, .35}, {.08, .16}, {.66, 1.56}, {-.29, -.02}, {-.67, -.23}, {-.18, -.08}, {-.54, -.08}, {-.07, .06}, {-.38, 0}, {0, .2}}; // circle arc {start,stop}
int[] radius = {300, 148, 160, 150, 177, 105, 255, 110, 110, 110, 110, 110, 250};

PImage img, imgf;
ArrayList<PVector> all_points = new ArrayList<PVector>();
ArrayList<PVector> rdp_points = new ArrayList<PVector>();
ArrayList<Contour> contours;
int index, total_points;
PShape s;
float[][] knots;
float loc = 15, num, angle, epsilon = 1;
OpenCV ocv;

void setup() {
  size(620, 520);
  background(0);
  textSize(18);
  stroke(255);
  drawShapeBorder();
  fillShape();
  stroke(0);
  drawShapeBorder();
  //sharp angle test
  //star(width/2, height/2, 60, 140, 5); 
  //fill(255);
  //fillShape();   
  ocv = new OpenCV(this, img);
  strokeWeight(1);
  contours = ocv.findContours();
  println("number of contours = "+contours.size()); // make sure to have only 1 !
  background(255);
  Contour contour = contours.get(1); // write 0 for the star test 
  total_points = contour.getPoints().size();
  knots = new float[total_points][2];
  for (PVector point : contour.getPoints()) {
    point = new PVector(point.x, point.y);
    knots[index][0] = point.x;
    knots[index][1] = point.y;
    index++;
  }
  PVector[]ak = new PVector[knots.length];
  for (int v = 0; v <= knots.length-1; v++) {
    ak[v] = new PVector(knots[v][0], knots[v][1]);
  }
  for (PVector point : contour.getPoints()) {
    point(point.x, point.y);
    point = new PVector(point.x, point.y);
    all_points.add(point);
  }
}

void draw() {
  background(255);
  rdp_points = new ArrayList<PVector>();
  int total = all_points.size();
  PVector start = all_points.get(0); 
  PVector end = all_points.get(total-1);
  rdp_points.add(start);
  rdp(0, total-1, all_points, rdp_points);
  rdp_points.add(end);
  stroke(220, 220, 255);
  strokeWeight(4);
  noFill();
  beginShape();
  for (PVector v : all_points) {
    vertex(v.x, v.y);
  }  
  endShape();
  stroke(0);
  strokeWeight(1);
  beginShape();
  PVector v1 = new PVector();
  v1 = rdp_points.get(rdp_points.size()-1);
  curveVertex(v1.x, v1.y); 
  for (PVector v : rdp_points) {
    curveVertex(v.x, v.y);
    fill(255, 0, 0);
    ellipse(v.x, v.y, 5, 5);  
  }  
  PVector v2 = new PVector();
  v2 = rdp_points.get(0);
  curveVertex(v2.x, v2.y);
  noFill();
  endShape();
  fill(0);
  text("epsilon: " + nf(epsilon, 2, 2), 150, 80);
  text("Total Points: " + (rdp_points.size()-1), 50, 50);
  drawSlider();
  fill(0);
  text("Print", 510, 430);  
  noFill();
  strokeWeight(2);
  rect(490, 410, 80, 28);
}

void mousePressed() {
  if (mouseX > 490 && mouseY > 410 && mouseY < 438) {
    println();
    print("float[] x = {"); 
    for (PVector v : rdp_points) {     
      print(v.x+", ");
    }
    print("};");
    println();
    print("float[] y = {"); 
    for (PVector v : rdp_points) {
      print(v.y+", ");
    }
    print("};");
  }
}

void mouseDragged() {
  loc = mouseX;
  if (loc < 15) loc = 15;
  if (loc > width-15) loc = width-15;
  epsilon =  int(map(loc-10.55, 0.0, width, 1.0, 17.0));
}

void rdp(int startIndex, int end_index, ArrayList<PVector> all_points, ArrayList<PVector> rdp_points) {
  int next_index = findFurthest(all_points, startIndex, end_index);
  if (next_index > 0) {
    if (startIndex != next_index) {
      rdp(startIndex, next_index, all_points, rdp_points);
    }
    rdp_points.add(all_points.get(next_index));
    if (end_index != next_index) {
      rdp(next_index, end_index, all_points, rdp_points);
    }
  }
}

int findFurthest(ArrayList<PVector> points, int a, int b) {
  float record_distance = -1;
  PVector start = points.get(a);
  PVector end = points.get(b);
  int furthest_index = -1;
  for (int i = a+1; i < b; i++) {
    PVector current_point = points.get(i);
    float d = lineDist(current_point, start, end);
    if (d > record_distance) {
      record_distance = d;
      furthest_index = i;
    }
  }
  if (record_distance > epsilon) {
    return furthest_index;
  } else {
    return -1;
  }
}

float lineDist(PVector c, PVector a, PVector b) {
  PVector norm = scalarProjection(c, a, b);
  return PVector.dist(c, norm);
}

PVector scalarProjection(PVector p, PVector a, PVector b) {
  PVector ap = PVector.sub(p, a);
  PVector ab = PVector.sub(b, a);
  ab.normalize(); // Normalize the line
  ab.mult(ap.dot(ab));
  PVector normal_point = PVector.add(a, ab);
  return normal_point;
}

void drawShapeBorder() {
  float step = 2*PI/60;
  float px = 0, py = 0;
  for (int i = 0; i < 13; i++) {
    push();
    translate(cc[i][0], cc[i][1]);
    int j = 0;
    for (float t = ca[i][0]*PI; t < ca[i][1]*PI+step; t += step) {
      float x = radius[i]*sin(t);
      float y = radius[i]*cos(t);
      if (j == 0) { 
        px = x; 
        py = y;
      }  
      line(x, y, px, py);
      px = x; 
      py = y; 
      j++;
    }
    pop();
  }
}

void fillShape() {
  img = get();
  img.loadPixels(); 
  Queue<Point> queue = new LinkedList<Point>();
  queue.add(new Point(width/2, height/2));
  while (!queue.isEmpty()) {
    Point p = queue.remove();
    if (check(p.x, p.y)) {     
      queue.add(new Point(p.x, p.y-1)); 
      queue.add(new Point(p.x, p.y+1)); 
      queue.add(new Point(p.x-1, p.y)); 
      queue.add(new Point(p.x+1, p.y));
    }
  }
  img.updatePixels();
}

boolean check(int x, int y) {
  if (x < 0 || y < 0 || y >= img.height || x >= img.width) return false;
  int pp = img.pixels[x+(y*img.width)];
  if (pp != color(0)) return false; 
  img.pixels[x + (y * img.width)] = color(255); 
  return true;
}

void drawSlider() {
  push();
  noStroke();
  fill(255);
  rect(0, height-30, width, 30); 
  fill(200, 200, 255);
  rect(10, height-15, width-20, 5); 
  fill(150, 150, 255);
  ellipse(loc, height-13, 15, 15);
  pop();
}

void star(float x, float y, float radius1, float radius2, int npoints) {
  float angle = TWO_PI / npoints;
  float halfAngle = angle/2.0;
  beginShape();
  for (float a = 0; a < TWO_PI; a += angle) {
    float sx = x + cos(a) * radius2;
    float sy = y + sin(a) * radius2;
    vertex(sx, sy);
    sx = x + cos(a+halfAngle) * radius1;
    sy = y + sin(a+halfAngle) * radius1;
    vertex(sx, sy);
  }
  endShape(CLOSE);
}

2 Likes