ICT 361: Introduction to Robotics
Mr. Seng Theara
By the end of the lesson, student will be able to:
Understand how a joystick works electrically
Explain pull-up button logic
Read analog and digital signals
Map controller inputs to robot movements
Design basic control logic for robots
By the end of the lesson, student will be able to:
4 Buttons
Joystick
Joystick
Joystick
Joystick
ROI helps the robot focus only on what matters.
In this image, the ROI is divided into three regions:
Left | Center | Right
Goal: Keep the line in the center region
4 Buttons
Joystick
4 Buttons
HSV (Hue, Saturation, Value) is like a color wheel + brightness control
1. Hue (H) represent the type of color and measure in degree
Example:
2. Saturation (S) represent controls color intensity
| Value | Meaning |
|---|---|
| 0 | Gray |
| 255 | Full Color |
4 Buttons
3. Value (V) represent the brightness
| Value | Meaning |
|---|---|
| 0 | Black |
| 255 | Bright |
A black line has
HSV Threshold
Lower = (0, 0, 0)
Upper = (180, 255, 80)
1. Hue (H)
We set H value range between 0 to 180 because we don't care about the color. Black has no color
2. Saturation (S)
We set S in the range between 0 to 255. All saturation value allowed because black already has low S but lighting noise may change S slightly
3. Value (V)
V is the most important which decide only the dark pixels are selected. We set V in the range of value between 0 to 80.
Compare Different Objects
| Object | H | S | V | Why |
|---|---|---|---|---|
| black | Any | Low | Low | No light |
| white | Any | Low | High | Bright |
| Gray | Any | Low | Medium | Medium Brightness |
| Red | Low | High | High | Strong Color |
For example
when you do
frame = cap.read()The image from the camera is BGR Format because of the openCV library.
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)from flask import Flask, Response
import cv2
import numpy as np
app = Flask(__name__)
cap = cv2.VideoCapture(0)
cap.set(3, 640)
cap.set(4, 480)
def get_mask(roi):
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
lower = (0, 0, 0)
upper = (180, 255, 80)
mask = cv2.inRange(hsv, lower, upper)
return mask
def generate_frames():
while True:
ret, frame = cap.read()
if not ret:
break
h, w = frame.shape[:2]
top = int(0.6 * h)
roi = frame[top:h, :]
mask = get_mask(roi)
M = cv2.moments(mask)
decision = "NO LINE"
if M["m00"] > 0:
cx = int(M["m10"] / M["m00"])
cv2.circle(roi, (cx, 50), 8, (0, 0, 255), -1)
# =========================
# 4. Decision
# =========================
if cx < w // 3:
decision = "LEFT"
elif cx > 2 * w // 3:
decision = "RIGHT"
else:
decision = "FORWARD"
cv2.rectangle(frame, (0, top), (w, h), (0, 255, 0), 2)
cv2.line(frame, (w//3, top), (w//3, h), (0, 0, 255), 2)
cv2.line(frame, (2*w//3, top), (2*w//3, h), (255, 0, 0), 2)
cv2.putText(frame, decision, (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2)
# =========================
# 6. Send to web
# =========================
_, buffer = cv2.imencode('.jpg', frame)
frame_bytes = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
@app.route('/')
def video():
return Response(generate_frames(),
mimetype='multipart/x-mixed-replace; boundary=frame')
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)