package camera; import java.awt.image.BufferedImage; import java.util.*; import java.util.Collections; /* * @author Marcel Schubert * * Klasse fuer Kalibrierung des Schachfelds unter der Kamera * sowie der Berechnung der geaenderten Schachfiguren */ public class ImageLoader { private final int FIELDS = 64; private int width, height; // groesse Schachbrett (nicht Bild) private ArrayList<Integer> r1, r2, g1, g2, b1, b2; private ArrayList<Integer> diffR, diffG, diffB; private int offsetX1, offsetX2, offsetY1, offsetY2; private List<Field> rgbFieldDiff = new ArrayList<Field>(); private Vec2f a, b; private float angle = 0; //mein internes system auf die andere Nummerierung anpassen private int[] fieldConversion = {57,58,59,60,61,62,63,64, 49,50,51,52,53,54,55,56, 41,42,43,44,45,46,47,48, 33,34,35,36,37,38,39,40, 25,26,27,28,29,30,31,32, 17,18,19,20,21,22,23,24, 9,10,11,12,13,14,15,16, 1, 2, 3, 4, 5, 6, 7, 8}; ImageRotator ir; ImageGrabber grabber; public ImageLoader() { a = new Vec2f(); b = new Vec2f(); grabber = new ImageGrabber(); ir = new ImageRotator(); r1 = new ArrayList<Integer>(); g1 = new ArrayList<Integer>(); b1 = new ArrayList<Integer>(); r2 = new ArrayList<Integer>(); g2 = new ArrayList<Integer>(); b2 = new ArrayList<Integer>(); diffR = new ArrayList<Integer>(); diffG = new ArrayList<Integer>(); diffB = new ArrayList<Integer>(); } /* * Offset eintragen, also Abstand vom Rand des Bildes bis zum eigentlichen * Schachfeld * * @param offsetX1 xWert links oben * * @param offsetY1 yWert links oben * * @param offsetX2 xWert rechts unten * * @param iffsetY2 yWert rechts unten */ public void setOffset(int offsetX1, int offsetY1, int offsetX2, int offsetY2) { this.offsetX1 = offsetX1; this.offsetX2 = offsetX2; this.offsetY1 = offsetY1; this.offsetY2 = offsetY2; // System.out.println(offsetX1 + " " + offsetY1 + " " + offsetX2 + " " + offsetY2); } /* * setzt den Vektor A, welcher auf linke obere Ecke des Schachfeldes zeigt * * @param x links oben x Wert * * @param y links oben y Wert */ public void setVecA(float x, float y) { this.a.setX(x); this.a.setY(y); } /* * setzt den Vektor B, welcher auf rechte untere Ecke des Schachfeldes zeigt * * @param x rechts unten x Wert * * @param y rechts unten y Wert */ public void setVecB(float x, float y) { this.b.setX(x); this.b.setY(y); } /* * Winkel um den das Schachfeld gedreht werden muss um horizontal zu sein * * @angle Winkel in Grad */ public void setAngle(float angle) { this.angle = angle; } /* * Winkel um den das Schachfeld gedreht werden muss um horizontal zu sein * * @return Winkel in Grad */ public float getAngle() { return this.angle; } /* * Gibt eine Liste der veraenderten Positionen zurueckTop 2 (4 bei Rochade) * mit meister Aenderung werden zurueckgegeben * * @return Liste mit "Field" Elementen. Entweder 2 oder 4(Rochade) */ public List<Integer> getChangedPositions() { setFieldDiff(); List<Field> sortedList = sortList(this.rgbFieldDiff); int average = sampleAverage(); int stDev = standardDeviation(average); List<Integer> l = new ArrayList<Integer>(); boolean rochadePossible = true; // Rochade nur auf der obersten o. untersten Reihe moeglich for (int i = 0; i < 4 && rochadePossible; i++) { if (sortedList.get(i).getPosition() > 7 && sortedList.get(i).getPosition() < 56) { rochadePossible = false; } } //+1 weil schachsystem von 1-64 felder System.out.println("FELD1 "+fieldConversion[sortedList.get(0).getPosition()]); System.out.println("FELD2 "+fieldConversion[sortedList.get(1).getPosition()]); l.add(fieldConversion[sortedList.get(0).getPosition()]); l.add(fieldConversion[sortedList.get(1).getPosition()]); // wenn rochade moeglich ist, dann wird noch geprueft, ob // die werte der felder ausserhalb der 2fachen standardabweichung vom // mittelwert // liegen. Wenn ja, dann ist eine Rochade sehr wahrscheinlich. if (rochadePossible) { if ((sortedList.get(2).getValue() > (average + 1 * stDev)) && (sortedList.get(3).getValue() > (average + 1 * stDev))) { l.add(fieldConversion[sortedList.get(2).getPosition()]); l.add(fieldConversion[sortedList.get(3).getPosition()]); } } return l; } /* * Sortiert die Liste mittles Collections Framework * * @return absteigend sortierte Liste */ private List<Field> sortList(List<Field> l) { Collections.sort(l); //System.out.println(); //System.out.println(l.size()); // for (int i = 0; i < l.size(); i++) { // System.out.println("Nummer " + l.get(i).getPosition() + " Wert: " + l.get(i).getValue()); // } return l; } /* * Berechnet die Differenz der 64 Felder aus Bild 1 und Bild 2 uns speichert * sie in einer Liste als Typ "Field". */ private void setFieldDiff() { if (r1 != null && r2 != null && g1 != null && g2 != null && b1 != null && b2 != null) { for (int i = 0; i < FIELDS; i++) { Field a = new Field(i, (int) Math.abs(getPositionAverage(i, 1) - getPositionAverage(i, 2))); rgbFieldDiff.add(a); if (i % 8 == 0) { // System.out.println(); } } } else { // System.out.println("Erst 2 Fotos machen"); } } /* * Vergleicht die 2 Schachbilder miteinander und erkennt, ob eine Figur * bewegt wurde anmerkung: funktioniert schon, return aber noch nicht fertig * * @return Array mit geaenderten Feldern */ public void compareFields() { int average = sampleAverage(); int stDev = standardDeviation(average); for (int i = 0; i < FIELDS; i++) { if (i % 8 == 0) { // System.out.println(); } // System.out.print("\t" + getPositionAverage(i, 1)); } System.out.println(); for (int i = 0; i < FIELDS; i++) { if (i % 8 == 0) { // System.out.println(); } // System.out.print("\t" + getPositionAverage(i, 2)); } // System.out.println(); for (int i = 0; i < FIELDS; i++) { if (i % 8 == 0) { // System.out.println(); } // System.out.print("\t" + rgbFieldDiff.get(i).getValue()); } // System.out.println(); for (int i = 0; i < FIELDS; i++) { if (i % 8 == 0) { // System.out.println(); } if (rgbFieldDiff.get(i).getValue() > average + stDev) { // System.out.print("\t" + "1"); } else { // System.out.print("\t" + "0"); } } // System.out.println("\n Toleranz:" + average + " stdabweichung" + stDev); } /* * EDIT: MOMENTAN NUR ROTE SPIELSTEINE! Mittelwert der RGB-Werte eines der * 64 Schachfelder wird berechnet * * @param position Position des Schachfeldes (oben links 0, unten rechts 63) * * @param image aus welchem Bild der RGB Wert genommen werden soll (1 oder * 2) * * @return durchschnittswert rgb */ private int getPositionAverage(int position, int image) { int unitWidth = (int) width / 8; int unitHeight = (int) height / 8; int unitX1 = position % 8; int unitY1 = (int) position / 8; int unitX2 = unitX1 + 1; int unitY2 = unitY1 + 1; int startX = unitX1 * unitWidth; int startY = unitY1 * unitHeight; int endX = unitX2 * unitWidth; int endY = unitY2 * unitHeight; // momentan nur ROTE SPIELSTEINE int value = 0; for (int y = startY; y < endY; y++) { for (int x = startX; x < endX; x++) { if (image == 1) { value = value + r1.get(y * width + x) + g1.get(y * width + x) + b1.get(y * width + x); // value = value + g1.get(y * width + x); } else if (image == 2) { value = value + r2.get(y * width + x) + g2.get(y * width + x) + b2.get(y * width + x); // value = value + g2.get(y * width + x); } else { value = value + diffR.get(y * width + x) + diffG.get(y * width + x) + diffB.get(y * width + x); // value = value + diffG.get(y * width + x); } } } return (int) (value / 3) / (unitWidth * unitHeight); } public String numberToRaster(int position) { String pos = ""; String[] character = { "a", "b", "c", "d", "e", "f", "g", "h" }; pos = character[position % 8]; pos += (8 - (int) (position / 8)); return pos; } /* * berechnet den Offset, der um das Schachbrett als Rand bleibt. Dieser wird * dann bei spaeterer Berechnung abgeschnitten Offset: linke obere Ecke, * rechte untere Ecke. */ public void calcOffset() { // OffsetGUI offsetGUI = new OffsetGUI(w.getImage(), this); BufferedImage img = grabber.getImage(); img = ir.getRotatedImage(img, this.angle); OffsetGUI offsetGUI = new OffsetGUI(img, this, true); while (offsetGUI.getStatus() != 'n') { try { Thread.sleep(1); } catch (Exception e) { e.printStackTrace(); } } this.width = offsetX2 - offsetX1; this.height = offsetY2 - offsetY1; // System.out.println("breite:" + this.width + " hoehe " + this.height); } /* * Berechnet den Winkel, um den das Spielfeld gedreht werden muss, damit der * obere Rand des Schachfelds horizontal ist * * @return Winkel in Grad */ public float calcAngle() { OffsetGUI offsetGUI = new OffsetGUI(grabber.getImage(), this, false); while (offsetGUI.getStatus() != 'n') { try { Thread.sleep(1); } catch (Exception e) { e.printStackTrace(); } } Vec2f diagon = new Vec2f(); diagon.calcVec2f(a, b); return -(diagon.getAngle(new Vec2f(100, 0)) - 45); } /* * Ermittelt den durchschnittlichen Toleranzdifferenzwert der beiden Bilder * Z.B. unterschiedliche Lichtverhaeltnisse erfordern andere Toleranzen... * (Mittelwert) * * @return mittlere Differenz der beiden Bilder Pixel pro Pixel */ private int sampleAverage() { int tolerance = 0; for (int i = 0; i < FIELDS; i++) { tolerance += rgbFieldDiff.get(i).getValue(); } return (int) Math.round(tolerance / FIELDS); } /* * Einfache Standardabweichung von der durchschnittlichen Toleranzdifferenz * * @param average Mittelwert * * @return mittlere Abweichung vom Mittelwert */ private int standardDeviation(int average) { // nach Formel Var(x) = E(X^2)-E(X)^2 und sqrt(Var(X)) = // Standardabweichung int t = 0; int av2 = (int) Math.round(Math.pow(average, 2)); for (int i = 0; i < FIELDS; i++) { // t += (int) Math.pow((diffR.get(i) + diffG.get(i) + diffB.get(i)) // / 3, 2); t += (int) Math.pow((rgbFieldDiff.get(i).getValue()), 2); } t = Math.round(t / FIELDS); return (int) Math.sqrt(t - av2); } /* * Erste Vergleichsfoto machen * * @param file Pfad zum Foto */ public boolean takePhoto1() { rgbFieldDiff.clear(); loadRGB(true); return true; } /* * Zweite Vergleichsfoto machen * * @param file Pfad zum Foto */ public boolean takePhoto2() { loadRGB(false); return true; } /* * Laed die RGB Werte eines Bildes in die ArrayListen r1,g1,b1 bzw. r2,g2,b2 * * @param file Pfad des Biles * * @param toggle true fuer das erste Foto, false fuer das zweite Foto */ private void loadRGB(boolean toggle) { ArrayList<Integer> r = new ArrayList<Integer>(); ArrayList<Integer> g = new ArrayList<Integer>(); ArrayList<Integer> b = new ArrayList<Integer>(); BufferedImage bu = grabber.getImage(); bu = ir.getRotatedImage(bu, angle); for (int y = offsetY1; y < offsetY2; y++) { for (int x = offsetX1; x < offsetX2; x++) { r.add((int) (((Math.pow(256, 3) + bu.getRGB(x, y)) / 65536))); // Umwandlung // der // ausgelesenen // Werte... g.add((int) (((Math.pow(256, 3) + bu.getRGB(x, y)) / 256) % 256)); // ...in // RGB // Werte b.add((int) ((Math.pow(256, 3) + bu.getRGB(x, y)) % 256)); } } if (toggle == true) { this.r1 = r; this.b1 = b; this.g1 = g; } else { this.r2 = r; this.b2 = b; this.g2 = g; } } public static void main(String[] args) { // ImageLoader erstellen ImageLoader im = new ImageLoader(); // Winkel um den Schachbrett gedreht werden soll eingeben // Bild wird in Fenster angezeigt im.setAngle(im.calcAngle()); // Offset bestimmen, also die groesse des Schachbretts // wird ueber Fenster manuell bestimmt im.calcOffset(); // erste Vergleichsfoto im.takePhoto1(); // nachfolgend nur fuer Tests (damit ich zeit habe // Figuren umzusetzen // System.out.println("Foto1 taken"); try { Thread.sleep(10000); } catch (Exception e) { e.printStackTrace(); } // 2te Vergleichsfoto nehmen im.takePhoto2(); // Hier werden die geaenderten Positionen geholt List<Integer> l = im.getChangedPositions(); // irrelevant // System.out.println("AUSGABE"); for (int i = 0; i < l.size(); i++) { // System.out.println(l.get(i)); // System.out.println(im.numberToRaster(l.get(i))); } im.compareFields(); /* * Aufruf erfolgt so: 1.) initialisieren; Imageloader, winkel setzen, * offset berechnen 2.) ueber takePhoto 1 und 2 Fotos aufnehmen * 3.)Positionen entnehmen * * Schritt 2.) und 3.) beliebig oft wiederholbar */ } }