Bildverarbeitung - Köttelerkennung für XYZ Roboter

Hallo liebe Forumsmitglieder,

Ich möchte mich in Sachen Bildverarbeitung und Sensorik weiterbilden, und zu diesem Zweck einen kleinen XYZ-Roboter bauen, der einen Meerschweinchenkäfig sauberhält.

Nach mehreren gebauten 3D Druckern und einer großen Fräse, fällt mir die mechanische Planung leichter und wird erst am Schluss mit vorhandenem Schrott & notwendigen Neuteilen erfolgen.

Die Köttel-Erkennung ist aber Neuland für mich, und bräuchte da einiges an Tipps und Hilfe wie man das lösen kann.

Meine Ideen bisher:

Eine Kamera überwacht von oben den Käfig, die Bildpunkte werden analysiert. Die Einstreu am Boden des Käfigs ist viel heller als die Köttel, somit könnte man anhand der Helligkeit der einzelnen bzw benachbarten Bildpunkte rausfinden, wo die Köttel liegen, und einen kleinen Greifer hinsteuern.

Eine andere Möglichkeit wäre, den Boden des Käfigs mit einem Ultraschallsensor abzuscannen. Solange die Frequenz überhalb des Hörbereichs der Tiere liegt dürfte das kein Problem sein, muss aber noch testen ob so kleine Objekte in & auf der Einstreu erkannt werden.

Ersteres gefällt mir besser, vielleicht hat auch jemand eine noch elegantere Lösung?

Gibt es bereits ähnliche Projekte zum adaptiern?

Lg

Hallo,

hatte mal was gelesen und denke für die Bilderkennung ist ein Raspberry notwendig. Passendes Kameramodul gibts dafür ja. Das Prinzip läuft über Kontrastunterschiede wenn ich mich recht entsinne. In dem Bereich sollte es auch Bsp. geben mit dem Raspi.
Ob Du dann noch einen Arduino für den Robi nimmst oder alles auf dem Raspi machts ist dir überlassen.
Nur bin ich mir sicher das eine 16MHz 8Bit Atmel CPU mit Kamera und Bildberechnung einfach überfordert ist.

Für Bildverarbeitung dürfte ein größerer Prozessor notwendig sein, damit ein ausreichend großer Teil des Bildes in seinen Speicher paßt. Zur US-Variante kann ich nichts sagen, was glaubst Du denn damit feststellen zu können?

Beim Greifen könnte ich mir vorstellen, daß der Greifer das Streu verschiebt, und dann daneben greift. Ich würde mal überlegen, den Sensor (welchen auch immer) an den Greifer zu montieren, und damit den Boden abzuscannen.

Es soll sehr minimalistisch bleiben, theoretisch müsste ein 16x16 Pixel Chip aus einer Optischen Maus ausreichen, um damit den Boden abzuscannen. In der richtigen Entfernung zum Boden müsste ein Köttel dann als dunkler Pixel dargestellt werden.

Bilddaten Umrechnen und Kompressionsverfahren will ich mir sparen wenns nicht zwingend erforderlich ist, ein Hell/Dunkel oder RGB-Wert je Pixel reicht schon aus.

Hab grad meine Microsoft Maus zerlegt, aber keine Chip-Beschriftung gefunden.
Aber in einer Logitech Maus bin ich fündig geworden, ein S5085 Chip, hat 18 Pins, finde aber kein Datenblatt zum Chip :frowning:
Sind die Pins bei den verschiedenen 18-Pin-Typen universell oder unterschiedlich belegt?

Sind die Pins bei den verschiedenen 18-Pin-Typen universell oder unterschiedlich belegt?

unterschiedlich belegt

Hmm, dann wirds schwierig, werd mir lieber einen dokumentierten Chip besorgen.

Danke soweit, und falls noch jemand Ideen für andere Hardware hat, bitte Posten. Es soll nur minimalistisch bleiben und mit einem Nano laufen. Lg!

Ein Freund hat mir seine alte Funkmaus überlassen, darin sitzt ein ADNS1620 :slight_smile:

Laut DATENBLATT 18x18 Pixel deren Werte sich auslesen lassen... irgendwie.

Bisher habe ich mir den Code von http://www.bidouille.org/hack/mousecam angeschaut und mal die Adressierung der Register angepasst:

#define SCLK 2
#define SDIO 3
// #define PD 4

#define REG_CONFIG_BITS 0x40
#define REG_PRODUCT_ID 0x41 //Asleep-Wake
#define REG_DELTA_Y 0x42
#define REG_DELTA_X 0x43
#define REG_SQUAL 0x44
#define REG_MAXIMUM_PIXEL 0x45
#define REG_MINIMUM_PIXEL 0x46
#define PIXEL_SUM 0x47
#define PIXEL_DATA 0x48
#define REG_SHUTTER_UPPER 0x49
#define REG_SHUTTER_LOWER 0x4A
#define REG_FRAME_PERIOD 0x4B
// #define REG_REVISION_ID 0x01
// #define REG_MOTION 0x02
// #define REG_DATA_OUT_LOWER 0x0C
// #define REG_DATA_OUT_UPPER 0x0D
// #define REG_FRAME_PERIOD_LOWER 0x10
// #define REG_FRAME_PERIOD_UPPER 0x11

int dumpWidth = 324; // Number of pixels to read for each frame.
byte frame[324];

void setup() {
  Serial.begin(115200);

  reset();
  byte productId = readRegister(REG_PRODUCT_ID);
  Serial.print("ProductId ");
  Serial.println(productId, HEX);

  byte config = readRegister(REG_CONFIG_BITS);
  config |= B00100001; // Don't sleep (LED always powered on).
  writeRegister(REG_CONFIG_BITS, config);

}

void loop() {
  // Allows to set the dump window by sending the number of lines to read via the serial port.
  if(Serial.available() > 0) {
    dumpWidth = 18 * Serial.read();
    dumpWidth = constrain(dumpWidth, 0, 324);
  }

//  readRegister(REG_MOTION); // Freezes DX and DY until they are read or MOTION is read again.
  char dx = readRegister(REG_DELTA_X);
  char dy = readRegister(REG_DELTA_Y);
  Serial.print("DELTA:");
  Serial.print(dx, DEC);
  Serial.print(" ");
  Serial.println(dy, DEC);

  if( dumpWidth > 0 )
    dumpFrame();
}

void dumpFrame() {
  byte config = readRegister(REG_CONFIG_BITS);
  config |= B00000001; // PixDump
  writeRegister(REG_CONFIG_BITS, config);

  int count = 0;
  do {
//    byte data = readRegister(REG_DATA_OUT_LOWER);
//    if( (data & 0x80) == 0 ) { // Data is valid
      frame[count++]; // = data;
    }
//  } 

  while (count != dumpWidth);

  config = readRegister(REG_CONFIG_BITS);
  config &= B11110111;
  writeRegister(REG_CONFIG_BITS, config);


  Serial.print("FRAME:");
  for(int i = 0; i < dumpWidth; i++) {
    byte pix = frame[i];
    if( pix < 0x10 )
      Serial.print("0");
    Serial.print(pix, HEX);
  }
  Serial.println();
}

void reset() {
  pinMode(SCLK, OUTPUT);
  pinMode(SDIO, INPUT);
//  pinMode(PD, OUTPUT);
  digitalWrite(SCLK, LOW);
//  digitalWrite(PD, HIGH);
  delayMicroseconds(1);
//  digitalWrite(PD, LOW);
}

byte readRegister(byte address) {
  pinMode (SDIO, OUTPUT);

  for (byte i=128; i >0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SDIO, (address & i) != 0 ? HIGH : LOW);
    digitalWrite (SCLK, HIGH);
  }

  pinMode (SDIO, INPUT);

  delayMicroseconds(100); // tHOLD = 100us min.

  byte res = 0;
  for (byte i=128; i >0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SCLK, HIGH);
    if( digitalRead (SDIO) == HIGH )
      res |= i;
  }

  return res;
}

void writeRegister(byte address, byte data) {
  address |= 0x80; // MSB indicates write mode.
  pinMode (SDIO, OUTPUT);

  for (byte i = 128; i > 0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SDIO, (address & i) != 0 ? HIGH : LOW);
    digitalWrite (SCLK, HIGH);
  }

  for (byte i = 128; i > 0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SDIO, (data & i) != 0 ? HIGH : LOW);
    digitalWrite (SCLK, HIGH);
  }

  delayMicroseconds(100); // tSWW, tSWR = 100us min.
}

Vorsicht, der Code lässt sich zwar kompilieren, funktioniert aber noch nicht.

Leider blicke ich bei dem vielen Englisch und einigen Befehlen noch nicht ganz durch. Soviel ich verstanden habe müssen noch die Timingzeiten und Reihenfolge der Bits ans Datenblatt angepasst werden, damit ich den 1620 richtig begrüßen und fragen kann wie´s den Pixeln so geht.

Kann mir da jemand weiterhelfen?

Das Auslesen der Pixel ist wirklich verwirrend beschrieben :frowning:

To dump a complete image, set the LED to forced awake mode, write anything to this register, then read 324 times where the Data Valid bit is set. On the 325th read, the StartOfFrame bit will be set indicating that we have completed one frame of pixels and are starting back at pixel 1.

Das klingt ja noch verständlich, nun haben wir 324 Bytes gelesen, was der Anzahl Pixels entspricht. Aber dann dies:

It takes at least 324 frames to complete an image as we can only read 1 pixel per frame.

Da vermute ich einen Schreibfehler, oder eine ganz andere Sorte "frame" (=read operation?). Anscheinend muß man jedes Byte einzeln abholen, d.h. Adresse schicken, Byte lesen, Adresse schicken...

Die readRegister und writeRegister Funktionen sehen sehr kreativ aus, sollten aber funktionieren. Ich hätte dafür direkt mit SPI read/write gearbeitet.

Danke für die Info, ich muss mir das Thema SPI nochmal anschaun. Mittlerweile hab ich einen Code gefunden, der sogar funktioniert, kann die Pixel auslesen :slight_smile:

Statt Graustufen-Zahlenwerte lasse ich mir Symbole im 18x18 Raster anzeigen, X für ganz Dunkel, Leerzeichen für ganz hell, dazwischen sind x, : und ., dadurch ergibt sich ein einfaches Bild.

Sogar Linse habe ich eine passende gefunden, und kann jetzt ausgemalte Blöcke eines karierten Blattes
von 10cm Entfernung erkennen. Funktioniert besser als erwartet, jetzt muss ich mir eine Logik überlegen die Mitte in einem/mehreren dunklen Objekt zu finden...

Die Linse passt doch nicht so ganz, hab schon 2 Tage lang probiert sie besser auszurichten, aber es klappt nicht. Wenn sie zu weit entfernt ist, habe ich ein superscharfes Bild, aber der Rand ist dunkel, dadurch verliere ich nutzbaren Bildbereich und müsste das ausbügelmäßig umprogrammieren. Schraub ich sie weiter rein, habe ich ein relativ gleichmäßig ausgeleuchtetes Bild, aber sehr unscharf und zu stark vergrößert, nutzbarer Scanbereich ca 3x3cm.
Da will ich jedenfalls noch nachbessern, zumindest 10x10cm Scanbereich solltens schon sein.

Die Beleuchtung muss scheinbar sehr hell sein, und reagiert auf rot besser als auf grün. Helles weißes Licht zb von einer 3W LED Lampe geht auch sehr gut wenn man die Leds um die Linse herum verteilt.
Ich habe jetzt noch ein paar alte Mäuse geschlachtet, um die eingebauten LEDs zu verwenden, funktioniert ganz gut. 4 dieser LEDs reichen bis ca 10cm Abstand, darüber wird das Bild sehr dunkel.

Hier der Code für den ADNS 1620:

/*  
 Serial driver for ADNS2010, by Conor Peterson (robotrobot@gmail.com)
 Serial I/O routines adapted from Martjin The and Beno?t Rosseau's work.
 
 Code bearbeitet von Kolbi (nachrichtannick@gmail.com) für ADNS1620 18x18 Pixel Sensor
 Neu: -Registeradressierung für A-1620
      -Vereinfachte 18x18 Bildausgabe
      -Min/Max Helligkeit & Position
      
      
 Datenblatt Agilent ADNS-1620:
 http://attach3.bdwm.net/attach/boards/ElecDIY/M.1364664063.A/adns-1620_ds.pdf
 
*/


const byte CLOCK = 2; // A1620 SCK Pin an Arduino Digital Pin 2
const byte SDIO = 3; // A1620 SDIO Pin an Arduino Digital Pin 3
const byte regConfig    = 0x40;
const byte regPixelData = 0x48;
const byte maskNoSleep  = 0x01;
#define FRAMELENGTH 324
byte frame[FRAMELENGTH];
int counter = 0;
int dunkel = 0;
int dunkelpos = 0;
int hell = 0;
int hellpos = 0;
int input;  


//++++++++++++++++++++++ SETUP +++++++++++++++++++
void setup()
{
  pinMode(CLOCK, OUTPUT);
  pinMode(SDIO, OUTPUT); 
  Serial.begin(38400); // Nicht vergessen, im Serial Monitor 38400 Baud einstellen!
  Serial.flush();
  Serial.print("Bildpunktpositionen werden dargestellt");
  delay(300);
  Serial.print(".");
  delay(300);
  Serial.print(".");
  delay(300);
  Serial.println(".");
  Serial.println();


// Zeige die Position der einzelnen Bildpunkte

  for( input = 0; input < FRAMELENGTH; input++ ) {
    if(input<10) Serial.print("  ");
    else if(input<100) Serial.print(" ");
    Serial.print(input+1);
    Serial.print(" ");
    ++counter;
    if(counter==18) {
      Serial.println(" ");
      Serial.println(" ");
      counter=0;
    }
  }

  Serial.println();
  Serial.println("Fuer Bildausgabe Enter druecken.");
  
  delay(2000);
  mouseInit();
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++ HAUPTSCHLEIFE ++++++++++++++++++
//++++++++++++++++++++++++++++++++++++++++++++++++++++
void loop() {
while(Serial.available() > 0) {
  readFrame(frame);

  // Hellsten & Dunkelsten Pixel errechnen
  dunkel = 60;
  hell = 0;
  for( input = 0; input < FRAMELENGTH; input++ ) {
    if(frame[input]<dunkel) {
      dunkel=frame[input];
      dunkelpos=input;
    }
    if(frame[input]>hell) {
      hell=frame[input];
      hellpos=input;
    }
  }
  Serial.print("Hellster Bildpunkt: ");
  Serial.print("Position=");
  Serial.print(hellpos+1);
  Serial.print("   Wert=");
  Serial.println(hell);
  Serial.print("Dunkelster Bildpunkt: ");
  Serial.print("Position=");
  Serial.print(dunkelpos+1);
  Serial.print("   Wert=");
  Serial.println(dunkel);

  // Vereinfachte Bilddarstellung
  for( input = 0; input < FRAMELENGTH; input++ ) {
    if(frame[input]>40) Serial.print("  ");
    else if(frame[input]>30) Serial.print(". ");
    else if(frame[input]>20) Serial.print(": ");
    else if(frame[input]>10) Serial.print("x ");
    else Serial.print("X ");

    counter = counter+1;
    if(counter==18) {
      Serial.println(" ");
      counter=0;
    }
  }
    Serial.flush();
}
}


// ++++++++++++++++++++++ VOIDS +++++++++++++++++++++++

void mouseInit(void)
{
  digitalWrite(CLOCK, HIGH);
  delayMicroseconds(5);
  digitalWrite(CLOCK, LOW);
  delayMicroseconds(1);
  digitalWrite(CLOCK, HIGH);
  delay(1025);
  writeRegister(regConfig, maskNoSleep); //Force the mouse to be always on.
}


void writeRegister(byte addr, byte data)
{
  byte i;

  addr |= 0x80; //Setting MSB high indicates a write operation.

  //Write the address
  pinMode (SDIO, OUTPUT);
  for (i = 8; i != 0; i--)
  {
    digitalWrite (CLOCK, LOW);
    digitalWrite (SDIO, addr & (1 << (i-1) ));
    digitalWrite (CLOCK, HIGH);
  }

  //Write the data    
  for (i = 8; i != 0; i--)
  {
    digitalWrite (CLOCK, LOW);
    digitalWrite (SDIO, data & (1 << (i-1) ));
    digitalWrite (CLOCK, HIGH);
  }
}

byte readRegister(byte addr)
{
  byte i;
  byte r = 0;

  //Write the address
  pinMode (SDIO, OUTPUT);
  for (i = 8; i != 0; i--)
  {
    digitalWrite (CLOCK, LOW);
    digitalWrite (SDIO, addr & (1 << (i-1) ));
    digitalWrite (CLOCK, HIGH);
  }

  pinMode (SDIO, INPUT);  //Switch the dataline from output to input
  delayMicroseconds(110);  //Wait (per the datasheet, the chip needs a minimum of 100 µsec to prepare the data)

  //Clock the data back in
  for (i = 8; i != 0; i--)
  {                             
    digitalWrite (CLOCK, LOW);
    digitalWrite (CLOCK, HIGH);
    r |= (digitalRead (SDIO) << (i-1) );
  }

  delayMicroseconds(110);  //Tailing delay guarantees >100 µsec before next transaction

  return r;
}


//ADNS2610 dumps a 324-byte array, so this function assumes arr points to a buffer of at least 324 bytes.
void readFrame(byte *arr)
{
  byte *pos;
  byte *uBound;
  unsigned long timeout;
  byte val;

  //Ask for a frame dump
  writeRegister(regPixelData, 0x2A);

  val = 0;
  pos = arr;
  uBound = arr + 325;

  timeout = millis() + 1000;

  //There are three terminating conditions from the following loop:
  //1. Receive the start-of-field indicator after reading in some data (Success!)
  //2. Pos overflows the upper bound of the array (Bad! Might happen if we miss the start-of-field marker for some reason.)
  //3. The loop runs for more than one second (Really bad! We're not talking to the chip properly.)
  while( millis() < timeout && pos < uBound)
  {
    val = readRegister(regPixelData);

    //Only bother with the next bit if the pixel data is valid.
    if( !(val & 64) )
      continue;

    //If we encounter a start-of-field indicator, and the cursor isn't at the first pixel,
    //then stop. ('Cause the last pixel was the end of the frame.)
    if( ( val & 128 ) 
      &&  ( pos != arr) )
      break;

    *pos = val & 63;
    pos++;
  }
  
}

20151125_104722.jpg

20151125_104808.jpg

hi,

Ich habe jetzt noch ein paar alte Mäuse geschlachtet

und was sagt das meerschweinchen zu diesen morden ?

gruß stefan

Helles weißes Licht zb von einer 3W LED Lampe geht auch sehr

Willst Du die Meerschweinchen bräunen?
Grüße Uwe

Meerschweinchen sind Rassisten, die mögen eh keine Mäuse :o

Ich schätze mal sie werden den Scanner/Greifarm im Betrieb meiden, evtl sogar lernen in die Kloecke zu machen damit der Greifer nicht ständig werkelt.

Hat noch jemand Ideen zur Belinsung vom Chip, um größere Bereiche scannen zu können?

Und wie kann ich Bilder vom Anhang in den Beitrag als Bild/Vorschau einfügen?
Lg

Hat noch jemand Ideen zur Belinsung vom Chip, um größere Bereiche scannen zu können?

Eine billige Weitwinkel-/Fisheyelinse, z.B. das Spielzeug, das als Handyzubehör verkauft wird.

Hier noch billiger.

Wäre es nicht einfacher, die Tierchen so zu dressieren, dass sie nur noch in eine festgelegte Ecke scheißen?

Gruß,

Helmuth

Gute Idee, vielleicht finde ich sowas iwo rumliegen, evtl geht auch die kleine Linse aus einem alten Handy.

Von kleinauf lassen sie sich normalerweise gut dressieren, aber die sind jetzt schon zu alt und ich hab keine zeit 24h Babysitter spielen, das muss eine Maschine in meiner Abwesenheit machen.

Ich möchte mich in naher Zukunft wieder mit Tieren beschäftigen, und neue Haltungsmethoden testen. Unter anderem sollen die Tiere auch ein besseres Umfeld bekommen und nicht immer in der eigenen Scheiße schlafen müssen. Automatisierte Trainings sollen stufenweise das Verhalten
und den Nutzen verbessern.

Bei Meerschweinchen ist es eh relativ einfach, man nutzt hauptsächlich die Ausscheidungen für Biogas und feinsten Dünger, bei langhaarigen wie den meinigen wär dann noch eine automatische Schurstation sinnvoll, um das Fell zu nutzen.

In Südamerika wird übrigens auch das Fleisch genutzt, aber keine Sorge, meine kommen nicht in den Suppentopf ^^

Es geht einiges weiter, die Tests sind gut ausgefallen, jetzt bau ich schon die 3. Version vom XY Rahmen:

20151128_170712.jpg

20151202_125714.jpg

20151203_140933.jpg

20151203_140943.jpg

Es funktioniert! :smiley:

LOL, das sieht gut aus. Gefällt mir!

Grüße,

Helmuth

Danke für das Kompliment :slight_smile:

Bin jedenfalls sehr überrascht dass es so schnell funktioniert hat, jetzt muss das Ganze nur noch optimiert werden.

  1. Am Rand werden schräge Leisten montiert damit die Köttel erreichbar für den Greifer bleiben.

  2. Die Bildeinlesezeit vom ADNS ist extrem hoch, ich dachte zuerst dass der Motor da durchfährt ohne merklich zu stoppen. Die Stopps sind nur während frame capture und dunkelste Pixelposition errechnen, ganz blicke ich da nicht durch warum das so lange dauert.?

Lg

das arme meerschwein, du hast auch wenig streu drin :slight_smile:
aber die idee ist gut, ich such noch sowas für ein katzenklo.