Search code examples
pythonfunctiondrawingbeziercurve

How to turn a hand drawn curve (vector drawing or paint etc..) into a function?


I would like to be able to draw a curve like this sample, and then turn that into a function that approximates the curve. Some pseudo python code might look like

>> drawing = file.open('sample_curve.jpg')
>> approx_function = function_from_drawing(drawing, x_scale=10, y_scale=5, y_offset=3)
>> print approx_function(2.2) 
5.3

I figure you might be able to pick a pixed in each column that has the line going through it (and decide to use the lowest one if there is more than one) and and then smooth that out with bezier curves. I guess what I'm wondering is what is does something like this exist already (of course it does...) and how can I integrate this with python. Also, how would I go about implementing this in python if I can't find something that is up to snuff? Would it be easier to use a vector drawing instead?


Solution

  • this is my preliminary hacky solution:

    from PIL import Image
    import numpy as np
    
    class Pic_Function():
        def __init__(self, picture_path):
            self.picture = Image.open(picture_path)
            self.pixels = self.picture.load()
            self.columns = []
            # is there really no image method to get a numpy array of pixels?
            for i in range(self.picture.size[0]):
                self.columns.append([self.pixels[i,j] for j in range(self.picture.size[1])])
            self.first_black = []
            for i in self.columns:
                try:
                    self.first_black.append(self.picture.size[0] - i.index((0,0,0)))
                except ValueError:
                    self.first_black.append(None)
            self.max, self.min = max(self.first_black), min([j for j in self.first_black if j != None])
    
        def at(self, x):
            upper_idx = int(math.ceil(x))
            lower_idx = upper_idx - 1
            try:
                upper = self.first_black[upper_idx]
                lower = self.first_black[lower_idx]
            except IndexError:
                return 0
            if None in [upper, lower]:
                return 0
    
            up_weight, low_weight = abs(upper-x), abs(lower-x)
            return (up_weight*upper + low_weight*lower)/(up_weight + low_weight)
    
        def norm_at(self, x, length):
            un_normed = self.at(x*self.picture.size[0]/length)
            return (un_normed - self.min)/self.max