diff --git a/openstagecontrol/stepper_control_gui.json b/openstagecontrol/stepper_control_gui.json
index ebdcff1..51463af 100644
--- a/openstagecontrol/stepper_control_gui.json
+++ b/openstagecontrol/stepper_control_gui.json
@@ -40,7 +40,7 @@
"height": 312,
"label": false,
"color": "auto",
- "css": "> .panel {\n background-color: black;\n border: 2px solid grey;\n}\n:host {\n top:calc(50% - 156rem);\n left:calc(50% - 251rem);\n}",
+ "css": "> .panel {\n background-color: black;\n border: 2px solid grey;\n}\n:host {\n top:calc(50% - 156rem);\n left:calc(50% - 251rem);\n z-index:15;\n}",
"scroll": true,
"border": true,
"default": "",
@@ -639,6 +639,22 @@
}
],
"tabs": []
+ },
+ {
+ "type": "frame",
+ "top": "auto",
+ "left": "auto",
+ "id": "frame_3",
+ "linkId": "",
+ "width": 600,
+ "height": 400,
+ "label": "auto",
+ "css": "> .frame {\n background-color: black;\n border: 2px solid grey;\n}\n:host {\n top:calc(50% - 200rem);\n left:calc(50% - 800rem);\n z-index: 10;\n}",
+ "border": true,
+ "default": "",
+ "value": "http://10.0.0.5:5000",
+ "address": "/frame_3",
+ "preArgs": ""
}
],
"tabs": [],
diff --git a/python/templates/index.html b/python/templates/index.html
new file mode 100644
index 0000000..e9ee49a
--- /dev/null
+++ b/python/templates/index.html
@@ -0,0 +1,9 @@
+
+
+ Video Streaming Demonstration
+
+
+ Video Streaming Demonstration
+
+
+
diff --git a/python/vernier_tracker.py b/python/vernier_tracker.py
index cece75b..712f880 100644
--- a/python/vernier_tracker.py
+++ b/python/vernier_tracker.py
@@ -1,12 +1,24 @@
#This is a proof of concept for motion tracking of the vernier in very early stages
-# TODO: stabilize the tracker and connect the plumbing via OSC to the SuperCollider app
-# and get the stream to feed to the Open Stage Control GUI for calibration
import cv2
import sys
+from pythonosc.udp_client import SimpleUDPClient
+from flask import Flask, render_template, Response
+import threading
+import argparse
+
+outputFrame = None
+lock = threading.Lock()
+
+app = Flask(__name__)
+
+ip = "127.0.0.1"
+port = 57120
+
+client = SimpleUDPClient(ip, port) # Create client
# Read video (eventually will be the live capture from the camera)
-video = cv2.VideoCapture("/home/mwinter/Sketches/a_history_of_the_domino_problem/recs/a_history_of_the_domino_problem_final_documentation_hq.mp4")
+video = cv2.VideoCapture("/home/mwinter/Portfolio/a_history_of_the_domino_problem/a_history_of_the_domino_problem/recs/a_history_of_the_domino_problem_final_documentation_hq.mp4")
# Exit if video not opened.
if not video.isOpened():
@@ -15,85 +27,136 @@ if not video.isOpened():
# Read first frame.
video.set(cv2.CAP_PROP_POS_FRAMES, 5000)
-ok, frame = video.read()
+ok, initFrame = video.read()
if not ok:
print('Cannot read video file')
sys.exit()
-# Define an initial bounding box
-#bbox = (287, 23, 86, 320)
-
-frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
+#frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
#frame = cv2.GaussianBlur(frame,(5,5),cv2.BORDER_DEFAULT)
-r1 = cv2.selectROI('Tracking', frame)
-r2 = cv2.selectROI('Tracking', frame)
-#r = (606, 448, 35, 177);
-#cv2.destroyWindow('select')
-#print(r)
-crop1 = frame[int(r1[1]):int(r1[1]+r1[3]), int(r1[0]):int(r1[0]+r1[2])]
-crop2 = frame[int(r2[1]):int(r2[1]+r2[3]), int(r2[0]):int(r2[0]+r2[2])]
+# all this for selecting ROI
+#xROI = cv2.selectROI('Tracking', initFrame)
+#yROI = cv2.selectROI('Tracking', initFrame)
+#print(xROI)
+#print(yROI)
+#xFine = (xROI[0], xROI[1], xROI[2], xROI[3] / 2)
+#xCourse = (xROI[0], xROI[1] + (xROI[3] / 2), xROI[2], xROI[3] / 2)
+#yFine = (yROI[0], yROI[1], yROI[2] / 2, yROI[3])
+#yCourse = (yROI[0] + (yROI[2] / 2), yROI[1], yROI[2] / 2, yROI[3])
+#print(xFine)
+#print(yFine)
+xFine = (848, 187, 225, 21.0)
+yFine = (604, 402, 20.5, 276)
-while True:
- # Read a new frame
- ok, frame = video.read()
- if not ok:
- break
+frameCountMod = 0
+centroidX = [0, 0]
+centroidY = [0, 0]
+
+def track(frame, ROI, centroid, update):
+ if(update):
+ crop = frame[int(ROI[1]):int(ROI[1]+ROI[3]), int(ROI[0]):int(ROI[0]+ROI[2])]
+ crop = cv2.cvtColor(crop, cv2.COLOR_RGB2GRAY)
+ crop = cv2.GaussianBlur(crop,(7,7),cv2.BORDER_DEFAULT)
+
+ #ret, thresh = cv2.threshold(crop, 100, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
+ ret,thresh = cv2.threshold(crop, 50, 255, 0)
+ M = cv2.moments(thresh)
- crop1 = frame[int(r1[1]):int(r1[1]+r1[3]), int(r1[0]):int(r1[0]+r1[2])]
- crop1 = cv2.cvtColor(crop1, cv2.COLOR_RGB2GRAY)
- crop1 = cv2.GaussianBlur(crop1,(5,5),cv2.BORDER_DEFAULT)
-
- crop2 = frame[int(r2[1]):int(r2[1]+r2[3]), int(r2[0]):int(r2[0]+r2[2])]
- crop2 = cv2.cvtColor(crop2, cv2.COLOR_RGB2GRAY)
- crop2 = cv2.GaussianBlur(crop2,(5,5),cv2.BORDER_DEFAULT)
-
- ret1, thresh1 = cv2.threshold(crop1, 230, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
- cnts1 = cv2.findContours(thresh1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- cnts1 = cnts1[1]
-
- ret2, thresh2 = cv2.threshold(crop2, 230, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
- cnts2 = cv2.findContours(thresh2.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- cnts2 = cnts2[1]
-
- center = None
-
- for c in cnts1[0:2]:
- # calculate moments for each contour
- M = cv2.moments(c)
# calculate x,y coordinate of center
if M["m00"] != 0:
- cX = int(M["m10"] / M["m00"])
- cY = int(M["m01"] / M["m00"])
+ centroid[0] = int(M["m10"] / M["m00"])
+ centroid[1] = int(M["m01"] / M["m00"])
#else:
# cX, cY = 0, 0
#print(cY)
- cv2.circle(frame, (int(r1[0]) + cX, int(r1[1]) + cY), 5, (255, 255, 255), -1)
+ cv2.circle(frame, (int(ROI[0]) + centroid[0], int(ROI[1]) + centroid[1]), 5, (255, 255, 255), -1)
+
+def detect_motion():
+ # grab global references to the video stream, output frame, and
+ # lock variables
+ global vs, outputFrame, lock
+
+ frameCountMod = 0
+ centroidX = [0, 0]
+ centroidY = [0, 0]
+ """Video streaming generator function."""
+ while True:
+ # Read a new frame
+ ok, frame = video.read()
+ if not ok:
+ break
+
+ if(frameCountMod == 0):
+ track(frame, xFine, centroidX, True)
+ track(frame, yFine, centroidY, True)
+ xPos = (centroidX[0] / xFine[2]) * 2 - 1
+ yPos = (centroidY[1] / yFine[3]) * 2 - 1
+ client.send_message("/trackerpos", [xPos, yPos])
+ else:
+ track(frame, xFine, centroidX, False)
+ track(frame, yFine, centroidY, False)
+
+
+ frameCountMod = (frameCountMod + 1) % 10
+
+ cv2.rectangle(frame, (int(xFine[0]), int(xFine[1])), (int(xFine[0]+int(xFine[2])),int(xFine[1]+xFine[3])), (255, 255, 255), 5)
+ cv2.rectangle(frame, (int(yFine[0]), int(yFine[1])), (int(yFine[0]+int(yFine[2])),int(yFine[1]+yFine[3])), (255, 255, 255), 5)
+
+ # Display result
+ #cv2.imshow("Tracking", frame)
+ #cv2.imshow("Crop", crop)
- # only proceed if at least one contour was found
- if len(cnts2) > 0:
- # find the largest contour in the mask, then use
- # it to compute the minimum enclosing circle and
- # centroid
- c = max(cnts2, key=cv2.contourArea)
- ((x, y), radius) = cv2.minEnclosingCircle(c)
- M = cv2.moments(c)
- center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
-
- # only proceed if the radius meets a minimum size
- if radius > 5:
- # draw the circle and centroid on the frame,
- # then update the list of tracked points
- cv2.circle(frame, (int(x), int(y)), int(radius), (0, 255, 255), 2)
- cv2.circle(frame, center, 5, (0, 0, 255), -1)
+ with lock:
+ outputFrame = frame.copy()
+
+ # Exit if ESC pressed
+ #k = cv2.waitKey(1) & 0xff
+ #if k == 27 :
+ # cv2.destroyWindow('Tracking')
+ # break
- # Display result
- cv2.imshow("Tracking", frame)
- #cv2.imshow("Crop", crop)
-
- # Exit if ESC pressed
- k = cv2.waitKey(1) & 0xff
- if k == 27 :
- cv2.destroyWindow('Tracking')
- break
+
+@app.route('/')
+def index():
+ """Video streaming home page."""
+ return render_template('index.html')
+
+
+def generate():
+ # grab global references to the output frame and lock variables
+ global outputFrame, lock
+
+ # loop over frames from the output stream
+ while True:
+ # wait until the lock is acquired
+ with lock:
+ # check if the output frame is available, otherwise skip
+ # the iteration of the loop
+ if outputFrame is None:
+ continue
+
+ # encode the frame in JPEG format
+ (flag, encodedImage) = cv2.imencode(".jpg", outputFrame)
+
+ # ensure the frame was successfully encoded
+ if not flag:
+ continue
+
+ # yield the output frame in the byte format
+ yield(b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
+ bytearray(encodedImage) + b'\r\n')
+
+
+@app.route('/video_feed')
+def video_feed():
+ """Video streaming route. Put this in the src attribute of an img tag."""
+ return Response(generate(),mimetype='multipart/x-mixed-replace; boundary=frame')
+
+
+if __name__ == '__main__':
+ t = threading.Thread(target=detect_motion)
+ t.daemon = True
+ t.start()
+ app.run(host='10.0.0.5', threaded=True)
diff --git a/supercollider/installation_control.scd b/supercollider/installation_control.scd
index 2cec82f..36ffad9 100644
--- a/supercollider/installation_control.scd
+++ b/supercollider/installation_control.scd
@@ -4,7 +4,7 @@
var imageDist, micronsPerStep, automation, imgPositions, curPos, tarPos,
netAddress, serialPort, serialListener,
moveTo, jogControl, jogHorizontal, jogVertical,
-imgSelect, imgCalibrate, automate, lastSelect;
+imgSelect, imgCalibrate, automate, lastSelect, trackerPos;
// init global vars
imageDist = 300; // in microns
@@ -27,7 +27,7 @@ netAddress = NetAddr.new("127.0.0.1", 7777);
byte = ~serialPort.read;
if(byte==13, {
if(str[1].asString == "[", {
- valArray = str.asString.interpret.postln;
+ valArray = str.asString.interpret; //.postln;
curPos = Point.new(valArray[0], valArray[1]);
limitSwitchNeg = valArray[2];
limitSwitchPos = valArray[3];
@@ -152,6 +152,10 @@ automate = OSCFunc({arg msg;
});
9.do({arg i; netAddress.sendMsg("/STATE/SET", "{img_" ++ (i + 1).asString ++ "_select: " ++ (i + 1).neg ++ "}")});
}, '/automate', netAddress);
+
+trackerPos = OSCFunc({arg msg;
+ msg.postln;
+}, '/trackerpos');
)
~serialPort.close
~serialPort = SerialPort.new("/dev/ttyACM0", baudrate: 115200, crtscts: true);