Skip to content
Snippets Groups Projects
Select Git revision
  • 4aeafcfa650ee66302e90f299215d52e943c775d
  • main default protected
2 results

Button.js

Blame
  • camera.cpp 13.57 KiB
    #include "gpio.h"
    #include <chrono>
    #include <iostream>
    #include <memory>
    #include <thread>
    #include <vector>
    #include <queue>
    #include <string>
    #include <fstream>
    #include <sys/mman.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <opencv2/opencv.hpp>
    #include <iomanip>
    #include <stdexcept>
    #include <cmath>
    #include <arpa/inet.h>
    
    using namespace std;
    using namespace GPIO;
    
    #define BUFFER_SIZE 1024
    
    #define SERVER_ADDR "192.168.121.4"
    #define SERVER_PORT 12345
    
    #define POUT 535  // 초음파 트리거 핀 (GPIO23)
    #define PIN 536  // 초음파 에코 핀 (GPIO24)
    
    #define DISTANCE 10  // 초음파 센서 인식 기준 거리 (cm 단위)
    
    
    // 번호판 후보 영역 필터링을 위한 파라미터 
    const int MIN_AREA = 100;
    const int MIN_WIDTH = 4;
    const int MIN_HEIGHT = 2;
    const float MIN_RATIO = 0.25f;
    const float MAX_RATIO = 0.9f;
    
    // 번호판 문자 검출 파라미터
    const float MAX_DIAG = 5.0f;
    const float MAX_ANGLE = 12.0f;
    const float MAX_AREA_DIFF = 0.6f;
    const float MAX_WIDTH_DIFF = 0.8f;
    const float MAX_HEIGHT_DIFF = 0.2f;
    const int MIN_CHARACTER = 6;
    
    // 번호판 문자 여백 비율
    const float MARGIN_RATIO = 0.1f;
    
    // 파일 저장 경로 (폴더 생성 필요)
    const string IMAGES_DIR = "../images";
    const string PLATES_DIR = "../plates";
    
    
    struct Rect {
        int x, y, w, h;
        float cx, cy;
    
        Rect(int x, int y, int w, int h, float cx, float cy) : x(x), y(y), w(w), h(h), cx(cx), cy(cy) {}
        
        bool operator==(const Rect& other) const {
            return x == other.x && y == other.y && w == other.w && h == other.h && cx == other.cx && cy == other.cy;
        }
    };
    
    int server_socket, python_socket;  // 서버 소켓 및 파이썬 소켓
    bool detected = false;  // 차량 감지 여부
    bool fireAlert = false;  // 화재 경보 여부
    pthread_t sensor_thread, receive_thread;
    
    vector<Rect> detect_character(const vector<Rect>& rects) {
        for (size_t idx = 0; idx < rects.size(); idx++) {
            vector<Rect> match = {rects[idx]};
            queue<size_t> queue;
            queue.push(idx);
            
            while (!queue.empty()) {
                size_t cur = queue.front(); queue.pop();
                
                const Rect& r1 = rects[cur];
                
                for (size_t nxt = 0; nxt < rects.size(); nxt++) {
                    if (cur == nxt || find(match.begin(), match.end(), rects[nxt]) != match.end()) continue;
                    
                    const Rect& r2 = rects[nxt];
                    
                    float dx = abs(r1.cx - r2.cx);
                    float dy = abs(r1.cy - r2.cy);
                    
                    float diagonal = sqrt(r1.w * r1.w + r1.h * r1.h);
                    float distance = hypot(dx, dy);
                    float angle = dx != 0 ? abs(atan2(dy, dx) * 180.0f / CV_PI) : 90.0f;
                    
                    float diff_area = abs(r1.w * r1.h - r2.w * r2.h) / float(r1.w * r1.h);
                    float diff_width = abs(r1.w - r2.w) / float(r1.w);
                    float diff_height = abs(r1.h - r2.h) / float(r1.h);
                    
                    if (distance < diagonal * MAX_DIAG && angle < MAX_ANGLE && diff_area < MAX_AREA_DIFF && diff_width < MAX_WIDTH_DIFF && diff_height < MAX_HEIGHT_DIFF) {
                        match.push_back(rects[nxt]);
                        queue.push(nxt);
                    }
                }
            }
            
            if (match.size() >= MIN_CHARACTER) {
                return match;
            }
        }
        
        throw runtime_error("문자 검출 실패");
    }
    
    void detect_plate(const string& img_path) {
        // 이미지 읽기
        cv::Mat img = cv::imread(img_path);
        if (img.empty()) {
            cout << "이미지를 찾을 수 없습니다." << endl;
            return;
        }
        
        cv::Mat orig = img.clone();
        cv::Mat final_result = orig.clone();
        
        // 그레이스케일 변환
        cv::Mat gray;
        cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
        
        // Morphology 연산
        cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
        cv::Mat tophat, blackhat, gray_enhanced;
        cv::morphologyEx(gray, tophat, cv::MORPH_TOPHAT, kernel);
        cv::morphologyEx(gray, blackhat, cv::MORPH_BLACKHAT, kernel);
        cv::add(gray, tophat, gray_enhanced);
        cv::subtract(gray_enhanced, blackhat, gray_enhanced);
        
        // 가우시안 블러
        cv::Mat blur;
        cv::GaussianBlur(gray_enhanced, blur, cv::Size(5, 5), 2);
        
        // 적응형 이진화
        cv::Mat thresh;
        cv::adaptiveThreshold(blur, thresh, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 91, 3);
        
        // 컨투어 찾기
        vector<vector<cv::Point>> contours;
        cv::findContours(thresh, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
        
        vector<Rect> rects;
        for (const auto& contour : contours) {
            cv::Rect rect = cv::boundingRect(contour);
            float cx = rect.x + rect.width / 2.0f;
            float cy = rect.y + rect.height / 2.0f;
            
            if (rect.width * rect.height > MIN_AREA && rect.width > MIN_WIDTH && rect.height > MIN_HEIGHT && MIN_RATIO < float(rect.width) / rect.height && float(rect.width) / rect.height < MAX_RATIO) {
                rects.emplace_back(rect.x, rect.y, rect.width, rect.height, cx, cy);
            }
        }
        
        try {
            // 문자 컨투어 찾기
            vector<Rect> chars = detect_character(rects);
            
            // 여백 적용
            vector<cv::Point2f> points;
            for (const auto& char_rect : chars) {
                int margin_w = int(char_rect.w * MARGIN_RATIO);
                int margin_h = int(char_rect.h * MARGIN_RATIO);
                
                points.emplace_back(char_rect.x - margin_w, char_rect.y - margin_h);
                points.emplace_back(char_rect.x + char_rect.w + margin_w, char_rect.y - margin_h);
                points.emplace_back(char_rect.x - margin_w, char_rect.y + char_rect.h + margin_h);
                points.emplace_back(char_rect.x + char_rect.w + margin_w, char_rect.y + char_rect.h + margin_h);
                
                cv::rectangle(final_result, cv::Point(char_rect.x, char_rect.y), cv::Point(char_rect.x + char_rect.w, char_rect.y + char_rect.h), cv::Scalar(255, 0, 0), 1);
            }
            
            // 좌상단, 우상단, 우하단, 좌하단 점 찾기
            vector<cv::Point2f> box(4);
            cv::Point2f* pts = &points[0];
            size_t n_pts = points.size();
            
            // 좌상단 (x+y가 최소)
            box[0] = *min_element(pts, pts + n_pts, [](const cv::Point2f& a, const cv::Point2f& b) { return a.x + a.y < b.x + b.y; });
    
            // 우상단 (-x+y가 최소)
            box[1] = *min_element(pts, pts + n_pts, [](const cv::Point2f& a, const cv::Point2f& b) { return -a.x + a.y < -b.x + b.y; });
    
            // 우하단 (-x-y   최소)
            box[2] = *min_element(pts, pts + n_pts, [](const cv::Point2f& a, const cv::Point2f& b) { return -a.x - a.y < -b.x - b.y; });
            
            // 좌하단 (x-y가 최소)
            box[3] = *min_element(pts, pts + n_pts, [](const cv::Point2f& a, const cv::Point2f& b) { return a.x - a.y < b.x - b.y; });
            
            // 전체 번호판 영역 표시
            vector<vector<cv::Point>> box_contour = { {cv::Point(box[0]), cv::Point(box[1]), cv::Point(box[2]), cv::Point(box[3])} };
            cv::drawContours(final_result, box_contour, 0, cv::Scalar(0, 0, 255), 2);
            
            // 투시 변환을 위한 크기 계산
            float width = cv::norm(box[1] - box[0]);
            float height = cv::norm(box[3] - box[0]);
            
            vector<cv::Point2f> dst_pts = {
                cv::Point2f(0, 0),
                cv::Point2f(width - 1, 0),
                cv::Point2f(width - 1, height - 1),
                cv::Point2f(0, height - 1)
            };
            
            cv::Mat M = cv::getPerspectiveTransform(box, dst_pts);
            cv::Mat plate_img;
            cv::warpPerspective(orig, plate_img, M, cv::Size(width, height));
            
            // 이미지 파일명에서 타임스탬프 추출
            size_t start = img_path.find("image_") + 6;
            size_t end = img_path.find(".jpg");
            string timestamp_str = img_path.substr(start, end - start);
            
            string plate_filename = PLATES_DIR + "/plate_" + timestamp_str + ".jpg";
            
            // 결과 저장
            cv::imwrite(plate_filename, plate_img);
            
            // 파이썬 노드로 번호판 이미지 전송
            if (send(python_socket, plate_filename.c_str(), plate_filename.size(), 0) < 0) {
                perror("파이썬 노드로 번호판 이미지 전송 실패");
            }
    
            char buffer[BUFFER_SIZE];
            auto read_bytes = read(python_socket, buffer, BUFFER_SIZE - 1);
            if (read_bytes >= 0) {
                buffer[read_bytes] = '\0';
                cout << "파이썬 노드로부터 받은 메시지: " << buffer << endl;
    
                string message = "0-" + string(buffer);
                send(server_socket, message.c_str(), message.size(), 0);
            }
        } catch (const exception& e) {
            cerr << e.what() << endl;
        }
    }
    
    // 서버 메시지 수신 스레드
    void* receive_message(void* arg) {
        char buffer[BUFFER_SIZE];
    
        while (true) {
            auto read_bytes = read(server_socket, buffer, BUFFER_SIZE - 1);
            if (read_bytes <= 0) {
                cout << "서버와의 연결이 종료되었습니다." << endl;
                close(server_socket);
                exit(0);
            }
    
            buffer[read_bytes] = '\0';
            cout << "서버로부터 받은 메시지: " << buffer << endl;
            
            string msg(buffer);
            msg = msg.substr(0, msg.find_first_of("\r\n"));
            
            if (msg == "0-FIRE") {
                fireAlert = true;
                cout << "화재 경보가 발생했습니다." << endl;
            } else if (msg == "0-CLEAR") {
                fireAlert = false;
                cout << "화재 경보가 해제되었습니다." << endl;
            }
        }
    
        return nullptr;
    }
    
    // 초음파 센서 측정 스레드
    void* ultrasonic_sensor(void *arg) {
        clock_t start_t, end_t;
        while (1) {
            // 초음파 센서 측정
            GPIOWrite(POUT, Value::HIGH);
            usleep(10);
            GPIOWrite(POUT, Value::LOW);
    
            // 에코 핀 신호 대기
            while (GPIORead(PIN) == 0) start_t = clock();
            while (GPIORead(PIN) == 1) end_t = clock();
    
            double time = (double) (end_t - start_t) / CLOCKS_PER_SEC;  // 시간 계산
            double distance = time / 2 * 34000;  // cm 단위 거리 계산
    
            cout << distance << "cm" << endl;
            if (!detected && distance <= DISTANCE) {  // 10cm 이하 -> 차량 접근
                detected = true;
                cout << "차량 감지: " << distance << "cm" << endl;
    
                // 현재 시간 타임스탬프
                auto now = chrono::system_clock::now();
                auto now_time = chrono::system_clock::to_time_t(now);
                stringstream timestamp;
                timestamp << put_time(localtime(&now_time), "%Y%m%d_%H%M%S");
                
                string timestamp_str = timestamp.str();
                string image_filename = IMAGES_DIR + "/image_" + timestamp_str + ".jpg";
                string cmd = "libcamera-still --nopreview --immediate --rotation=180 --width=1296 --height=972 --denoise off -t 1 -o " + image_filename;
    
                if (system(cmd.c_str()) == 0) {
                    cout << "이미지 저장: " << image_filename << endl;
                } else {
                    cerr << "이미지 저장 실패: " << image_filename << endl;
                }
    
                detect_plate(image_filename);
            } else if (detected && distance > 10) {  // 10cm 초과 -> 차량 없음
                detected = false;
                cout << "차량 감지 해제" << endl;
            }
    
            usleep(500000);  // 0.5초 주기
        }
    
        return nullptr;
    }
    
    int main() {
        try {
            struct sockaddr_in server_addr, python_addr;
            
            // 서버 노드 소켓 생성
            if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                perror("서버 노드 소켓 생성 실패");
                return -1;
            }
    
            // 서버 노드 주소 설정
            server_addr.sin_family = AF_INET;
            server_addr.sin_port = htons(SERVER_PORT);
            server_addr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
    
            // 서버 노드 연결
            if (connect(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
                perror("서버 노드 연결 실패");
                return -1;
            }
    
            printf("서버 노드에 연결되었습니다.\n");
    
            // 파이썬 노드 소켓 생성
            if ((python_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                perror("파이썬 노드 소켓 생성 실패");
                return -1;
            }
    
            // 파이썬 노드 주소 설정
            python_addr.sin_family = AF_INET;
            python_addr.sin_port = htons(3000);
            python_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
            // 파이썬 노드 연결
            if (connect(python_socket, (struct sockaddr *) &python_addr, sizeof(python_addr)) < 0) {
                perror("파이썬 노드 연결 실패");
                return -1;
            }
    
            printf("파이썬 노드에 연결되었습니다.\n");
    
            // GPIO 핀 설정
            GPIOExport(POUT);
            GPIOExport(PIN);
    
            usleep(100000);  // GPIO 설정 대기
    
            // GPIO 핀 방향 설정
            GPIODirection(POUT, Direction::OUT);
    
            // 스레드 생성
            pthread_create(&sensor_thread, NULL, ultrasonic_sensor, NULL);
            pthread_create(&receive_thread, NULL, receive_message, NULL);
    
            // 스레드 종료 대기
            pthread_join(sensor_thread, NULL);
            pthread_join(receive_thread, NULL);
    
            // 자원 해제
            close(server_socket);
            close(python_socket);
            GPIOUnexport(POUT);
            GPIOUnexport(PIN);
        } catch (const exception& e) {
            cerr << "Error: " << e.what() << endl;
            return 1;
        }
    
        return 0;
    }