Because the Python code is so long, the easiest thing to do is explain it as simply as possible. An image is either taken or loaded from the working directory. Then the image is processed by going through the following steps:

  1. Edges are found using Canny edge detection from a resized grayscale image
    1. I added a trackbar for the upper and lower thresholds to make it easier to create these values, seeing as every image is unique
    2. The image is resized because the time to computer the inverse kinematics, even when shrinking it down, increases exponentially with the size of the image
  2. After the image is processed, the pixel coordinates of all white pixels (edges) are found in drawing order
    1. This is done by indexing through the image until a white pixel (edge) is found
    2. After a pixel is found, that pixel is appended to pix_list and the neighboring pixels (3x3 neighborhood) are search to find a surrounding pixel
      1. If a neighboring pixel is found, follow_pix( ) is called and and the process of looking at the neighboring pixels is continued until the end of the line/curve
      2. This creates an array with all of the pixel coordinates in drawing order, so that once a pixel is found (which means it's the start of a line/curve), the line/curve is followed until the end is reached, and the next starting pixel is found
  3. After pixels are added to the list (pix_list), they are immediately deleted so as not to be drawn twice
    1. Every pixel is added to pix_list in order and saved to be entered through the inverse kinematics function
  4. The function pix_to_ik( ) computes the inverse kinematics of every single pixel; the more edges, the more computing time; typically 15,000 pixels takes about 7 or 8 minutes
    1. The atan2( , ) function is used in place of acos( ) so as to avoid any issues with ambiguity
  5. After all pixels are converted to Cartesian then angles, a SQL table is created; this isn't absolutely necessary, in fact it might make more sense to just send them to a microcontroller via SPI

a. However, this was the best method for us because the microcontroller we had to use is a piece of shit and this is what we had to do to make it work

b. Next time, given the chance, we would have used an Arduino and servo.h