라즈베리파이(C++)로 자율주행자동차 만들기(1) - 카메라, 바퀴 제어

박영재·2024년 11월 24일
post-thumbnail

설계

라즈베리파이 모델

Raspberry Pi 4 Model B Rev 1.2

운영체제

  • 서버(랩탑) - Windows11
  • 클라이언트(라즈베리파이) - Debian GNU/Linux 12

opencv

Windows 설치

https://velog.io/@mouse0429/openCVVisual-Studio-OpenCV-%EC%84%A4%EC%B9%98 참고

Linux

설치

sudo apt install libopencv-dev

컴파일

g++ test.cpp -o test `pkg-config --cflags --libs opencv4`

wiringPi

설치

git clone https://github.com/WiringPi/WiringPi.git
cd WiringPi
./build

compile

g++ motor_test.cpp -o motor_test -lwiringPi 

카메라 데이터 통신

테스트는 편의상 사설 네트워크에서 진행.
TCP 통신, 차후 필요에 따라 UDP로 변경 예정

서버 코드(윈도우즈11)


#include <iostream>
#include <vector>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <opencv2/opencv.hpp>

#pragma comment(lib, "ws2_32.lib")

#define PORT 5006
#define BUFFER_SIZE 65536

// Receive precisely sized data 
bool receiveExact(SOCKET sock, char* buffer, int size) {
    int totalReceived = 0;
    while (totalReceived < size) {
        int bytesReceived = recv(sock, buffer + totalReceived, size - totalReceived, 0);
        if (bytesReceived <= 0) {
            return false;
        }
        totalReceived += bytesReceived;
    }
    return true;
}

int main() {
    WSADATA wsaData;
    SOCKET serverSocket, clientSocket;
    struct sockaddr_in serverAddress, clientAddress;
    int clientLen = sizeof(clientAddress);

    // Init WinSock 
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed with error: " << WSAGetLastError() << std::endl;
        return -1;
    }

    // Create TCP socket
    serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == INVALID_SOCKET) {
        std::cerr << "Socket creation failed with error: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return -1;
    }

    // Set server address
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_addr.s_addr = INADDR_ANY;
    serverAddress.sin_port = htons(PORT);

    // Bind socket
    if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR) {
        std::cerr << "Bind failed with error: " << WSAGetLastError() << std::endl;
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }

    // Wait for client
    if (listen(serverSocket, 1) == SOCKET_ERROR) {
        std::cerr << "Listen failed with error: " << WSAGetLastError() << std::endl;
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }

    std::cout << "Waiting for connection..." << std::endl;

    // Accept client
    clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientLen);
    if (clientSocket == INVALID_SOCKET) {
        std::cerr << "Accept failed with error: " << WSAGetLastError() << std::endl;
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }
    std::cout << "Client connected!" << std::endl;

    
    while (true) {
        // Get data size
        uint32_t frameSize = 0;
        if (!receiveExact(clientSocket, (char*)&frameSize, sizeof(frameSize))) {
            std::cerr << "Failed to receive data size" << std::endl;
            break;
        }
        frameSize = ntohl(frameSize); // Network-byte order into host order

        // Receive one frame
        std::vector<char> buffer(frameSize);
        if (!receiveExact(clientSocket, buffer.data(), frameSize)) {
            std::cerr << "Failed to receive frame data" << std::endl;
            break;
        }

        // Decode one frame into JPEG
        std::vector<uchar> data(buffer.begin(), buffer.end());
        cv::Mat frame = cv::imdecode(data, cv::IMREAD_COLOR);
        if (frame.empty()) {
            std::cerr << "Failed to decode frame" << std::endl;
            continue;
        }

        
        cv::imshow("Received Video", frame);

        // Exit for ESC
        if (cv::waitKey(1) == 27) {
            break;
        }
    }

    closesocket(clientSocket);
    closesocket(serverSocket);
    WSACleanup();
    return 0;
}

클리이언트(라즈베리파이 리눅스)

#include <iostream>
#include <vector>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
#include <opencv2/opencv.hpp>

#define SERVER_IP "192.168.0.27"
#define PORT 5006
#define BUFFER_SIZE 65536

int main() {
    int sockfd;
    struct sockaddr_in serverAddress;

    // Create Socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        return -1;
    }

    // Set server address
    memset(&serverAddress, 0, sizeof(serverAddress));
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(PORT);

    // Convert server address into IP address
    if (inet_pton(AF_INET, SERVER_IP, &serverAddress.sin_addr) <= 0) {
        perror("Invalid address/ Address not supported");
        close(sockfd);
        return -1;
    }

    // Connect server
    if (connect(sockfd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) < 0) {
        perror("Connection failed");
        close(sockfd);
        return -1;
    }
    std::cout << "Connected to the server!" << std::endl;

    // Set camera
    cv::VideoCapture cap(0); // Camera0
    if (!cap.isOpened()) {
        std::cerr << "Error: Unable to open camera" << std::endl;
        close(sockfd);
        return -1;
    }

    std::cout << "Streaming video to server..." << std::endl;

    while (true) {
        cv::Mat frame;
        cap >> frame; // Capture frame

        if (frame.empty()) {
            std::cerr << "Error: Captured empty frame" << std::endl;
            break;
        }

        // Encode frame into JPEG
        std::vector<uchar> buffer;
        // std::vector<int> compressionParams = {cv::IMWRITE_JPEG_QUALITY, 50}; // 압축 품질 50%
        if (!cv::imencode(".jpg", frame, buffer)) {
            std::cerr << "Error: Failed to encode frame" << std::endl;
            continue;
        }

        // 데이터 크기 전송 (4바이트)
        uint32_t dataSize = htonl(buffer.size());
        if (send(sockfd, &dataSize, sizeof(dataSize), 0) < 0) {
            perror("Failed to send data size");
            break;
        }

        // 데이터 전송
        if (send(sockfd, buffer.data(), buffer.size(), 0) < 0) {
            perror("Failed to send frame data");
            break;
        }

        // ESC 키로 종료
        if (cv::waitKey(1) == 27) {
            break;
        }
    }

    // 자원 해제
    cap.release();
    close(sockfd);
    return 0;
}

카메라 테스트

업로드중..

모터 제어

코드

#include <wiringPi.h>
#include <softPwm.h>
#include <iostream>
#include <unistd.h> // for sleep()
#include <csignal>  // for signal()

// BCM Pin numbers -> WiringPi Pin number
#define PWMA 1  // GPIO 18
#define AIN1 3  // GPIO 22
#define AIN2 2  // GPIO 27

#define PWMB 4  // GPIO 23
#define BIN1 6  // GPIO 25
#define BIN2 5  // GPIO 24

void cleanupMotors() {
    softPwmWrite(PWMA, 0);
    softPwmWrite(PWMB, 0);
    digitalWrite(AIN1, LOW);
    digitalWrite(AIN2, LOW);
    digitalWrite(BIN1, LOW);
    digitalWrite(BIN2, LOW);
    std::cout << "Motors stopped and GPIO cleaned up." << std::endl;
}


void signalHandler(int signum) {
    std::cout << "\nInterrupt signal (" << signum << ") received. Stopping motors..." << std::endl;
    cleanupMotors();
    exit(signum);
}

int main() {
    // Init WiringPi
    if (wiringPiSetup() == -1) {
        std::cerr << "WiringPi initialization failed!" << std::endl;
        return 1;
    }

    // Set pin modes
    pinMode(PWMA, PWM_OUTPUT);
    pinMode(AIN1, OUTPUT);
    pinMode(AIN2, OUTPUT);

    pinMode(PWMB, PWM_OUTPUT);
    pinMode(BIN1, OUTPUT);
    pinMode(BIN2, OUTPUT);

    // Generate software PWM
    softPwmCreate(PWMA, 0, 100); // PWM pin, Init 0, Max 100
    softPwmCreate(PWMB, 0, 100);

    // Register sighandle for SIGINT(ctrl + c)
    signal(SIGINT, signalHandler);

    try {
        while (true) {
            // Set forward
            digitalWrite(AIN1, LOW);
            digitalWrite(AIN2, HIGH);
            digitalWrite(BIN1, LOW);
            digitalWrite(BIN2, HIGH);

            softPwmWrite(PWMA, 100); // 왼쪽 모터 100% 출력
            softPwmWrite(PWMB, 100); // 오른쪽 모터 100% 출력
            sleep(1);

            // 정지 설정
            softPwmWrite(PWMA, 0);
            softPwmWrite(PWMB, 0);
            sleep(1);
        }
    } catch (...) {
        std::cerr << "An error occurred. Cleaning up..." << std::endl;
        cleanupMotors();
    }

    return 0;
}

동작 테스트

업로드중..

profile
People live above layers of abstraction beneath which engineers reside

0개의 댓글