top of page

import cv2 as cv
import datetime
from HSL2BGRconvert import HSL2BGR2
import serial
import time

#Cv window setup
nameFeed = 'Live feed'
nameThresh = 'Threshold'
nameColours = 'Output test'
nameGray = 'Gray feed'
colourImg = cv.imread('colours.jpg')

#Camera settings
dthresh = 2                                                                      #parameters for motion detection
minarea = 800
maxarea = 200000
cam = cv.VideoCapture(0)
avg = None
threshavg = None

#Colour setup
nlights = 5
coloursHSL = [[0,0,0] for i in range(nlights)]                #matrix for HSL parameters / fixture
coloursBGR = [[0,0,0] for i in range(nlights)]                #matrix for BGR parameters / fixture
coloursBGR_old = [[0,0,0] for i in range(nlights)]         #used to determine if BGR values have changed (reduces serial bandwidth for dmx)

ncolours = 3                                                                         #resolution for colour detection (vertical)
basecolour = 200                                                                 #colour which the theremin converges to
sensitivity_hue = 400000                                                    #sensitivity of motion detection
sensitivity_brightness = 70000                                          #sensitivity of motion detection
detect_value = [[0]*ncolours for i in range(nlights)]        #matrix to hold integral sum of detection
detect_total = [0]*nlights                                                    #list to hold total integral / fixture
detect_colour = [[basecolour,100,50] for i in range(nlights)]        #matrix to hold HSL colour values calculated by detection

print 'detect_colour start:', detect_colour

mode = 'theremin'                                             #starting mode (currently; theremin, rainbow, light_fade, still)
mode_motion   = 1                                            #type of motion detection interpretation
start         = True
delay_program = 0.001                                    #controls speed of program
delay_display = 0.000                                       #controls the display update frequency
delay_dmx     = 0.01                                          #controls the dmx frequency

next_update = datetime.datetime.now()
next_display = datetime.datetime.now()
next_dmx = datetime.datetime.now()

#Output setting
display = True
dmx_send = True
showIntegral = True
calculate_contours = False
detect_brightness = False

#DMX setup
if dmx_send == True:
    dmxChannels = [[3,2,1],[6,5,4],[9,8,7],[12,11,10],[15,14,13]]
    port = "COM4"
    baud = 9600
    ser = serial.Serial(port, baud)

print '[THEREMIN] Parameters checked'
    
# A small function to make writing DMX signals quicker
def DMX(channel,value):
    ser.write(str(channel) + ',' + str(value) + 'x')

#cv.namedWindow(nameFeed, cv.CV_WINDOW_AUTOSIZE)
#cv.namedWindow(nameGray, cv.CV_WINDOW_AUTOSIZE)
cv.namedWindow(nameThresh, cv.CV_WINDOW_AUTOSIZE)
cv.namedWindow(nameColours, cv.CV_WINDOW_AUTOSIZE)

time.sleep(0.5)

print '[THEREMIN] Entering continuous loop...'
while True:
    timestart = datetime.datetime.now()
    ret_val, img = cam.read()
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    gray = cv.GaussianBlur(gray, (51, 51), 0)
    
    if avg == None:
        avg = gray.copy().astype("float")
    
    frameDelta = cv.absdiff(gray, cv.convertScaleAbs(avg))

    #threshold the delta image, maybe dilate the thresholded image to fill in holes, then find contours on thresholded image
    #thresh = cv.threshold(frameDelta, dthresh, 255, cv.THRESH_BINARY)[1]
    thresh = cv.threshold(frameDelta, dthresh, 255, cv.THRESH_TOZERO)[1]
    #thresh = cv.dilate(thresh, None, iterations=1)
    
    #smooth out threshold image
    if threshavg == None:
        threshavg = thresh.copy().astype("float")
    cv.accumulateWeighted(thresh, threshavg, 0.1 )

    
    if calculate_contours == True:
        (cnts, _) = cv.findContours(thresh.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
        #analyse all areas
        for c in cnts:
            #if the contour is too small, ignore it
            if cv.contourArea(c) < minarea or cv.contourArea(c) > maxarea:
                continue
            #compute the bounding box for the contour, draw it on the frame, update compound centre and update the text
            (x, y, w, h) = cv.boundingRect(c)
            cv.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
            motion = True
            
    if mode == 'theremin':
        time = datetime.datetime.now()
        if time > next_update:
            next_update = datetime.datetime.now() + datetime.timedelta(seconds=delay_program)
            #integral image
            for c in range(0, nlights):
                detect_total[c] = 0
                for r in range(0,ncolours):
                    roi = threshavg[(r * thresh.shape[0] / ncolours):((r+1) * thresh.shape[0] / ncolours), (c * thresh.shape[1] / nlights):((c+1) * thresh.shape[1] / nlights)]
                    sumint = cv.integral(roi)
                    subtotal = int(sumint[sumint.shape[0]-1,sumint.shape[1]-1])
                    #print c, r, total
                    detect_value[c][r] = subtotal
                    detect_total[c] = detect_total[c] + subtotal
            
            if mode_motion == 1:                                #motion controls hue change
                for c in range(0, nlights):
                    detect_colour[c] = [basecolour,100,50]
                    for r in range(0,ncolours):
                        if detect_total[c] <> 0:
                            detect_colour[c][0] = detect_colour[c][0] - r * 360 * detect_value[c][r] / (detect_total[c] + sensitivity_hue)
                    detect_colour[c][0] = int((0+detect_colour[c][0])%360)
                    
                    #reset the colours to base colours
                    if detect_colour[c][0] > (basecolour + 0):
                        detect_colour[c][0] = detect_colour[c][0] - 13
                    elif detect_colour[c][0] < (basecolour - 0):
                        detect_colour[c][0] = detect_colour[c][0] + 24
                    #print c, detect_colour[c][0]
            
            elif mode_motion == 2:                                #motion directly controls hue
                for c in range(0, nlights):
                    for r in range(0,ncolours):
                        target = 0
                        if detect_total[c] <> 0:
                            #print detect_colour, detect_total[c], detect_value[c][0]
                            target = target + int((r*r) *256 * detect_value[c][r] / detect_total[c])
                            
                    print c, target        
                    if detect_colour[c][0] - target < 0:
                        detect_colour[c][0] = detect_colour[c][0] + 2
                    elif detect_colour[c][0] - target > 0:
                        detect_colour[c][0] = detect_colour[c][0] - 2
                        
                    
            
            if detect_brightness == True:                                #motion directly controles hue
                for c in range(0, nlights):
                    #detect_colour[c] = [0,100,50]
                    for r in range(0,ncolours):
                        if detect_total[c] <> 0:
                            detect_colour[c][2] = 25 + (r) * 1 * detect_value[c][r] / sensitivity_brightness
                    detect_colour[c][2] = int(min(detect_colour[c][2],75))
                    if detect_colour[c][2] > 0:
                        detect_colour[c][2] = detect_colour[c][2] - 1

            
    #print the integral value in the feed
    if showIntegral == True:
        for c in range(0,nlights):
            for r in range(0,ncolours):
                cv.putText(thresh, str(detect_value[c][r]), (int(c*thresh.shape[1]/nlights+10), int((r+1)*(thresh.shape[0]-40)/ncolours) - 10), cv.FONT_HERSHEY_SIMPLEX, 0.35, (125, 125, 125), 1)    
            
                
    #Video output
    #cv.imshow(nameFeed, img)
    #cv.imshow(nameGray, gray)
    cv.imshow(nameThresh, thresh)
    cv.accumulateWeighted(gray, avg, 0.5)
    
    #different modes
    if mode == 'theremin':
        for n in range(0,len(detect_colour)):
            coloursBGR[n] = HSL2BGR2(detect_colour[n])    
        
    if mode == 'rainbow':
        if start == True:
            print 'Starting rainbow...'
            coloursHSL = [[20*i,100,50] for i in range(nlights)]
            print coloursHSL
            start = False
        time = datetime.datetime.now()
        if time > next_update:
            for n in range(0,nlights):
                coloursHSL[n][0] = (coloursHSL[n][0] + 5)%360
                coloursBGR[n] = HSL2BGR2(coloursHSL[n])    
            next_update = time + datetime.timedelta(seconds=delay_program)
    
    elif mode == 'light fade':
        if start == True:
            coloursHSL = [[(50*i)%360,100,50] for i in range(nlights)]
            start = False
        time = datetime.datetime.now()
        if time.now() > next_update:
            for n in range(0,nlights):
                coloursHSL[n][2] = (coloursHSL[n][2] + 1)%100
                coloursBGR[n] = HSL2BGR2(coloursHSL[n])    
            next_update = time + datetime.timedelta(seconds=delay_program)
            
    elif mode == 'still':
        if start == True:
            coloursHSL = [[basecolour,100,50] for i in range(nlights)]
            start = False
        time = datetime.datetime.now()
        if time > next_update:
            for n in range(0,len(coloursHSL)):
                coloursBGR[n] = HSL2BGR2(coloursHSL[n])
            next_update = time + datetime.timedelta(seconds=delay_program)
            
    #draw and display the output test
    if display == True:
        time = datetime.datetime.now()
        if time > next_display:
            for i in range(0, len(coloursBGR)):
                cv.rectangle(colourImg, (i*100,0), ((i+1)*100,100), coloursBGR[i], -1)
            cv.imshow(nameColours, colourImg)
            next_display = time + datetime.timedelta(seconds=delay_display)

    #send dmx-signal
    if dmx_send == True:
        time = datetime.datetime.now()
        if time > next_dmx:
            for i in range(0, len(coloursBGR)):
                for k in range(0, 3):
                    if coloursBGR[i][k] != coloursBGR_old[i][k]:
                        DMX(dmxChannels[i][k],coloursBGR[i][k])
                        coloursBGR_old[i][k] = coloursBGR[i][k]
            next_dmx = time + datetime.timedelta(seconds=delay_dmx)
    
    key = cv.waitKey(5)
    if key == ord('q'):        #quit
        print('[THEREMIN] Logging off...')
        cv.destroyAllWindows()
        break
    elif key == ord('z'):    #rainbow
        mode = 'rainbow'
        start = True
    elif key == ord('x'):    #light fade
        mode = 'light fade'
        start = True
    elif key == ord('c'):    #still (basecolour)
        mode = 'still'
        start = True
    elif key == ord('v'):    #theremin
        mode = 'theremin'
        start = True
    elif key == 91:
        delay_program = delay_program + 0.001
        print "program delay:", delay_program
    elif key == 93:
        delay_program = delay_program - 0.001
        if delay_program < 0.001:
            delay_program = 0.001
        print "program delay:", delay_program
    elif key == 44:
        sensitivity_hue = sensitivity_hue + 100000
        print "hue sensitivity:", sensitivity_hue
    elif key == 46:
        sensitivity_hue = sensitivity_hue - 100000
        if sensitivity_hue < 100000:
            sensitivity_hue = 100000
        print "hue sensitivity:", sensitivity_hue    
    elif key == 107:
        sensitivity_brightness = sensitivity_brightness + 10000
        print "brightness_sensitivity:", sensitivity_brightness
    elif key == 108:
        sensitivity_brightness = sensitivity_brightness - 10000
        if sensitivity_brightness < 10000:
            sensitivity_brightness = 10000
        print "brightness sensitivity:", sensitivity_brightness            
        
       

“Scientists have calculated that the chance of anything so patently absurd actually existing are millions to one. But magicians have calculated that million-to-one chances crop up nine times out of ten”.
- Terry Pratchett, Mort

bottom of page