λ³Έ νλ‘μ νΈλ Raspberry Pi 5, PiCamera2, κ·Έλ¦¬κ³ Arduino Unoλ₯Ό κΈ°λ°μΌλ‘ ν WebRTC μ€μκ° μμ μ€νΈλ¦¬λ° λ° λΌμΈνΈλ μ΄μ± μμ¨μ£Όν RCμΉ΄λ₯Ό ꡬννλ κ²μ λͺ©νλ‘ ν©λλ€.
PiCamera2κ° μΊ‘μ²ν μ€μκ° μμμOpenCVλ‘ μ²λ¦¬νμ¬ λΌμΈμ μΈμνκ³ , μ€μ¬ μ’νλ₯Ό κ³μ°ν©λλ€.- ν΄λΉ μ’νλ μλ¦¬μΌ ν΅μ μ ν΅ν΄ Arduinoμ μ λ¬λλ©°, Arduinoλ μ΄λ₯Ό λ°νμΌλ‘ λͺ¨ν°(ESC)μ μ보λ₯Ό μ λ°νκ² μ μ΄νμ¬ μ μ λ°λΌ μ£Όνν©λλ€.
- RC μ‘°μ’ κΈ°(CH1, CH2, CH6)λ₯Ό ν΅ν΄ μλ μ μ΄ λν κ°λ₯νλ©°, CH6 μ€μμΉλ‘ μμ¨/μλ λͺ¨λλ₯Ό μ νν μ μμ΅λλ€.
- ν₯ν μ΄μν μΌμ, LIDAR λ± λ€μν νμ₯μ κ³ λ €ν λͺ¨λν μ€κ³κ° μ μ©λμμ΅λλ€.
| μ΄λ¦ | μν λ° κΈ°μ¬ λ΄μ© |
|---|---|
| μ€μ°½ν | μμ€ν
μ 체 μ€κ³ λ° ν΅ν© PiCamera2 μμμ²λ¦¬ μκ³ λ¦¬μ¦ κ΅¬ν Arduino PWM μ μ΄ μκ³ λ¦¬μ¦ κ΅¬ν μμ¨μ£Όν λͺ¨λμμμ μ€νμ κΈ°λ° μ‘°ν₯ μκ³ λ¦¬μ¦ μ΅μ ν |
| μ¬μ°½μ | WebRTC μλ² λ° μΉ μΈν°νμ΄μ€ ꡬν λΌμΈ μ΄ν 볡μ/λμ μκ³ λ¦¬μ¦ κ°λ° μ€μκ° μΉμμΌ ν΅μ μ€κ³ λ° μ°κ²° μμ μ± ν보 aiortc κΈ°λ° WebRTC μλ² μ΅μ ν λ° μ§μ° μκ° κ°μ μ λ΅ μ μ© |
.
βββ arduino/
β βββ hybrid_manual_auto.ino # μλ λ° μμ¨ μ£Όν ν΅ν© μλμ΄λ
Έ μ½λ
βββ README.md # λ³Έ λ¬Έμ
βββ LICENSE # μ€νμμ€ λΌμ΄μ μ€ (MIT)
βββ WebRTC/
β βββ camera_stream_server.py # python WebRTC μλ² μ½λ
β βββ index.html # WebRTC μΉ λ·°μ΄ μ½λ
βββ images/
βββ linetracking.png # μΉ λ·°μ΄ μ΄λ―Έμ§
[RC μ‘°μ’
κΈ° (CH1/CH2/CH6)] [PiCamera2 + Raspberry Pi]
β β
ββ PWM μ
λ ₯ βββ ββββββ
βΌ βΌ
[Arduino Uno] βββ Serial ν΅μ βββ [Line Detection + Offset κ³μ°]
β β β
Servo ESC(PWM) (Python + OpenCV)
βΌ βΌ β
[RC Car] μμ¨/μλ μ£Όν βΌ
[π HTML λ·°μ΄ (index.html)]
β
λΈλΌμ°μ μμ WebRTC + WebSocket μμ
- PiCamera2 + Raspberry Pi: HSV λ§μ€ν¬ κΈ°λ° λΌμΈνΈλ μ΄μ± λ° μμ μ€νΈλ¦¬λ°
- Arduino Uno: PWM μ λ ₯ λμ½λ© λ° μλ¦¬μΌ μμ κΈ°λ° μ보/ESC μ μ΄
- WebRTC μλ²:
aiortc,websocketsκΈ°λ° μ€μκ° μμ μ‘μΆ - LED νΌλλ°±: μ‘°ν₯ λ°©ν₯ λ° μλ μνλ₯Ό μκ°μ μΌλ‘ νμ
- μ
λ ₯ ν΄μλ:
640x480 (RGB888) - μ²λ¦¬ νμ΄νλΌμΈ:
- HSV μκ³΅κ° λ³ν
- κ²μμ λ§μ€νΉ λ° Morphological Closing
- ROI1 (νλ¨) β
cv2.fitLine()κΈ°λ° μ€μ¬ μ’ν κ³μ° - ROI2 (μλ¨) β 보쑰 μμΈ‘ μ 보 μΆμΆ
- μλ¦¬μΌ ν¬λ§·:
center_x,center_y\n - λ―Έκ²μΆ μ
-1,-1\nμ μ‘, 100ms μ΄μ κ°κ²© μ μ§
- CH1: μ‘°ν₯ (Steering)
- CH2: μλ (Throttle)
- CH6: λͺ¨λ μ ν
- μλ¦¬μΌ μμ μ€μ¬ μ’ν κΈ°λ° offset κ³μ°
- offset β λΉμ ν 보μ (steerExpo) β steerRange λ°μ
- Y μ’νμ λ°λΌ μλ μ ν (
cornerSpeedβstraightSpeed) - λ―Έκ²μΆ μ 곑μ νμ§ λ‘μ§ μν
- PWM μ§μ λμ½λ© λ° steerExpo 보μ
- μ νμ§ μλ κ³μ λ°μ
- μ’μ° μ‘°ν₯: μ’/μ° LED μ λ©Έ
- νμ§ μ: μμͺ½ LED μ λ±
λ³Έ μμ€ν μ Raspberry Pi (Python) μμ μμ μ²λ¦¬λ λΌμΈνΈλ μ΄μ± κ²°κ³Όλ₯Ό Arduino (C++) λ‘ μ μ‘νμ¬ μμ¨μ£Όν λμμ μνν©λλ€.
λ¨λ°©ν₯ Serial ν΅μ (RPi β Arduino) κΈ°λ°μΌλ‘ ꡬνλλ©°, ν΅μ ꡬ쑰λ λ¨μνμ§λ§ μ€μκ°μ±κ³Ό μμ μ±μ λμμ μ 곡ν©λλ€.
| νλͺ© | μ€λͺ |
|---|---|
| ν΅μ λ°©ν₯ | Raspberry Pi β Arduino (λ¨λ°©ν₯) |
| ν΅μ μλ | 9600 bps |
| μ μ‘ μ£ΌκΈ° | μ΅μ 100ms μ΄μ (λ²νΌ μ€λ²νλ‘ λ°©μ§ λͺ©μ ) |
| λ°μ΄ν° ν¬λ§· | "center_x,center_y\n" |
| μ μ μμ | 320,410\n |
| λ―Έκ²μΆ μμ | -1,-1\n |
| μλ¬ μ²λ¦¬ | 100ms μ΄μ μμ μμ λλ -1,-1 μμ μ κ°μ§ μ€ν¨λ‘ κ°μ£Ό |
# μ€μ¬ μ’νκ° κ°μ§λμμ κ²½μ°
serial.write(f"{cx},{cy}\n".encode())
# λΌμΈμ μ°Ύμ§ λͺ»νμ κ²½μ°
serial.write("-1,-1\n".encode())cx,cy: λΌμΈ μ€μ¬ μ’ν (μ μν)\n: κ° νλ μμ μ’ λ£λ₯Ό λνλ΄λ μ’ λ¨ λ¬Έμ- μ μ‘ κ°κ²©: μ΅μ 100ms μ΄μ
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
int commaIndex = serialBuffer.indexOf(',');
if (commaIndex > 0) {
lineCenter = serialBuffer.substring(0, commaIndex).toInt();
lineY = serialBuffer.substring(commaIndex + 1).toInt();
lastSerialTime = millis();
lostLineCount = 0;
}
serialBuffer = "";
} else {
serialBuffer += c;
}
}- λ¬Έμ λ¨μλ‘ μ½κΈ° β
\nμμ μ ν¨ν· μμ± ,λ₯Ό κΈ°μ€μΌλ‘ μ’ν λΆλ¦¬ βtoInt()λ‘ λ³ν- μ μ μ’ν μμ μ
lastSerialTimeμ΄κΈ°ν λ° λ‘μ§ μ€ν - λΉμ μ λλ λλ½ μ κ°μ§ μ€ν¨ μ²λ¦¬
if (millis() - lastSerialTime > serialFrameTimeout) {
if (lostLineCount < lostLineThreshold) {
lostLineCount++;
lastSerialTime = millis();
}
if (lostLineCount >= lostLineThreshold) {
lineCenter = -1;
lineY = -1; // κ°μ§ μ€ν¨λ‘ κ°μ£Ό
}
}- μΌμ μκ° μμ μμ μ:
lostLineCountμ¦κ° - μ°μ μ€ν¨(
lostLineThresholdμ΄κ³Ό) β-1,-1λ‘ κ°μ μ²λ¦¬ - μ΄ μνμμλ νμ§ λλ ννΌ λ± μμ λ‘μ§ μ€ν κ°λ₯
| νλͺ© | λ΄μ© |
|---|---|
| μ μ‘ ν¬λ§· | "center_x,center_y\n" (UTF-8 μΈμ½λ©) |
| μ μ‘ μ£ΌκΈ° | 100ms μ΄μ |
| μμ λ°©μ | λ¬Έμ λ¨μ νμ±, μ’
λ¨λ¬Έμ \n κΈ°μ€ |
| μλ¬ μ²λ¦¬ | -1,-1 μ μ‘ λλ timeout λ°μ μ κ°μ§ μ€ν¨ κ°μ£Ό |
-
νλ‘μ νΈ ν΄λ‘
git clone https://github.com/YUNCHANGHYEOK/RC_Project.git cd RC_Project -
κ°μνκ²½ μμ± λ° νμ±ν
κ°μνκ²½ μμ± λ° νμ±ν python3 -m venv venv source venv/bin/activate # Windowsμ κ²½μ°: venv\\Scripts\\activate
-
νμν ν¨ν€μ§ μ€μΉ
pip install opencv-python aiortc websockets picamera2 numpy
-
WebRTC μμ μ²λ¦¬ μλ² μ€ν
source venv/bin/activate # κ°μνκ²½ νμ±ν python camera_stream_server.py # λΌμΈνΈλ μ΄μ± + WebRTC μλ² μ€ν
- Picamera2λ‘ λΌμΈμ μΈμνκ³
- λΌμΈμ μ€μ¬μ’νλ₯Ό μλμ΄λ Έλ‘ μ μ‘
- WebRTC + WebSocketμ ν΅ν΄ λΈλΌμ°μ λ‘ μμ μ€νΈλ¦¬λ°
-
ν΄λΌμ΄μΈνΈ HTML λ·°μ΄ μ€ν
- λ€λ₯Έ ν°λ―Έλμμ λμΌν κ°μνκ²½μ νμ±νν ν, HTML νμΌμ΄ μλ λλ ν 리μμ μλ λͺ λ Ήμ΄ μ€ν:
source venv/bin/activate # κ°μνκ²½ νμ±ν cd html/ # (μ: HTML νμΌμ΄ html ν΄λμ μλ€λ©΄) python -m http.server 8000 # μΉμλ² μ€ν
- HTML λ΄λΆμ ν¬ν¨λ JavaScriptκ° WebSocket (8765)μ ν΅ν΄ λΌμΈ μ€μ¬ μ’νμ μμμ μμ
| μ΄κΈ° λ²μ 1 | μ΄κΈ° λ²μ 2 | μ΄κΈ° λ²μ 3 |
|---|---|---|
![]() |
![]() |
![]() |
| μ΅μ’ λ²μ |
|---|
![]() |
- μ΄λ―Έμ§μλ λ κ°μ μ΄λ‘μ μ¬κ°νμ΄ λ³΄μ΄λ©°, μ΄λ κ°κ° ROI1 (νλ¨)κ³Ό ROI2 (μ€κ° μλ¨)μΌλ‘ μ μΆμ μ μν λΆμ ꡬκ°μ λλ€.
- λ Έλ μ μ ROI1 λ΄μμ κ²μ μ μ μ€μ¬μ κ³μ°ν μ’νλ‘, μ°¨λ μ μ΄μ νμ©λ©λλ€.
- λΉ¨κ° μ μ ROI2 λ΄ μ€μ¬μΌλ‘, μ£Όλ‘ μ μ μ§ν λ°©ν₯ μμΈ‘μ΄λ μ‘°ν₯ νλ¨μ μ°Έκ³ λ©λλ€.
- κ° ROIμ ν½μ λ°μ΄ν°λ₯Ό κΈ°λ°μΌλ‘ μ§μ μ μΆμ νκ³ , μ€μ¬ μ’νλ₯Ό μλμ΄λ Έ λ° WebSocketμΌλ‘ μ μ‘νμ¬ μ μ΄ μμ€ν κ³Ό μ°λλ©λλ€.
-
π― HSV λ§μ€ν¬ κΈ°λ° λΌμΈ κ²μΆ
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower_black = np.array([0, 0, 0]) upper_black = np.array([180, 90, 170]) mask = cv2.inRange(hsv, lower_black, upper_black)
- μΉ΄λ©λΌ μμμμ μ΄λμ΄(κ²μ μ) μ μ μΈμνκΈ° μν΄ BGR μ΄λ―Έμ§λ₯Ό HSVλ‘ λ³νν ν, νΉμ λ²μμ ν΄λΉνλ ν½μ λ§ ν°μμΌλ‘ λ§μ€νΉ
-
π© ROI1μμ μ€μ¬ μ’ν κ³μ°(λΌμΈ μΆμ )
roi = mask[320:400, :] # νλ¨ μμ μλΌλ΄κΈ° ys, xs = np.where(roi == 255) if len(xs) > 100: points = np.array(list(zip(xs, ys))) [vx, vy, x0, y0] = cv2.fitLine(points, cv2.DIST_L2, 0, 0.01, 0.01) t = ((400 - 320) // 2 - y0) / vy cx = int(x0 + vx * t)
- μμ νλ¨ μμ(ROI1)μμ ν°μ ν½μ μμΉλ₯Ό μμ§ν λ€, μ§μ μ νΌν νμ¬ λΌμΈμ μ€μ¬μ’νλ₯Ό κ³μ°
- μ΄ κ°μ RCμΉ΄μ μ£Όν μ€μ¬μ νμ©
-
π μ΄μ μ€μ¬ μ μ§ λ‘μ§(λΌμΈ λ―Ένμ§ λλΉ)
if len(xs) <= 100: lost_counter += 1 if last_valid_center and lost_counter < 10: center_x, center_y = last_valid_center else: center_x = center_y = None
- λΌμΈμ΄ μΌμμ μΌλ‘ κ°μ§λμ§ μλλΌλ, μ΅κ·Ό νμ§λ μ’νλ₯Ό μΌμ νλ μ λμ μ μ§μμΌ RCμΉ΄κ° λΆλλ½κ² μ£Όννλλ‘ ν¨
-
π μλμ΄λ Έλ‘ μ’ν μ μ‘
if center_x is not None: arduino.write(f\"{center_x},{center_y}\\n\".encode()) response = arduino.readline().decode().strip()
- κ°μ§ν μ€μ¬ μ’νλ₯Ό μλμ΄λ Έλ‘ μλ¦¬μΌ ν΅μ μ ν΅ν΄ μ μ‘
- RCμΉ΄μ μ‘°ν₯ λ° ESC μ μ΄λ μ΄ κ°μ κΈ°λ°μΌλ‘ μ²λ¦¬λ¨
-
π‘ WebRTCλ₯Ό ν΅ν μμ μ€νΈλ¦¬λ°
video_frame = VideoFrame.from_ndarray(color_frame, format=\"bgr24\") video_frame.pts = pts return video_frame
- μ²λ¦¬λ νλ μμ WebRTCμ© λΉλμ€ νλ μ κ°μ²΄λ‘ λ³νν λ€, λΈλΌμ°μ λ‘ μ μ‘
- λΈλΌμ°μ μμλ μ€μκ°μΌλ‘ λΌμΈ κ²μΆ μν νμΈ κ°λ₯
-
Arduino IDE μ€μΉ
-
λΌμ΄λΈλ¬λ¦¬ μ€μΉ
PinChangeInterrupt(μ€μΌμΉ > λΌμ΄λΈλ¬λ¦¬ κ΄λ¦¬μμ μ€μΉ)
-
μ λ‘λ μ μ°¨
hybrid_manual_auto.inoμ΄κΈ°- 보λ: Arduino Uno / ν¬νΈ μ€μ
- μ»΄νμΌ ν μ λ‘λ
- λΌμΈ μΈμ μμ
π μΌμ§μ μμ¨μ£Όν
|
π§ͺ μλ² μμ¨μ£Όν
|
β
μ€μ ꡬν μλ£λ μμ¨μ£Όν
|
- Raspberry Pi 5
- PiCamera2
- Arduino Uno
- RC μμ κΈ° + μ‘°μ’ κΈ°
- μ보 λͺ¨ν°, ESC
- Python 3.9+
- OpenCV, aiortc, websockets
- Arduino IDE 1.8+
- PID μ‘°ν₯ μ μ΄: 곑μ μ£Όν μ±λ₯ ν₯μ (μ€μ μ£Όνλ νλ² νΈλμμ λ²μ΄λ¬λ€ λμμ€λ νμ λ°μ -> μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ μ‘°μ )
- μ€μκ° Web UI: λΈλΌμ°μ μμμ μ£Όν μ 보 νμΈ λ° μλ μ‘°μ κΈ°λ₯
- μ μν λΌμΈ μμ κ°μ§: HSV λ²μ μλ μ‘°μ κΈ°λ₯ μΆκ° (λΉλ²μ§μλ λΌμΈμ μ λν μ±ν μ μλλ‘ μ‘°μ )
- λ€μ€ ROI 보μ : μ λ¨μ κ΅¬κ° μμΈ‘ λμ ν₯μ
- μΌμ ν΅ν© νμ₯: μ΄μν, LIDAR λ± μΈλΆ μΌμ μ°λ (μμ μ₯μ λ¬Όμ΄ μλ€λ©΄ μ΄λ₯Ό μΈμνκ³ μ‘°μ ν μ μλλ‘ λ°μ )
- μ±λ₯ μ΅μ ν λ° μλ μ¦κ°: μμμ ν¨μ¨ κ°μ (μλλ₯Ό λμ± λλ €μ λΉ λ₯Έ μ£Όνμ΄ κ°λ₯νλλ‘ μ‘°μ )
μ΄λ² WebRTC κΈ°λ° λΌμΈνΈλ μ΄μ± μμ¨μ£Όν RCμΉ΄ νλ‘μ νΈλ₯Ό μ§ννλ©΄μ λ€μν κΈ°μ μ λμ κ³Ό μ±μ₯μ κ²½νν μ μμμ΅λλ€.
νΉν νλμ¨μ΄μ μννΈμ¨μ΄κ° μ κΈ°μ μΌλ‘ κ²°ν©λ μμ€ν
μ μ€κ³νκ³ κ΅¬ννλ κ³Όμ μμ λ§μ κ²μ λ°°μ μ΅λλ€.
μ λ μμ€ν
μ 체 μ€κ³ λ° ν΅ν©, κ·Έλ¦¬κ³ PiCamera2 μμμ²λ¦¬ μκ³ λ¦¬μ¦ κ΅¬νμ μ§μ€νμ΅λλ€.
νΉν λΌμΈνΈλ μ΄μ± μ μ€νμ
κΈ°λ° μ‘°ν₯ μκ³ λ¦¬μ¦μ μ΅μ ννλ κ³Όμ μμ λ§μ μνμ°©μ€λ₯Ό κ²ͺμμ§λ§,
λμμλ ν
μ€νΈμ λλ²κΉ
μ ν΅ν΄ RCμΉ΄κ° μμ μ μΌλ‘ λΌμΈμ λ°λΌ μ£Όννλ λͺ¨μ΅μ 보며 ν° λ³΄λμ λκΌμ΅λλ€.
μ€μ λ‘ ν루 μ’
μΌ μ½λλ₯Ό μμ νλ©΄μ, νλ‘κ·Έλλ¨Έμκ² μμ΄ κ°λ°λΏλ§ μλλΌ ν
μ€νΈμ λλ²κΉ
κ³Όμ μ΄ μΌλ§λ μ€μνμ§λ₯Ό κΉμ΄ κΉ¨λ¬μμ΅λλ€.
μ΄ κ³Όμ μ λ¨μν κΈ°λ₯ ꡬνμ λμ΄μ, νλμ¨μ΄μ μννΈμ¨μ΄ κ°μ μ¬μΈν μΈν°νμ΄μ€ μ€κ³κ° νλ‘μ νΈμ μμ±λμ ν° μν₯μ λ―ΈμΉλ€λ κ²μ μκ² ν΄μ£Όμμ΅λλ€.
λν μ΄λ² νλ‘μ νΈλ₯Ό ν΅ν΄, κΈ°μ μ μΈ κ΅¬νλ§νΌμ΄λ README λ¬Έμ μμ±κ³Ό κΈ°λ‘ μ 리μ μ€μμ±λ μλ‘κ² μ€κ°ν μ μμμ΅λλ€.
λͺ
νν λ¬Έμνλ μ μ§λ³΄μλ νμ
μ νμμ μ΄λ©°, νλ‘μ νΈλ₯Ό λμ± κ°μΉ μκ² λ§λλ ν΅μ¬ μμμμ λ€μκΈ κΉ¨λ«λ κ³κΈ°κ° λμμ΅λλ€.
WebRTC μλ²μ μΉ μΈν°νμ΄μ€ ꡬνμ λ΄λΉνλ©°,
μ€μκ° μμ μ€νΈλ¦¬λ°μ 볡μ‘μ±κ³Ό μΉμμΌ ν΅μ μ μμ μ± ν보μ λν΄ κΉμ΄ μκ² μ΄ν΄ν μ μμμ΅λλ€.
νΉν aiortc κΈ°λ°μ WebRTC μλ²λ₯Ό μ΅μ ννμ¬ μ§μ° μκ°μ μ΅μννλ κ³Όμ μ λμ μ μ΄μμ§λ§,
μ¬μ©μμκ² λκΉ μλ μ€μκ° μμμ μ 곡ν μ μκ² λμ΄ λΏλ―ν©λλ€.
λν λΌμΈ μ΄ν μ 볡μ/λμ μκ³ λ¦¬μ¦μ κ°λ°νλ©΄μ
μμΈ‘ λΆκ°λ₯ν μν©μ λν μμ€ν
μ κ²¬κ³ μ±μ λμ΄λ λ° κΈ°μ¬ν μ μμμ΅λλ€.
μ΄λ² νλ‘μ νΈλ₯Ό ν΅ν΄ νμλ€κ³Όμ κΈ΄λ°ν νμ
μ μ€μμ±μ λ€μ νλ² λκΌμ΅λλ€.
κ°μμ μν μ μ΅μ μ λ€νλ©΄μλ μλ‘μ μ΄λ €μμ ν¨κ» κ³ λ―Όνκ³ ν΄κ²°ν΄ λκ°λ κ³Όμ μ΄ μμκΈ°μ
μ±κ³΅μ μΌλ‘ νλ‘μ νΈλ₯Ό λ§λ¬΄λ¦¬ν μ μμμ΅λλ€.
μμΌλ‘λ μ΄λ¬ν κ²½νμ λ°νμΌλ‘ λμ± λ³΅μ‘νκ³ ν₯λ―Έλ‘μ΄ νλ‘μ νΈμ λμ νκ³ μΆμ΅λλ€.
MIT License β LICENSE νμΌ μ°Έμ‘°



