29 Ekim 2018 Pazartesi günü TEM otobanında aracı sürerken yaptığım ilk deneme ve kalibrasyonsuz sonuç video sunun animated gif ini paylaşıyorum.. Aracın ön cam orta üst tarafında android cihaz veya kamera montelenirse %100 başarılı olacaktır. Ben sağ elimde bunu uyguladım dolayısı ile elim sürekli oynamasına rağmen bu sonuç gayet iyi. Araç tanıma ve şerit takip birlikte oldukça iyi sonuç verdi diyebilirim..
Araç mesafe ölçümü ve otonom fren mesafesi ayarlama, cruise control gibi entegrasyonlar için Bosch LRR4 veya MRR radar sensor cihazı (tabii ek olarak aynı firmanın on cam küçük kamerası da bonus olur) elimde olsa harika işler çıkarırdım ki fiyatları 2500-3000 Euro arasında olduğundan şimdilik o cihazlarla kodlama ve entegrasyon yapamıyorum. Biri bana bu imkanı sağlarsa belki olur 🙂
Lane detection dediğimiz otobanda giderken şerit takibi yapabileceğiniz yazılımlardan daha önce bahsetmiştim ve nasıl kodlama yapılabileceğini anlatmıştım. Şimdi ise internette hiç bir yerde bulamayacağınız Android Java kodunu size açıklamalarıyla veriyorum. Her yerde Python olarak değişik şekillerde bunu bulabilirsiniz. Python kodundan farkı Android de yazılım kodlamasında bazı kodların püf noktaları olması. Ayrıca farklı yazılım dillerinde yazılım yapanlarda bu kod dan aslında yararlanabilir. Çünkü oldukça detaylı açıklamalar yazdım kodların arasına. Yine kod aralarına o an yapılan işlemin sonuç resmini de koydum.
Android tabanlı cihazınızda ister kamera onpreviewframe metodu içinden isterseniz de imagereader ile aldığınız görüntüden tek bir frame I Bitmap e çevirip bu metoda gönderin. Metod size otoban şeridinde iken sağ ve sol şeritleri kırmızı ile çizecek ve bu metodun sonucu olan bitmap I alıp ister onpreviewframe de ki byte a çevirip android ekrana bunu vererek video yu manipüle edersiniz isterseniz de tensorflow örneğinde anlattığım gibi Canvas ile ekrana yapıştırırsınız. Sonuçta görüntü de video akarken şeritler kırmızı ile işaretlenecek.
Metod içindeki bazı yerlerde ne ve neden yapıldığını da yazacağım. OPENCV 3.2.0 versionunu kullandım. Android Projenize bunu da katmalısınız ki daha önceki tensorflow yazımda 2 paragrafta bunun nasıl yapıldığını anlattım. Bu kodun aynısını tensorflow örneğinde anlattığım yazıdaki C++ kodu olarak çevirip yazmanız da mümkün. Aşağıda kod içinde anlattığım açıklama notlarına mutlaka dikkate edin. Bu kodu as is olduğu gibi copy-paste ile programlarınızda kullanabilirsiniz. (kameradan gerçek anlık görüntüyü byte array vb alıp resme çevirip yani bitmap e bu metoda gönderin bu metod sonucundaki bitmap I alıp byte array e çevirip kameraya geri iade edin o kadar !)
public Bitmap process_image(Bitmap bitmap) {
Mat mat = new Mat();
Mat gray_image = new Mat();
Mat img_hsv = new Mat();
// Öncelikle gelen bitmap I Mat objesine çevireceğiz. Artık bununla işlem //yapacağız.
Utils.bitmapToMat(bitmap,mat);
// Mat resim objemizi mutlaka Gray e çevirmemiz lazım. Normal resim bitmap //yani 3 veya 4 channel lı geliyor elimize ancak gray e çevirirsek elimizde 1 //channel lı hale geliyor. Aşağıda yapılacak işlemlerde 3 veya 4 channel lı ile bu 1 //channel lı resimleri işleme sokmak bazan bazı metodlarda hata verir. (Runtime //da) ya bu neden bu hatayı verir derseniz bu nedenledir. Benim kodlarımda bu problem OLMAYACAK.
Imgproc.cvtColor(mat, gray_image ,Imgproc.COLOR_RGB2GRAY);
// elimizde artık gray mat objesi var 1 channel lı. Şimdi de original Mat resmi bir //de HLS ye çevirelim. RGB2HVS olanı da var ve bazıları buna çevirir. O da olur //ancak şeritler bazan ağaç gölgesi vb kalır HLS //de bu gölge olsa dahi resim de // çizgiler çok daha net olur bilesiniz.
Imgproc.cvtColor(mat,img_hsv,Imgproc.COLOR_RGB2HLS);
// Gelen resimde şeritler de sarı da beyaz da olacağı için bu 2 renk için de mask oluşturacağız. Bunun //nedeni ilgilendiğimiz pixel leri sadece işlemek istediğimizden.
Scalar lower_yellow = new Scalar(20,100,100);
Scalar upper_yellow = new Scalar(30,255,255);
Mat mask_yellow= new Mat();
// Inrange kullanmamızın sebebi sarı ve beyaz renkleri filtrelemek için. 255 //sonucu döner ise OK aksi halde 0 gelir (her pixel için)
inRange(img_hsv,lower_yellow,upper_yellow,mask_yellow);
Mat mask_white= new Mat();
inRange(gray_image ,new Scalar(200),new Scalar(255),mask_white);
Mat mask_yw= new Mat();
// bitwise_or her iki sarı ve beyaz mask I birleştirmek içindir. 255 dönen her değer //için beyaz veya sarı var demektir.
bitwise_or(mask_white,mask_yellow,mask_yw);
Mat mask_yw_image= new Mat();
// bitwise_and ise yukarıda birleştirilen sarı ve beyaz mask sonucunu original //resme uygulamak içindir. Bakınız altta yukarıdaki sarı ve beyaz dan birleştirilerek //oluşturulan mask_yw değeri gray image e uygulanarak sonuç mask_yw_image e //atılıyor.
bitwise_and(gray_image,mask_yw,mask_yw_image);
Mat gauss_gray= new Mat();
// Gaussian Blur yöntemi bulunan çizgileri daha net yapmak için kullanılır. Kısaca //mask_yw_image resmini daha net yap ve gauss_gray e at diyoruz.
GaussianBlur(mask_yw_image,gauss_gray ,new org.opencv.core.Size(5, 5), 0);
Mat canny_edges= new Mat();
// Şimdi sıra gauss_gray resmindeki şerit çizgilerini bulmaya geldi. Canny edge //metodu bu işe yarıyor..
Canny(gauss_gray,canny_edges, 50, 150);
// Şimdi geldik ROI dediğimiz ekranda resmi hayal edelim bulunduğumuz şeridi //görüyoruz. Sağ ve sol. İşte o alan ile sadece ilgilenmek istiyoruz. Gökyüzü , //ağaçlar vb bizi ilgilendirmiyor. Tam o alan bizim roi miz yani ilgi alanımız. O //alanı vektörel olarak noktaları vererek tanımlamamız lazım.
int imshape0=0;
imshape0=canny_edges.rows(); // Canny_edge resmimizdeki toplam satır sayısı yani resmin genişliği.
int imshape1 = 0;
imshape1=canny_edges.cols(); // Resmin yüksekliği.
// Bu genişlik ve yüksekliğe göre istediğimiz Alani seçecek noktaları belirliyoruz. //Aslında Polygon oluşturacağız. Bu işin püf noktası alttaki kodlar. Çoğu developer //yanlış düşündüğünden bir türlü //yapamaz. Direkt Matofpoint tanımlar içine point //koyar sonra bunu vertices e add yapar . Bu hatalıdır. //Tam da aşağıdaki gibi //olmalı. Yoksa polygon çizilmez ROI de seçilemez.
Point p = new Point(0,imshape0*1);
Point p1 = new Point(imshape1*1,imshape0*1);
Point p2 = new Point(imshape1*0.4,imshape0*0.6);
Point p3 = new Point(imshape1*0.6,imshape0*0.6);
List<Point> vertices= new ArrayList<Point>();
vertices.add(p);
vertices.add(p2);
vertices.add(p3);
vertices.add(p1);
org.opencv.core.Point [] pointArray = new org.opencv.core.Point[vertices.size()];
Point pt;
for(int i = 0; i < vertices.size(); i++) {
pt = vertices.get(i);
pointArray[i] = new org.opencv.core.Point(pt.x, pt.y);
}
MatOfPoint points = new MatOfPoint(pointArray);
// Alttaki kod işin püf noktalarından bir başkasıdır. Bir mask oluşturacağız. Ama // öyle bir mask ki 1 channel olmalı ve içi tamamen 0 yani siyah renk ile dolmalı. //Bu mask I canny_edge ile aynı genişlik ve yükseklik le oluşturmalıyız. 8UC1 8 bit //unsigned integer ve C1 de 1 channel resim demek. 🙂
//Şunu yapmak istiyoruz: Mask I oluşturduk içi sıfır yani siyah. Aslında tamamen //bir resim bu mask diyoruz. Amacımız bu siyah mask içine tanımladığımız polygon //noktalarıyla bir şekil çizmek. Çizdiğimiz şekil şerit li yolda ilgilendiğimiz alan //aslında anladınız siz.
Mat mask = new Mat(canny_edges.rows(), canny_edges.cols(), CvType.CV_8UC1, Scalar.all(0));
Scalar ignore_mask_color = new Scalar(255);
if (canny_edges.rows()>0 && canny_edges.cols()>0 && canny_edges.channels()>0) {
int channel_count = canny_edges.channels();
if (channel_count == 1) ignore_mask_color = new Scalar(255);
else if (channel_count == 2) ignore_mask_color = new Scalar(255, 255);
else if (channel_count == 3) ignore_mask_color = new Scalar(255, 255, 255);
else if (channel_count == 4) ignore_mask_color = new Scalar(255, 255, 255,255);
}
else
ignore_mask_color=new Scalar (255);
fillConvexPoly(mask, points, ignore_mask_color);
// siyah mask yani siyah resim üzerine beyazla pointlerimizi poly yaptık yani. Şimdi //sırada hani en son resmimiz canny_edge vary a işte onu bu siyah mask üzerinde //beyaz alan ile işaretlenen poly mizle bitwise_and e sokacağız. Peki sizce bu //anda çıkacak sonuç resim ne olur? Evet bildiniz ! Simsiyah bir resim üzerinde //beyaz (sarı ve beyaz her ikisi de beyaz olur) otoban şeritleri sadece olur.
Mat roi_image = new Mat();
bitwise_and(canny_edges, mask,roi_image);
int rho = 2;
double theta = Math.PI/180;
int threshold = 20;
int min_line_len = 50;
int max_line_gap = 200;
Mat line_image = new Mat();
Mat lines = new Mat();
// Şimdi yapacağımız ise yukarıdaki parametrelerle bu şeritleri roi_image //üzerinde bulup lines image ına atması.
HoughLinesP(roi_image,lines,rho,theta,threshold,min_line_len,max_line_gap);
// Yine olayın diğer bir püf noktasına geldik. Elimizde ilk anda 4 kanallı resim vardı //bunu değişik mat resimlere çevirdik işlemler yaptık. Bu mat ların hepsi 1 kanallı //idi. Bu adımdan sonra devam eder isek problem yaşayacağız. Çünkü artık 4 //kanallı bir mat MASK I gerekiyor bize. Aşağıda 8UC4 bunu sağlıyor ve yine içini //sıfırla yani siyah ile doldurup aslında bir siyah resim üretiyoruz.
Mat line_img = new Mat(roi_image.rows(), roi_image.cols(), CvType.CV_8UC4, Scalar.all(0));
for (int x = 0; x < lines.rows(); x++) {
double[] vec = lines.get(x, 0);
double x1 = vec[0],
y1 = vec[1],
x2 = vec[2],
y2 = vec[3];
Point start = new Point(x1, y1);
Point end = new Point(x2, y2);
// Bu işlem de lines imag ındaki beyaz şeritli resim siyah Zemin üzerine basılıyor. ! //Kırmızı hale getiriyoruz.
Imgproc.line(line_img, start, end, new Scalar(255, 0, 0),2);
}
// Şimdi addWeighted da original 4 kanallı en baştaki mat resmimize line image //daki her tarafı siyah ve sadece kırmızı ile işaretli şeritli resmi birleştirme //yapıyoruz. Eğer her iki resim de aynı shape yani genişlik, yükseklik ve özellikle //aynı kanal sayısına sahip olmasa bu satırlarda çalışma anında hata alırsınız.
Mat result = new Mat();
addWeighted(mat,0.8,line_img,1.0,0.0,result);
// Ve nihayet mat resmimizi bitmap e çevirip sonucu metod geri dönüş değeri //olarak geri yolluyoruz.
Utils.matToBitmap(result,bitmap);
return bitmap;
}
Sağlıcakla kalın.
Selcuk Celik
merhabalar, öncelikle çok güzel anlatmışsınız emeğinize sağlık. Fakat hough transform parametrelerinden dolayı keskin virajlarda pek sağlıklı çalışacağını düşünmüyorum denediniz mi acaba
Haklı olabilirsiniz denemedim.. Ancak bu sadece bir bakış açısı… Farklı bir yaklaşımla onu da halletmek mümkün..