• Das Erstellen neuer Accounts wurde ausgesetzt. Bei berechtigtem Interesse bitte Kontaktaufnahme über die üblichen Wege. Beste Grüße der Admin

[FRAGE] QR code in Video Erkennung mit JavaScript und OpenCV - Augmented Reality Web App

eclipse240hp

New member
Ich arbeite an einer augmented reality App für den Browser welcher einen QR Code erkennt, der auf einem DIN A4 Blatt ausgedruckt ist und dadurch ein Objekt in den Raum projeziert.
Ich habe im Internet eine fertige Lösung gefunden, die auf ARUCO codes basiert, aber für meinen Zweck benötige ich die Erkennung der Konturen eines QR Codes, da diese gröber sind und auch von größeren Entfernungen (z.B. 3 Meter) erkannt werden.
Hier geht es nicht um das auslesen eines QR codes, sondern nur um die Feststellung der Position.

Ich habe eine Lösung gefunden, die die Konturen des QR Codes erkennt, diese ist aber auf C++ geschrieben und ich habe versucht das Script auf JavaScript umzuschreiben.

Das ist die Lösung mit den ARUCO Codes die in JavaScript geschrieben ist und super funktioniert.
var JS = Augmented Reality in Three.js
Das ist das Script dafür: https://github.com/jeromeetienne/arplayerforthreejs

Das ist das C++ Script, welches QR Codes erkennt:

var C++ = https://github.com/xingdi-eric-yuan/qr-decoder

Soweit habe ich die Logik des C++ Scriptes umgeschrieben, dass mein Programm bereits die 3 Erkennungspunkte des QR Codes feststellen sollte und in die Variable this.vecpair; schreibt.
Leider werden aber Keine 3 festgestellte Konturpaare ausgegeben...

Die Funktion zum analysieren nach QR Codes wird in der Datei "threex.jsarucomarker.js" mit QR.Detector(); aufgerufen und liefert die Konturpaare.

Und das ist meine bisherige Lösung die noch nicht ganz fertig ist aber dennoch schon den QR Code erkennen sollte.. Das Script ist ein Mix aus der ARUCO Lösung "aruco.js" und dem C++ Script qrdecoder.cpp

Code:
var QR = QR || {};

QR.Marker = function(id, corners){
  this.id = id;
  this.corners = corners;
};

QR.Detector = function(){
  this.grey = new CV.Image();
  this.thres = new CV.Image();
  this.homography = new CV.Image();
  this.binary = [];
  this.cont = [];  
  this.vec4i = [];
  this.contours = this.cont.contours = [];
};

QR.Detector.prototype.detect = function(image){
  CV.grayscale(image, this.grey);
  CV.adaptiveThreshold(this.grey, this.thres, 2, 7);
  
  this.contours = CV.findContours(this.thres, this.binary);

  //this.contours = this.findLimitedConturs(this.thres, 8.00, 0.2 * image.width * image.height);
  
 // console.log(this.contours);
  
  this.vecpair = this.getContourPair(this.contours);
  
  console.log(this.vecpair);
  
  // ARUCO CODE.. MAYBE NOT NECESSARY
  //this.candidates = this.findCandidates(this.contours, image.width * 0.10, 0.05, 10);
  //this.candidates = this.clockwiseCorners(this.candidates);
  //this.candidates = this.notTooNear(this.candidates, 10);

  //return this.findMarkers(this.grey, this.candidates, 49);
};

/* C++
struct FinderPattern{
    Point topleft;
    Point topright;
    Point bottomleft;
    FinderPattern(Point a, Point b, Point c) : topleft(a), topright(b), bottomleft(c) {}
};

bool compareContourAreas ( std::vector<cv::Point> contour1, std::vector<cv::Point> contour2 ) {
    double i = fabs( contourArea(cv::Mat(contour1)) );
    double j = fabs( contourArea(cv::Mat(contour2)) );
    return ( i > j );
}
*/

QR.Detector.prototype.compareContourAreas = function(c1,c2){
	
	//console.log('compareContourAreas'+c2);
	
	var i = Math.abs(CV.contourArea(c1));
    var j = Math.abs(CV.contourArea(c2));
    
    if(i > j){
    	return true;
    }
    return false;
};


/* C++
 Point getContourCentre(CONT& vec){
    double tempx = 0.0, tempy = 0.0;
    for(int i=0; i<vec.size(); i++){
        tempx += vec[i].x;
        tempy += vec[i].y;
    }
    return Point(tempx / (double)vec.size(), tempy / (double)vec.size());
}
*/
QR.Detector.prototype.getContourCentre = function(vec){
	
};


/* C++
 bool isContourInsideContour(CONT& in, CONT& out){
    for(int i = 0; i<in.size(); i++){
        if(pointPolygonTest(out, in[i], false) <= 0) return false;
    }
    return true;
}
*/
QR.Detector.prototype.isContourInsideContour = function(c_in, c_out){
	for(var i = 0; i<c_in.length; i++){
		
		//console.log('-- '+c_out+' -- '+c_in[i]);
		
		 if(CV.pointPolygonTest(c_out, c_in[i]) == false) return false;
    }
	console.log('c in c!!');
    return true;
};

/* C++
 vector<CONT > findLimitedConturs(Mat contour, float minPix, float maxPix){
    vector<CONT > contours;
    vector<Vec4i> hierarchy;
    findContours(contour, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
    cout<<"contours.size = "<<contours.size()<<endl;
    int m = 0; 
    while(m < contours.size()){
        if(contourArea(contours[m]) <= minPix){
            contours.erase(contours.begin() + m);
        }else if(contourArea(contours[m]) > maxPix){
            contours.erase(contours.begin() + m);
        }else ++ m;
    }
    cout<<"contours.size = "<<contours.size()<<endl;
    return contours;
}
*/
QR.Detector.prototype.findLimitedConturs = function(contour, minPix, maxPix){
	
		this.contours = this.cont.contours = []; 
		this.hierarchy = this.vec4i.hierarchy = []; 
		 
		CV.findContours(contour, this.contours);
	    
	   // console.log(this.contours);
	    
	    var m = 0; 
	    while(m < this.contours.length){
	        if(CV.contourArea(this.contours[m]) <= minPix){
	            this.contours.splice(this.contours[0] + m,1);
	        }else if(CV.contourArea(this.contours[m]) > maxPix){
	            this.contours.splice(this.contours[0] + m,1);
	        }else ++ m;
	    }
	    
	   // console.log(this.contours.length);
	    
	    return this.contours;
	
};

/*
 vector<vector<CONT > > getContourPair(vector<CONT > &contours){
    vector<vector<CONT > > vecpair;
    vector<bool> bflag(contours.size(), false);

    for(int i = 0; i<contours.size() - 1; i++){
        if(bflag[i]) continue;
        vector<CONT > temp;
        temp.push_back(contours[i]);
        for(int j = i + 1; j<contours.size(); j++){
            if(isContourInsideContour(contours[j], contours[i])){
                temp.push_back(contours[j]);
                bflag[j] = true;
            }
        }
        if(temp.size() > 1){
            vecpair.push_back(temp);
        }
    }
    bflag.clear();
    for(int i=0; i<vecpair.size(); i++){
        sort(vecpair[i].begin(), vecpair[i].end(), compareContourAreas);
    }
    return vecpair;
}
 */
QR.Detector.prototype.getContourPair = function(contours){
	this.vecpair = this.cont.vecpair = [];
	var bflag = new Array(); // similar to c++: vector<bool> bflag(contours.size(), false);?
	
	for(var i = 0; i<contours.length - 1; i++){
		bflag[i] = false;
	}
	
	//console.log(bflag);
	
	for(var i = 0; i<contours.length - 1; i++){
        if(bflag[i] == false){	//similar to c++:  if(bflag[i]) continue; ??        
        	var temp = this.cont.temp = [];
        	
        	//console.log(contours[i]);
        	
        	temp.push(contours[i]); //similar to c++: temp.push_back(contours[i]); ??
	        for(var j = i + 1; j<contours.length; j++){
	            if(this.isContourInsideContour(contours[j], contours[i])){
	                temp.push(contours[j]);
	                bflag[j] = true;
	            }
	        }
	        if(temp.length > 1){
	            this.vecpair.push(temp);
	        }
        }
    }
    
    //console.log(this.vecpair);
    
    bflag = [];
    
    
    
    for(i=0; i<this.vecpair.length; i++){
       // sort(this.vecpair[0], this.vecpair[this.vecpair.length], compareContourAreas);
            	
    	this.vecpair.sort(function(a,b){
    		return a-b;    		
        }); 
    	
    	//console.log('start --- '+this.vecpair[i][0]);
    	//console.log('end ---'+this.vecpair[i][this.vecpair[i].length-1]);
    	
    	console.log('org: '+this.vecpair.length);
    	
    	//console.log(this.compareContourAreas(this.vecpair[i][0], this.vecpair[i][this.vecpair[i].length-1]));
    	
    	if(this.compareContourAreas(this.vecpair[i][0], this.vecpair[i][this.vecpair[i].length-1]) == false)
    	{
    		this.vecpair[1].slice();
    		
    	}
    	
    	console.log('after compare : '+this.vecpair.length);
    	
       // console.log(this.vecpair);
    }
   
    //console.log(this.vecpair);
    
    return this.vecpair;	
};

/* C++
 void eliminatePairs(vector<vector<CONT > >& vecpair, double minRatio, double maxRatio){
    cout<<"maxRatio = "<<maxRatio<<endl;
    int m = 0; 
    bool flag = false;
    while(m < vecpair.size()){
        flag = false;
        if(vecpair[m].size() < 3){
            vecpair.erase(vecpair.begin() + m);
            continue;
        }
        for(int i=0; i<vecpair[m].size() - 1; i++){
            double area1 = contourArea(vecpair[m][i]);
            double area2 = contourArea(vecpair[m][i + 1]);
            if(area1 / area2 < minRatio || area1 / area2 > maxRatio){
                vecpair.erase(vecpair.begin() + m);
                flag = true;
                break;
            }
        }
        if(!flag){
            ++ m;
        }
    }
    if(vecpair.size() > 3){
        eliminatePairs(vecpair, minRatio, maxRatio * 0.9);
    }
}
 */
QR.Detector.prototype.eliminatePairs = function(){};

/* C++
 double getDistance(Point a, Point b){
    return sqrt(pow((a.x - b.x), 2) + pow((a.y - b.y), 2));
}
 */
QR.Detector.prototype.getDistance = function(){};

/* C++
 FinderPattern getFinderPattern(vector<vector<CONT > > &vecpair){
    Point pt1 = getContourCentre(vecpair[0][vecpair[0].size() - 1]);
    Point pt2 = getContourCentre(vecpair[1][vecpair[1].size() - 1]);
    Point pt3 = getContourCentre(vecpair[2][vecpair[2].size() - 1]);
    double d12 = getDistance(pt1, pt2);
    double d13 = getDistance(pt1, pt3);
    double d23 = getDistance(pt2, pt3);
    double x1, y1, x2, y2, x3, y3;
    double Max = max(d12, max(d13, d23));
    Point p1, p2, p3;
    if(Max == d12){
        p1 = pt1;
        p2 = pt2;
        p3 = pt3;
    }else if(Max == d13){
        p1 = pt1;
        p2 = pt3;
        p3 = pt2;
    }else if(Max == d23){
        p1 = pt2;
        p2 = pt3;
        p3 = pt1;
    }
    x1 = p1.x;
    y1 = p1.y;
    x2 = p2.x;
    y2 = p2.y;
    x3 = p3.x;
    y3 = p3.y;
    if(x1 == x2){
        if(y1 > y2){
            if(x3 < x1){
                return FinderPattern(p3, p2, p1);
            }else{
                return FinderPattern(p3, p1, p2);
            }
        }else{
            if(x3 < x1){
                return FinderPattern(p3, p1, p2);
            }else{
                return FinderPattern(p3, p2, p1);
            }
        }
    }else{
        double newy = (y2 - y1) / (x2 - x1) * x3 + y1 - (y2 - y1) / (x2 - x1) * x1;
        if(x1 > x2){
            if(newy < y3){
                return FinderPattern(p3, p2, p1);
            }else{
                return FinderPattern(p3, p1, p2);
            }
        }else{
            if(newy < y3){
                return FinderPattern(p3, p1, p2);
            }else{
                return FinderPattern(p3, p2, p1);
            }
        }
    }
}
 */

QR.Detector.prototype.getFinderPattern = function(){};



Die folgenden Funktionen sind meine erstellten CV Funktionen die in der "cv.js" (https://github.com/jeromeetienne/arplayerforthreejs) enthalten sind und die Originalen OpenCV Funktionen "darstellen".

Eventuell scheitert es bei der Analyse ja an dieser Stelle...

Diese Funktionen sollten die selbe Funktion haben wie bei OpenCV in C++

pointPolygonTest() = Point Polygon Test — OpenCV 2.4.13.1 documentation

contourArea() = Structural Analysis and Shape Descriptors — OpenCV 2.4.13.1 documentation

Code:
//src: http://jsfromhell.com/math/is-point-in-poly
    CV.pointPolygonTest = function(poly, pt){
        for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
            ((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
            && (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
            && (c = !c);
        return c;
    };

//http://stackoverflow.com/questions/16285134/calculating-polygon-area
    CV.contourArea = function(cont){    
        //console.log('cont: '+cont);

        var area = 0;  // Accumulates area in the loop   
        var j = cont.length-1;  // The last vertex is the 'previous' one to the first

          for (var i=0; i<cont.length; i++)
          { 
              area = area + (cont[j].x+cont[i].x) * (cont[j].y+cont[i].y)
              //area = area +  (X[j]+X[i]) * (Y[j]-Y[i]); 
              j = i;  //j is previous vertex to i
          }   
          return area/2;

    };

Vielen Dank für jede Hilfe!
Vielleicht schaffen wir es ja zusammen das ganze zum Laufen zu bringen :)
 
Zuletzt bearbeitet:
ohne lauffähiges beispiel wirst du wohl kaum jemanden begeistern sich durch deinen code zu wühlen.
das wichtigste dürfte sein, daß du dir den output darstellen kannst, da du sonst wohl kaum begutachten kannst was da schief läuft.
ich würde an deiner stelle mal mit this.thres anfangen, liefert das überhaupt ein brauchbares bild?

ansonsten habe ich da meine zweifel ob du mit 3 zeilen js einen bibliothek wie openCV ersetzen kannst.
da du ja sowieso an das bild kommen musst, wo kommt das denn her und kannst du das nicht nativ mit openCV aufbereiten?
 
clientseitig hat er doch ein 700-zeilen js gepostet was openCV in clientseitigem js sein "soll"

- - - Aktualisiert - - -

achso, du meinst mich, ich dachte das wäre ein weiterer
also nee, gibt es nicht, aber das bild bekommst du ja nicht im browser, sondern woanders her, da muss dann opencv laufen
 
Den Input und Output habe ich ja fertig.
Woran ich arbeite ist die Erkennung der 3 QR Vierecke, was ich jetzt hinbekommen habe.
Ich bin aktuell dabei die Position zu bestimmen.
 
Zurück
Oben