Derin Öğrenme ve OpenCV
OpenCV 2.4 sürümü ile birlikte derin öğrenme tarafındaki birçok gelişmeye yer verilmeye başlanmıştı. Görüntü sınıflandırma için farklı kütüphaneler ile oluşturulmuş modellerin içe aktarılabilmesi, farklı sınıflandırıcı ağların oluşturulabilmesi, model oluşturabilme vb. OpenCV 3.3 sürümü ile birlikte ise neredeyse nihai bir dağıtım oluşturuldu ve Caffe, TensorFlow, Torch DarkNet gibi framework’ler için destek sağlandı. Popüler; AlexNet, GoogLeNet , ResNet, SqueezeNet, , VGG, ENet , VGG-based SSD, MobileNet-based SSD gibi ağlar için ise destek sağlanmaktadır.
Örneğimizde bugün için en güncel sürüm olan 3.4 kullanacağız. Derin öğrenme uygulamalarınızda OpenCV kullanmak istiyorsanız 3.3 sürümü ve üzeri bir dağıtımı kullanmanız gerekecektir. 3.4 sürümü ile birlikte yeni geliştirmelerin yanı sıra hali hazırda yer alan bazı sınıflandırıcı parametreleri de değişti farklı sürümler ile geliştirme yapacaksanız buna dikkat etmenizde fayda var.
Derin Sinir Ağı (DNN) ve OpenCV
Derin sinir ağının daha önce değindiğimiz yapay sinir ağlarından çok büyük farkı yoktur. Hatırlarsanız sinir ağlarında gizli katmanlardan bahsetmiştik ve probleme göre katman sayısının değişebileceğine göz atmıştık. Derin sinir ağı yani DNN en temel anlamıyla birden fazla gizli katmana sahip sinir ağı olarak adlandırılabilir. Bu sinir ağları probleme göre farklı sayıda gizli katmanlara sahiptirler. Bu yazıda nesne tanıma için OpenCV’de yer alan DNN modülünü ve kullanımını göreceğiz. Derin sinir ağları hakkında detaylı bilgi için bu yazıyı önerebilirm.
Belli başlı tanımamız gereken bazı fonksiyonlara bakmakta fayda var, bu fonksiyonları bir kaç farklı grupta inceleyebiliriz. Öncelikli olarak farklı kütüphanelerdeki modelleri (Kısaca model, daha önceden veri seti ile eğitilerek ağırlıkların hesaplanmış bir şekilde sunulduğu dosyalar) içe aktarmamızı sağlayan; readNetFromCaffe, readNetFromTensorFlow, readNetFromTorch. Görüntülerin okunarak sinir ağı girişine hazırlayan okuma fonksiyonu blobFromImage. Ayrıca blobFromImage’in nasıl çalıştığını merak ediyorsanız buradaki yazıya göz atabilirsiniz.
DNN Kullanarak Görüntünün Sınıflandırılması
Öncelikli olarak nesne tespitinde kullanacağımız MobileNet-SSD modelini aşağıdaki bağlantıdan indirelim (Bu dosyalar proje içerisinde de mevcut isterseniz oradaki modeli kullanabilirsiniz fakat güncel olması açısından kullanacağız zaman indirip değiştirebilirsiniz). Burada yapılandırma dosyası olarak adlandırılan *.prototxt ve ağırlıkların yer aldığı *.caffemodel e ihtiyacımız olacak. Bu model caffe kütüphanesi için hazırlanmış olsada daha öncede belirttiğimiz readNetFromCaffe fonksiyonu ile OpenCV için de anlamlı hale getireceğiz. Kullanacağım model içerisinde birçok sınıf mevcut, bu sınıflardan başlıcaları; uçak, bisiklet, insan, kuş, at, koyun, otobüs, otomobil …
- https://github.com/chuanqi305/MobileNet-SSD
- MobileNetSSD_deploy.prototxt
- MobileNetSSD_deploy.caffemodel
Algoritmik olarak yapacaklarımızı tanımlamak gerekirse;
- Derin sinir ağı oluşturulur
- Kameradan görüntü okunur
- Okunan görüntü sinir ağına sokulur
- Tespit edilen nesnelerin konumu hesaplanır
- Doğruluk oranı ve nesne hatları görüntü üzerine çizilir
Öncelikle prot, model dosyalarımızın dosya dizinleri ve model içerisinde yer alan sınıf etiketlerini tanımladık, ardından Net nesnesi ile sinir ağını caffe modeli kullanarak oluşturduk.
public class DeepNeuralNetworkProcessor { private Net net; private final String proto = "res/MobileNetSSD_deploy.prototxt"; private final String model = "res/MobileNetSSD_deploy.caffemodel"; private final String[] classNames = {"background", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"}; public DeepNeuralNetworkProcessor() { this.net = Dnn.readNetFromCaffe(proto, model); }
Şimdi ise bir metot yazalım, bu metot input olarak aldığı frame’i sinir ağına göndererek çıktıları tasarladığım dnnobject tipinde döndürecek. Dikkat edilmesi gereken bir diğer nokta ise sinir ağının RGB renk uzayında görüntüler ile çalışmasıdır. Farklı renk uzayındaki görüntü dönüşümleri esnasında görüntü üzerinde gürültü oluşabileceği veya verimli bir dönüşüm yapılamayacağı için renk dönüşümü yapmadan RGB görüntü üzerinde çalışmanızı öneririm. Burada eklediğim parametre gri olarak gönderilmiş bir görüntü var ise bunun dönüştürülüp dönüştürülmeyeceğini ifade etmektedir.
public List<DnnObject> getObjectsInFrame(Mat frame, boolean isGrayFrame) { //Görüntünün genişlik ve yükseklik değeri int inWidth = 320; int inHeight = 240; double inScaleFactor = 0.007843; //Tahmin oranı için belirlediğim bir eşik değer double thresholdDnn = 0.2; double meanVal =127.5; Mat blob = null; Mat detections = null; List<DnnObject> objectList = new ArrayList<>(); int cols = frame.cols(); int rows = frame.rows(); try { if (isGrayFrame) Imgproc.cvtColor(frame, frame, Imgproc.COLOR_GRAY2RGB); //Giriş görüntüsü sinir ağı için blob haline getirilir blob = Dnn.blobFromImage(frame, inScaleFactor, new Size(inWidth, inHeight), new Scalar(meanVal, meanVal, meanVal), false, false); net.setInput(blob); detections = net.forward(); detections = detections.reshape(1, (int) detections.total() / 7); //Tespit edilen tüm nesneler for (int i = 0; i < detections.rows(); ++i) { double confidence = detections.get(i, 2)[0]; // Tahmin oranı eşik değerinden büyük mü if (confidence < thresholdDnn) continue; //Tespit edilen nesnenin model içerisindeki sınıfının id'si int classId = (int) detections.get(i, 1)[0]; //Tespit edilen nesnenin dikdörtgen köşe koordinatları int xLeftBottom = (int) (detections.get(i, 3)[0] * cols); int yLeftBottom = (int) (detections.get(i, 4)[0] * rows); Point leftPosition = new Point(xLeftBottom, yLeftBottom); int xRightTop = (int) (detections.get(i, 5)[0] * cols); int yRightTop = (int) (detections.get(i, 6)[0] * rows); Point rightPosition = new Point(xRightTop, yRightTop); //Nesnenin merkez noktası float centerX = (xLeftBottom + xRightTop) / 2; float centerY = (yLeftBottom - yRightTop) / 2; Point centerPoint = new Point(centerX, centerY); //Tespit edilen nesneleri listeye ekle DnnObject dnnObject = new DnnObject(classId, classNames[classId].toString(), leftPosition, rightPosition, centerPoint); objectList.add(dnnObject); } } catch (Exception ex) { LOGGER.error("An error occurred DNN: ", ex); } return objectList; }
Daha derli toplu ve OOP olması açısından sinir ağından dönen nesneleri barındıracak sınıfımız ise aşağıdaki gibidir.
import org.opencv.core.Point; @Data public class DnnObject { private int objectClassId; private String objectName; private Point leftBottom; private Point rightTop; private Point centerCoordinate; public DnnObject(int objectClassId, String objectName, Point leftBottom, Point rightTop, Point centerCoordinate) { this.objectClassId = objectClassId; this.objectName = objectName; this.leftBottom = leftBottom; this.rightTop = rightTop; this.centerCoordinate = centerCoordinate; } }
Sonuç
Kaynak Kod
Sınıflandırma ile ilgili bu projenin kaynak koduna aşağıdaki github linki üzerinden ulaşabileceğiniz repo içerisindeki dnn klasöründen erişebilirsiniz. https://github.com/mesutpiskin/OpenCvObjectDetection