00 OpenCV gömülü kullanım senaryoları
OpenCV, dünyada en yaygın kullanılan açık kaynak bilgisayarlı görme kütüphanesidir. Gömülü Linux sistemlerde, AI çıkarımı öncesinde ya da bağımsız görüntü işleme pipeline'larında vazgeçilmez bir araçtır.
Gömülü OpenCV senaryoları
Alternatifler ve ne zaman OpenCV kullanılır?
| Araç | Güçlü Yönler | Zayıf Yönler | Tercih Edildiği Durum |
|---|---|---|---|
| OpenCV | Geniş op kümesi, iyi belgelenmiş | Büyük binary (~30MB) | Genel amaçlı görüntü işleme |
| libjpeg/libpng | Çok küçük, minimal | Yalnızca codec | MCU, çok kısıtlı bellek |
| Pillow (Python) | Kolay API | C/C++ yok, yavaş | Prototipleme, Python script |
| Halide | Otomatik optimizasyon | Öğrenme eğrisi yüksek | Pipeline optimizasyonu araştırma |
| Vitis AI | Xilinx FPGA hızlandırma | Xilinx'e özgü | FPGA tabanlı gömülü |
01 Cross-compile — aarch64 toolchain, CMake
Host PC'de (x86_64 Ubuntu) aarch64 için OpenCV derlemek, hedef cihazda derlemeye göre çok daha hızlıdır. Minimal modül seçimi ve headless build, binary boyutunu azaltır.
Toolchain kurulumu
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
pkg-config cmake ninja-build
# Sysroot hazırlama (RPi4 OS dosyalarını host'a kopyala)
mkdir -p /opt/sysroot-rpi4
rsync -avz --rsync-path="sudo rsync" \
pi@raspberrypi.local:/lib /opt/sysroot-rpi4/
rsync -avz --rsync-path="sudo rsync" \
pi@raspberrypi.local:/usr/lib /opt/sysroot-rpi4/usr/
rsync -avz --rsync-path="sudo rsync" \
pi@raspberrypi.local:/usr/include /opt/sysroot-rpi4/usr/
CMake toolchain dosyası
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(TOOLCHAIN_PREFIX aarch64-linux-gnu)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++)
set(CMAKE_STRIP ${TOOLCHAIN_PREFIX}-strip)
set(SYSROOT /opt/sysroot-rpi4)
set(CMAKE_SYSROOT ${SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# ARM NEON ve Cortex-A72 optimizasyonu
set(CMAKE_C_FLAGS "-march=armv8-a+simd -O3 -ffast-math")
set(CMAKE_CXX_FLAGS "-march=armv8-a+simd -O3 -ffast-math")
OpenCV minimal CMake build
git clone --depth 1 --branch 4.9.0 \
https://github.com/opencv/opencv
mkdir opencv-build-aarch64 && cd opencv-build-aarch64
cmake ../opencv \
-DCMAKE_TOOLCHAIN_FILE=../aarch64-rpi4.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/opt/opencv-aarch64 \
\
# NEON hızlandırma
-DENABLE_NEON=ON \
-DWITH_OPENCL=ON \
\
# Headless build: GUI yok
-DWITH_GTK=OFF \
-DWITH_QT=OFF \
-DWITH_OPENGL=OFF \
\
# Kamera
-DWITH_V4L=ON \
-DWITH_GSTREAMER=ON \
-DWITH_FFMPEG=OFF \
\
# Minimal modüller: sadece gerekli olanlar
-DBUILD_LIST=core,imgproc,imgcodecs,videoio,video \
\
# Python binding'leri devre dışı
-DBUILD_opencv_python2=OFF \
-DBUILD_opencv_python3=OFF \
-DBUILD_TESTS=OFF \
-DBUILD_PERF_TESTS=OFF \
-DBUILD_EXAMPLES=OFF
make -j$(nproc)
make install
# Sonuç: /opt/opencv-aarch64/lib/libopencv_*.so (~15MB toplam)
ls -lh /opt/opencv-aarch64/lib/libopencv_*.so
-DBUILD_LIST ile yalnızca gerekli modülleri derleyerek binary boyutunu 30MB'tan 5-10MB'a düşürebilirsin. Nesne tespiti için: core, imgproc, imgcodecs, videoio. DNN çıkarımı için dnn modülünü ekle.
02 NEON optimizasyonu — ENABLE_NEON, Universal Intrinsics
ARM NEON, aarch64'te 128-bit SIMD işlemleri sağlar. OpenCV, NEON'u Universal Intrinsics (UI) soyutlama katmanı üzerinden kullanır; aynı kod AVX2, NEON ve RVV'ye derlenir.
NEON kapasite kontrolü
#include <opencv2/core.hpp>
#include <iostream>
int main() {
// OpenCV CPU özelliklerini listele
std::string features = cv::getBuildInformation();
std::cout << features << std::endl;
// NEON aktif mi?
bool neon = cv::checkHardwareSupport(CV_CPU_NEON);
std::cout << "NEON: " << (neon ? "EVET" : "HAYIR") << std::endl;
// FP16 NEON
bool fp16 = cv::checkHardwareSupport(CV_CPU_FP16);
std::cout << "FP16: " << (fp16 ? "EVET" : "HAYIR") << std::endl;
return 0;
}
Universal Intrinsics (UI) ile özel kernel
#include <opencv2/core/hal/intrin.hpp>
#include <opencv2/imgproc.hpp>
// NEON hızlandırmalı uint8 → float normalize
// [0,255] → [0.0, 1.0] dönüşümü, 16 piksel paralel
void normalize_neon(const uint8_t* src, float* dst, int n) {
const float inv255 = 1.0f / 255.0f;
int i = 0;
// 16 piksel blok halinde işle (NEON 128-bit = 16x uint8)
for (; i <= n - 16; i += 16) {
cv::v_uint8x16 v_src = cv::v_load(src + i);
// uint8 → uint16 genişlet (2 blok × 8)
cv::v_uint16x8 lo, hi;
cv::v_expand(v_src, lo, hi);
// uint16 → float32 (4 blok × 4)
cv::v_float32x4 f0, f1, f2, f3;
cv::v_expand(lo, f0, f1); // uygun tip dönüşümü
// ... (tam implementasyon için v_cvt_f32 kullanılır)
// Scale: × (1/255)
cv::v_float32x4 scale = cv::v_setall_f32(inv255);
f0 = f0 * scale;
f1 = f1 * scale;
cv::v_store(dst + i, f0);
cv::v_store(dst + i + 4, f1);
}
// Kalan pikseller
for (; i < n; i++)
dst[i] = src[i] * inv255;
}
// OpenCV önerilen yol: convertTo (NEON otomatik kullanılır)
void normalize_opencv(const cv::Mat& src, cv::Mat& dst) {
src.convertTo(dst, CV_32F, 1.0/255.0); // NEON otomatik
}
NEON benchmark
| İşlem | C skaler | NEON (auto) | NEON (manual) |
|---|---|---|---|
| convertTo float32 (1080p) | 28 ms | 7 ms | 6 ms |
| resize INTER_LINEAR (1080→640) | 22 ms | 6 ms | — |
| GaussianBlur 5x5 (1080p) | 45 ms | 11 ms | — |
| BGR2RGB (1080p) | 8 ms | 2 ms | 1.5 ms |
03 Kamera pipeline — V4L2, VideoCapture, normalize
Linux'ta kamera erişiminin standart yolu V4L2 (Video for Linux 2)'dir. OpenCV'nin VideoCapture sınıfı, V4L2 backend ile USB ve CSI kameralara şeffaf erişim sağlar.
Temel kamera yakalama
#include <opencv2/videoio.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
int main() {
// V4L2 backend ile aç (MJPEG için daha hızlı)
cv::VideoCapture cap("/dev/video0", cv::CAP_V4L2);
// Format ayarla
cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M','J','P','G'));
cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
cap.set(cv::CAP_PROP_FPS, 30);
cap.set(cv::CAP_PROP_BUFFERSIZE, 2); // düşük gecikme
if (!cap.isOpened()) {
std::cerr << "Kamera açılamadı!" << std::endl;
return -1;
}
cv::Mat frame, rgb_frame, resized, normalized;
while (true) {
cap.read(frame); // BGR, uint8
if (frame.empty()) break;
// Renk dönüşümü: BGR → RGB
cv::cvtColor(frame, rgb_frame, cv::COLOR_BGR2RGB);
// AI giriş boyutuna yeniden boyutlandır
cv::resize(rgb_frame, resized, cv::Size(224, 224),
0, 0, cv::INTER_LINEAR);
// Normalizasyon: [0,255] → [0.0, 1.0] float32
resized.convertTo(normalized, CV_32F, 1.0/255.0);
// Model girişine kopyala (NCHW format için)
// cv::dnn::blobFromImage daha kullanışlı:
cv::Mat blob = cv::dnn::blobFromImage(
resized,
1.0/255.0, // scale
cv::Size(224, 224), // boyut
cv::Scalar(0,0,0), // mean (ImageNet: 0.485,0.456,0.406)
true, // swapRB: BGR→RGB
false // crop
);
// blob.shape: (1, 3, 224, 224) — NCHW, float32
}
cap.release();
return 0;
}
V4L2 format desteği kontrolü
# Desteklenen formatları listele
v4l2-ctl --list-formats-ext -d /dev/video0
# Mevcut kameralar
v4l2-ctl --list-devices
# Kamera özellikleri (brightness, contrast, vs.)
v4l2-ctl --list-ctrls -d /dev/video0
# Exposure ve gain manuel ayar (otomatik kapalı)
v4l2-ctl -d /dev/video0 \
--set-ctrl=exposure_auto=1 \
--set-ctrl=exposure_absolute=200
04 GStreamer entegrasyonu — appsink, HW decoder
GStreamer, Linux'ta güçlü bir medya pipeline çerçevesidir. OpenCV'nin VideoCapture sınıfı, GStreamer pipeline dizelerini doğrudan kabul eder; böylece donanım hızlandırmalı decoder ve çoklu kaynak kullanılabilir.
GStreamer ile VideoCapture
#include <opencv2/videoio.hpp>
#include <string>
// USB kamera — MJPEG donanım decode
std::string gst_usb =
"v4l2src device=/dev/video0 ! "
"image/jpeg,width=1280,height=720,framerate=30/1 ! "
"jpegdec ! "
"videoconvert ! "
"video/x-raw,format=BGR ! "
"appsink max-buffers=2 drop=true sync=false";
// RPi kamera — picamera2 üzerinden (libcamera-src)
std::string gst_picam =
"libcamerasrc ! "
"video/x-raw,width=1280,height=720,framerate=30/1 ! "
"videoconvert ! "
"video/x-raw,format=BGR ! "
"appsink max-buffers=2 drop=true";
// NV12 donanım decode (RPi4 V4L2 M2M decoder)
std::string gst_hw_decode =
"filesrc location=test_720p.h264 ! "
"h264parse ! "
"v4l2h264dec ! " // RPi4 donanım H.264 decoder
"video/x-raw,format=NV12 ! "
"videoconvert ! "
"video/x-raw,format=BGR ! "
"appsink max-buffers=2 drop=true";
// Ağ kamera (RTSP)
std::string gst_rtsp =
"rtspsrc location=rtsp://192.168.1.100:554/stream latency=100 ! "
"rtph264depay ! h264parse ! "
"avdec_h264 ! "
"videoconvert ! "
"video/x-raw,format=BGR ! "
"appsink max-buffers=2 drop=true";
cv::VideoCapture cap(gst_usb, cv::CAP_GSTREAMER);
cv::Mat frame;
while (cap.read(frame)) {
// frame: BGR, uint8, 1280×720
}
NV12 → BGR dönüşümü (NEON)
// GStreamer appsink'ten gelen NV12 frame'i BGR'ye çevir
// NV12: Y plane (W×H) + UV interleaved plane (W×H/2)
void nv12_to_bgr(const uint8_t* nv12_data,
int width, int height,
cv::Mat& bgr_out) {
// OpenCV'nin YUV2BGR fonksiyonu NV12 (YUV420sp) destekler
cv::Mat yuv(height + height/2, width, CV_8UC1,
(void*)nv12_data);
cv::cvtColor(yuv, bgr_out, cv::COLOR_YUV2BGR_NV12);
// NEON ile otomatik hızlandırılmış
}
05 OpenCL/GPU hızlandırma — Mali, UMat, profiling
OpenCV'nin T-API (Transparent API), cv::UMat üzerinden OpenCL backend'i şeffaf biçimde kullanır. ARM Mali GPU'lu RPi 4 veya RK3588'de belirli işlemler için belirgin hızlanma sağlar.
UMat kullanımı
#include <opencv2/core/ocl.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
int main() {
// OpenCL mevcut mu?
if (!cv::ocl::haveOpenCL()) {
std::cout << "OpenCL yok, CPU kullanılıyor" << std::endl;
} else {
cv::ocl::setUseOpenCL(true);
cv::ocl::Context ctx = cv::ocl::Context::getDefault();
std::cout << "OpenCL cihaz: "
<< ctx.device(0).name() << std::endl;
// Örnek: Mali-G52 MP2
}
cv::Mat cpu_frame = cv::imread("test.jpg");
// Mat → UMat (GPU belleğine kopyala)
cv::UMat gpu_frame;
cpu_frame.copyTo(gpu_frame);
// Aşağıdaki işlemler GPU'da çalışır (OpenCL):
cv::UMat gray, blurred, resized;
cv::cvtColor(gpu_frame, gray, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(gray, blurred, cv::Size(5,5), 1.5);
cv::resize(blurred, resized, cv::Size(640,640));
// Sonucu CPU'ya geri al
cv::Mat result;
resized.copyTo(result);
return 0;
}
OpenCL profiling
# OpenCL kernel profilini etkinleştir
export OPENCV_OCL_PROFILING=1
# Uygulama çalıştır
./opencv_app 2>&1 | grep -E "kernel|time"
# Mali GPU kullanım izleme (Panfrost sürücü)
cat /sys/bus/platform/drivers/panfrost/*/utilization
# opencl benchmark
clpeak # opencl peak performance testi
UMat'ın faydası yalnızca hesaplama yoğun işlemlerde ortaya çıkar. Küçük görüntüler veya basit işlemler için CPU→GPU ve GPU→CPU kopyalama maliyeti, hesaplama kazancını geçebilir. Profiling ile karar ver.
06 Temel görüntü işleme — threshold, contour, Kalman
Gömülü görüntü işleme pipeline'larında sıkça kullanılan algoritmalar: adaptif eşikleme, morfoloji, kontur analizi ve Kalman filtresi ile nesne takibi.
Adaptif eşikleme ve morfoloji
#include <opencv2/imgproc.hpp>
#include <vector>
struct Defect {
cv::Rect bbox;
double area;
cv::Point2f center;
};
std::vector<Defect> find_defects(const cv::Mat& frame) {
cv::Mat gray, binary, morph;
// Gri ton
cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
// Adaptif eşikleme (aydınlatma değişimine dayanıklı)
cv::adaptiveThreshold(
gray, binary,
255,
cv::ADAPTIVE_THRESH_GAUSSIAN_C,
cv::THRESH_BINARY_INV,
31, // block size
5 // sabit C
);
// Morfoloji: gürültüyü kaldır, hataları koru
cv::Mat kernel = cv::getStructuringElement(
cv::MORPH_ELLIPSE, cv::Size(5,5));
cv::morphologyEx(binary, morph, cv::MORPH_CLOSE, kernel);
cv::morphologyEx(morph, morph, cv::MORPH_OPEN, kernel);
// Kontur tespiti
std::vector<std::vector<cv::Point>> contours;
cv::findContours(morph, contours,
cv::RETR_EXTERNAL,
cv::CHAIN_APPROX_SIMPLE);
// Hata filtresi: alan eşiği
std::vector<Defect> defects;
for (auto& c : contours) {
double area = cv::contourArea(c);
if (area > 50 && area < 10000) { // piksel² cinsinden
Defect d;
d.area = area;
d.bbox = cv::boundingRect(c);
cv::Moments M = cv::moments(c);
d.center = cv::Point2f(M.m10/M.m00, M.m01/M.m00);
defects.push_back(d);
}
}
return defects;
}
Kalman filtresi ile nesne takibi
#include <opencv2/video/tracking.hpp>
class DefectTracker {
public:
cv::KalmanFilter kf;
cv::Mat state; // [x, y, vx, vy]
cv::Mat meas; // [x, y]
bool initialized = false;
DefectTracker() {
kf.init(4, 2, 0, CV_32F);
// Durum geçiş matrisi: konum + hız modeli
cv::setIdentity(kf.transitionMatrix);
kf.transitionMatrix.at<float>(0,2) = 1; // x += vx*dt
kf.transitionMatrix.at<float>(1,3) = 1; // y += vy*dt
// Ölçüm matrisi: sadece konum gözlemlenir
kf.measurementMatrix = cv::Mat::zeros(2, 4, CV_32F);
kf.measurementMatrix.at<float>(0,0) = 1;
kf.measurementMatrix.at<float>(1,1) = 1;
cv::setIdentity(kf.processNoiseCov, cv::Scalar(1e-4));
cv::setIdentity(kf.measurementNoiseCov, cv::Scalar(1e-1));
cv::setIdentity(kf.errorCovPost, cv::Scalar(1));
meas = cv::Mat::zeros(2, 1, CV_32F);
}
cv::Point2f predict() {
cv::Mat prediction = kf.predict();
return cv::Point2f(prediction.at<float>(0),
prediction.at<float>(1));
}
cv::Point2f update(cv::Point2f detected) {
meas.at<float>(0) = detected.x;
meas.at<float>(1) = detected.y;
cv::Mat corrected = kf.correct(meas);
return cv::Point2f(corrected.at<float>(0),
corrected.at<float>(1));
}
};
07 Pratik — Conveyor belt hata tespiti
Endüstriyel konveyör bant üzerinde ürün hata tespiti. USB kamera, Raspberry Pi 4, OpenCV pipeline ve çıkış için seri port alarmı. Hedef: <20ms işleme süresi.
Sistem mimarisi
USB Kamera (640×480, 30fps)
↓ MJPEG → BGR
[GStreamer/V4L2 capture]
↓
[ROI kırp — bant alanı]
↓
[Beyazlatma + gri ton]
↓
[Adaptif threshold]
↓
[Morfoloji — açma/kapama]
↓
[Kontur analizi — alan filtresi]
↓
[Kalman takip — sahte alarm azalt]
↓
┌────────────────────────────────┐
│ HATA var? │
│ Evet → GPIO HIGH + UART alarm │
│ Hayır → devam │
└────────────────────────────────┘
Tam implementasyon
import cv2, numpy as np, serial, time
import RPi.GPIO as GPIO
# --- Konfigürasyon ---
ALARM_PIN = 17
ROI = (100, 50, 440, 380) # (x1, y1, x2, y2)
MIN_AREA = 200
ALARM_FRAMES = 3 # Kaç ardışık frame'de hata olursa alarm verilir
# --- UART Alarm ---
ser = serial.Serial("/dev/ttyS0", 115200, timeout=0.1)
# --- GPIO ---
GPIO.setmode(GPIO.BCM)
GPIO.setup(ALARM_PIN, GPIO.OUT, initial=GPIO.LOW)
# --- Kamera (GStreamer MJPEG) ---
gst = ("v4l2src device=/dev/video0 ! "
"image/jpeg,width=640,height=480,framerate=30/1 ! "
"jpegdec ! videoconvert ! video/x-raw,format=BGR ! "
"appsink max-buffers=2 drop=true")
cap = cv2.VideoCapture(gst, cv2.CAP_GSTREAMER)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7,7))
alarm_counter = 0
total_defects = 0
frame_count = 0
def send_alarm(defect_count, area):
msg = f"ALARM:defects={defect_count},area={area:.0f}\r\n"
ser.write(msg.encode())
GPIO.output(ALARM_PIN, GPIO.HIGH)
while True:
ret, frame = cap.read()
if not ret: break
frame_count += 1
x1, y1, x2, y2 = ROI
roi = frame[y1:y2, x1:x2]
# İşleme pipeline
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
eq = cv2.equalizeHist(gray) # aydınlatma normalleştirme
blur = cv2.GaussianBlur(eq, (5,5), 0)
thr = cv2.adaptiveThreshold(blur, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 21, 4)
morph = cv2.morphologyEx(thr, cv2.MORPH_CLOSE, kernel)
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
contours, _ = cv2.findContours(morph,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
defects = [c for c in contours
if MIN_AREA < cv2.contourArea(c) < 15000]
# Alarm mantığı
if defects:
alarm_counter += 1
if alarm_counter >= ALARM_FRAMES:
total_area = sum(cv2.contourArea(c) for c in defects)
send_alarm(len(defects), total_area)
total_defects += len(defects)
# Bounding box çiz
for c in defects:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(roi, (x,y), (x+w,y+h), (0,0,255), 2)
else:
alarm_counter = 0
GPIO.output(ALARM_PIN, GPIO.LOW)
# Overlay
status = f"HATA: {len(defects)}" if defects else "TAMAM"
color = (0,0,255) if defects else (0,255,0)
cv2.putText(frame, status, (10,30),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, color, 2)
if frame_count % 300 == 0:
print(f"Toplam frame: {frame_count}, "
f"Tespit edilen hata: {total_defects}")
Performans ölçümü (RPi4)
| Adım | Süre | Optimizasyon |
|---|---|---|
| Kamera capture | 4 ms | GStreamer buffer |
| Gri ton + equalize | 2 ms | NEON |
| Adaptive threshold | 6 ms | NEON |
| Morfoloji (2x) | 3 ms | NEON |
| Kontur analizi | 1.5 ms | — |
| Toplam | 16.5 ms | <20ms hedefi OK |
Aydınlatmayı sabit tut (strobo LED, tetiklenmiş flaş) — değişken ortam ışığı en büyük doğruluk düşmanıdır. ROI kırpma işlem süresini %60 azaltır. Alarm debounce (ardışık ALARM_FRAMES) sahte pozitif sayısını büyük ölçüde azaltır.