Upgoat czy Downgoat?

309

Biorąc pod uwagę obraz kozy, twój program powinien najlepiej spróbować ustalić, czy koza jest do góry nogami, czy nie.

Przykłady

Są to przykłady danych wejściowych. Nie rzeczywiste dane wejściowe

Wejście:

Downgoat

Wynik: Downgoat

Spec

Twój program powinien mieć maksymalnie 30 000 bajtów

  • Dane wejściowe będą zawierać pełną kozę
  • Zdjęcie zawsze będzie zawierać kozę
  • Jeśli koza jest odwrócona, wyjdź Downgoat, w przeciwnym razieUpgoat

Wejście będzie jednak można wziąć obraz jako wejście (nazwa pliku, base64 obrazu itp.)

Nie należy polegać na nazwie obrazu lub innych metadanych zawierających „Upgoat” lub „Downgoat”, ponieważ nazwy plików GIST służą wyłącznie jako odniesienie.


Proszę nie kodować na stałe . To nudne, nie mogę tego całkowicie egzekwować, ale mogę ładnie zapytać.

Przypadki testowe

Gist ze zdjęciami . obrazy zaczynające się od downgoatmają Downgoatwyjście, a obrazy zaczynające się od upgoatmają Upgoatwyjście.

Druga partia przypadków testowych Pamiętaj, aby przetestować swoje zdjęcia na wszystkich przypadkach testowych. Te obrazy są jpgs. Rozmiary obrazów różnią się, ale nie tak bardzo.


Uwaga: Przed zaakceptowaniem odpowiedzi można dodać kilka przypadków testowych, aby uniknąć odpowiedzi na kod i sprawdzić ogólną wydajność programu.

Punkty bonusowe za poprawienie mojego awatara: P

Punktacja

Wynik to procent, który można obliczyć przez: (number_correct / total) * 100

Downgoat
źródło
1
Czy „dopasowanie” liczy się jako kodowanie na stałe?
Nick T
@NickT co rozumiesz przez „dopasowanie”?
Downgoat
@Downgoat zbliżające się parametry modelu (równania), który wyświetla, jeśli koza jest zwrócona w prawidłową stronę. Przez „ dopasowanie” rozumiem dopasowanie modelu do całego zestawu danych, w przeciwieństwie do niektórych zestawów treningowych.
Nick T
29
Jestem ciekawy, jak te rozwiązania poradzą sobie z dwoma kozami na jednym zdjęciu.
Daniel

Odpowiedzi:

293

Mathematica, 100%, 141 bajtów

f@x_:=Count[1>0]@Table[ImageInstanceQ[x,"caprine animal",RecognitionThreshold->i/100],{i,0,50}];If[f@#>f@ImageReflect@#,"Up","Down"]<>"goat"&

Cóż, to coś więcej niż oszustwo. Jest również niezwykle powolny, a także bardzo głupi. Funkcja z fgrubsza widzi, jak wysoko można ustawić próg rozpoznawania w jednym z wbudowanych komputerowych systemów wizyjnych Mathematiki, i nadal rozpoznaje obraz jako kozę.

Następnie sprawdzamy, czy obraz lub odwrócony obraz jest bardziej kozi. Działa tylko na Twoim zdjęciu profilowym, ponieważ remis jest zerwany na korzyść downgoat. Prawdopodobnie istnieje wiele sposobów, które można by ulepszyć, w tym pytanie, czy obraz przedstawia Bovids lub inne uogólnienia rodzaju bydła koziego.

Odpowiedz jako pisemne wyniki 100% dla pierwszego zestawu testowego i 94% dla drugiego zestawu testowego, ponieważ algorytm daje niejednoznaczny wynik dla kozy 1. Można to zwiększyć do 100% kosztem jeszcze dłuższego czasu obliczeniowego o testowanie większej liczby wartości RecognitionThreshold. Podnoszenie z 100do 1000sufficies; z jakiegoś powodu Mathematica uważa, że ​​to bardzo nieprzyjemny obraz! Wydaje się, że zmiana jednostki rozpoznającej z kozy na Kopytny Ssak również działa.

Nie golfowany:

goatness[image_] := Count[
                      Table[
                        ImageInstanceQ[
                          image, Entity["Concept", "CaprineAnimal::4p79r"],
                          RecognitionThreshold -> threshold
                        ],
                        {threshold, 0, 0.5, 0.01}
                      ],
                      True
                    ]

Function[{image},
  StringJoin[      
    If[goatness[image] > goatness[ImageReflect[image]],
      "Up",
      "Down"
    ],
    "goat"
  ]
]

Alternatywne rozwiązanie, 100% + bonus

g[t_][i_] := ImageInstanceQ[i, "caprine animal", RecognitionThreshold -> t]
f[i_, l_: 0, u_: 1] := Module[{m = (2 l + u)/3, r},
  r = g[m] /@ {i, ImageReflect@i};
  If[Equal @@ r,
   If[First@r, f[i, m, u], f[i, l, m]],
   If[First@r, "Up", "Down"] <> "goat"
   ]
  ]

Ten wykorzystuje tę samą strategię jak poprzednio, ale z wyszukiwaniem binarnym powyżej progu. W grę wchodzą dwie funkcje:

  • g[t]zwraca, czy jego argumentem jest obraz kozi z progiem t.
  • fprzyjmuje trzy parametry: obraz oraz górną i dolną granicę progu. Jest rekurencyjny; działa poprzez testowanie progu mmiędzy górnym a dolnym progiem (odchylonym w kierunku dolnego). Jeśli obraz i obraz odbity są zarówno kozie, jak i nie-kozie, eliminuje odpowiednio dolną lub górną część zakresu i wywołuje się ponownie. W przeciwnym razie, jeśli jeden obraz jest kozi, a drugi nie jest kozi, zwraca, Upgoatjeśli pierwszy obraz jest kozi, a w Downgoatprzeciwnym razie (jeśli drugi obraz odbity jest kozi).

Definicje funkcji zasługują na małe wyjaśnienie. Po pierwsze, aplikacja funkcji jest lewostronna. Oznacza to, że coś podobnego g[x][y]jest interpretowane jako (g[x])[y]; „wynik g[x]zastosowania do y”.

Po drugie, przypisanie w Mathematica jest w przybliżeniu równoznaczne ze zdefiniowaniem reguły zastępowania. Oznacza to, f[x_] := x^2że nie oznacza „zadeklaruj funkcję o nazwie fz parametrem, xktóry zwraca x^2;” jego znaczenie jest bliższe: „za każdym razem, gdy zobaczysz coś takiego f[ ... ], zadzwoń do środka xi zastąp to x^2”.

Zestawiając te dwa elementy, widzimy, że definicja gmówi Mathematica, aby zastąpić dowolne wyrażenie formularza (g[ ... ])[ ... ]prawą stroną zadania.

Kiedy Mathematica napotka wyrażenie g[m](w drugim wierszu f), zobaczy, że wyrażenie nie pasuje do żadnych znanych reguł i pozostawia je bez zmian. Następnie dopasowuje Mapoperator /@, którego argumentami są, g[m]i listę {i, ImageReflect@i}. ( /@to notacja infiksowa; to wyrażenie jest dokładnie równoważne z Map[g[m], { ... }].) MapZastępuje się przez zastosowanie pierwszego argumentu do każdego elementu jego drugiego argumentu, więc otrzymujemy {(g[m])[i], (g[m])[ ... ]}. Teraz Mathematica widzi, że każdy element pasuje do definicji gi zastępuje.

W ten sposób musimy gzachowywać się jak funkcja, która zwraca inną funkcję; to znaczy działa mniej więcej tak, jak napisaliśmy:

g[t_] := Function[{i}, ImageInstanceQ[i, "caprine animal", RecognitionThreshold -> t]]

(Z wyjątkiem tego przypadku g[t]samo ocenia na a Function, podczas gdy wcześniej g[t]samo w ogóle nie zostało przekształcone).

Ostatnią sztuczką, której używam, jest opcjonalny wzór. Wzorzec l_ : 0oznacza „dopasuj dowolne wyrażenie i udostępnij je jako l, lub nie dopasowuj niczego i 0udostępnij jako l”. Tak więc, jeśli wywołujesz f[i]z jednym argumentem (obrazem do przetestowania), to tak jakbyś zadzwonił f[i, 0, 1].

Oto uprząż testowa, której użyłem:

gist = Import["https://api.github.com/gists/3fb94bfaa7364ccdd8e2", "JSON"];
{names, urls} = Transpose[{"filename", "raw_url"} /. Last /@ ("files" /. gist)];
images = Import /@ urls;
result = f /@ images
Tally@MapThread[StringContainsQ[##, IgnoreCase -> True] &, {names, result}]
(* {{True, 18}} *)

user = "items" /.
           Import["https://api.stackexchange.com/2.2/users/40695?site=codegolf", "JSON"];
pic = Import[First["profile_image" /. user]];
name = First["display_name" /. user];
name == f@pic
(* True *)
Simmons
źródło
344
Mathematica ma wbudowane narzędzie do określania kóz. Nie wiem jak się z tym czuć.
Robert Fraser
119
Whaaat Oo, jest do tego wbudowane .... Wow ...
Downgoat
171
Żartujesz sobie ze mnie ...
corsiKa
27
+1 za to, że Mathematica może zobaczyć, który obraz jest „bardziej kozi”.
QBrute
9
To jest pozytywnie śmieszne. +1.
ApproachingDarknessFish
71

JavaScript, 93,9%

var solution = function(imageUrl, settings) {

  // Settings
  settings = settings || {};
  var colourDifferenceCutoff = settings.colourDifferenceCutoff || 0.1,
      startX = settings.startX || 55,
      startY = settings.startY || 53;

  // Draw the image to the canvas
  var canvas = document.createElement("canvas"),
      context = canvas.getContext("2d"),
      image = new Image();
  image.src = imageUrl;
  image.onload = function(e) {
    canvas.width = image.width;
    canvas.height = image.height;
    context.drawImage(image, 0, 0);

    // Gets the average colour of an area
    function getColour(x, y) {

      // Get the image data from the canvas
      var sizeX = image.width / 100,
          sizeY = image.height / 100,
          data = context.getImageData(
            x * sizeX | 0,
            y * sizeY | 0,
            sizeX | 0,
            sizeY | 0
          ).data;

      // Get the average of the pixel colours
      var average = [ 0, 0, 0 ],
          length = data.length / 4;
      for(var i = 0; i < length; i++) {
        average[0] += data[i * 4] / length;
        average[1] += data[i * 4 + 1] / length;
        average[2] += data[i * 4 + 2] / length;
      }
      return average;
    }

    // Gets the lightness of similar colours above or below the centre
    function getLightness(direction) {
      var centre = getColour(startX, startY),
          colours = [],
          increment = direction == "above" ? -1 : 1;
      for(var y = startY; y > 0 && y < 100; y += increment) {
        var colour = getColour(startX, y);

        // If the colour is sufficiently different
        if(
          (
            Math.abs(colour[0] - centre[0]) +
            Math.abs(colour[1] - centre[1]) +
            Math.abs(colour[2] - centre[2])
          ) / 256 / 3
          > colourDifferenceCutoff
        ) break;
        else colours.push(colour);
      }

      // Calculate the average lightness
      var lightness = 0;
      for(var i = 0; i < colours.length; i++) {
        lightness +=
          (colours[i][0] + colours[i][1] + colours[i][2])
          / 256 / 3 / colours.length;
      }

      /*
      console.log(
        "Direction:", direction,
        "Checked y = 50 to:", y,
        "Average lightness:", lightness
      );
      */
      return lightness;
    }

    // Compare the lightness above and below the starting point
    //console.log("Results for:", imageUrl);
    var above = getLightness("above"),
        below = getLightness("below"),
        result = above > below ? "Upgoat" : "Downgoat";
    console.log(result);
    return result;
  };
};
<div ondrop="event.preventDefault();r=new FileReader;r.onload=e=>{document.getElementById`G`.src=imageUrl=e.target.result;console.log=v=>document.getElementById`R`.textContent=v;solution(imageUrl);};r.readAsDataURL(event.dataTransfer.files[0]);" ondragover="event.preventDefault()" style="height:160px;border-radius:12px;border:2px dashed #999;font-family:Arial,sans-serif;padding:8px"><p style="font-style:italic;padding:0;margin:0">Drag & drop image <strong>file</strong> (not just link) to test here... (requires HTML5 browser)</p><image style="height:100px" id="G" /><pre id="R"></pre></div>

Wyjaśnienie

Prosta implementacja pomysłu @BlackCap polegającego na sprawdzeniu, skąd pochodzi światło.

Większość kóz znajduje się pośrodku swoich wizerunków, a ich brzuchy są zawsze ciemniejsze niż grzbiety z powodu światła słonecznego. Program rozpoczyna się na środku obrazu i zapisuje kolor. Następnie uzyskuje średnią jasność pikseli powyżej i poniżej środka aż do miejsca, w którym kolor różni się od koloru w środku (kiedy ciało kozy kończy się i zaczyna się tło). Która strona jest lżejsza, określa, czy jest to upgoat czy downgoat.

Nie powiodło się dla downgoat 9 i upgoats 7 i 9 w drugim przypadku testowym.

użytkownik 81655
źródło
4
Miły! Nie spodziewałem się, że 100% będzie tak łatwe. Dodałem drugą serię przypadków testowych. Czy możesz na tej podstawie zaktualizować swoją odpowiedź?
Downgoat
Oto alternatywny link czy to działa?
Downgoat
@Downgoat Yep. Wynik zaktualizowany.
user81655
Niestety nie działa po obróceniu obrazu o 180 ° i obróceniu go w pionie. zrzut ekranu
mr5
@ mr5 Interesujące ... Czy obraz na twoim zrzucie ekranu różni się nieco od downgoat 4? Istnieją również niewielkie różnice między przeglądarkami (a może systemami operacyjnymi?). Dzięki parametrom podanym w tej odpowiedzi uzyskałem te same wyniki zarówno dla Chrome, jak i Firefoksa (w systemie Windows).
user81655
63

Python, 100%, 225 bajtów

import requests

SEARCH = "http://www.bing.com/images/searchbyimage?FORM=IRSBIQ&cbir=sbi&imgurl="
THRESHOLD = 30
url = raw_input()
print "Upgoat" if requests.get(SEARCH + url).content.count('img') > THRESHOLD else "Downgoat"

Użyj kozła do wyszukiwania wstecznego. Jeśli strona zwraca satysfakcjonującą liczbę wyników, prawdopodobnie jest to koza w górę. To rozwiązanie prawdopodobnie nie będzie działać na ręcznie rysowane kozy lub jeśli Bing kiedykolwiek zostanie uszkodzony.

jasonshao
źródło
32
Nie jestem pewien, co sądzę o tej odpowiedzi. Jest ważny na granicy i prawie narusza tę lukę . Obecnie łamie jawną zasadę, że dane wejściowe to plik lub ścieżka lokalna, a nie adres URL. To ciekawa odpowiedź, ale biorąc pod uwagę, jak ważna jest granica, powiedziałbym, że jej konkurencyjność jest wątpliwa.
Downgoat
50
@Downgoat, więc obniżyłeś jego odpowiedź?
Ave
2
napraw to, przesyłając plik do imgur lub coś ^^ Również dlaczego na świecie miałbyś używać bing ???
Eumel
17
@Eumel Ponieważ Google sprawdza, czy User-Agent w żądaniu HTTP należy do rzeczywistej przeglądarki internetowej (lub czegoś, na co pozwalają), a nie do innej aplikacji lub skryptu. Bing tego nie sprawdza, są trochę zdesperowani otrzymywaniem przychodzących zapytań. Wydaje mi się, że User-Agent można sfałszować dodatkowym kodem i nie miałoby to znaczenia, ponieważ nie jest to golf kodowy.
JordiVilaplana
14
Ta standardowa luka polega na tym, że odpowiedzi na golfa kodu są mniejsze. To nie jest wyzwanie do gry w golfa, więc nie rozumiem, dlaczego miałaby obowiązywać luka
SztupY
58

Java, 93,9% 100%

Działa to poprzez określenie kontrastu wiersza w górnej i dolnej części obrazu. Zakładam, że kontrast w dolnej połowie obrazu jest większy z 2 powodów:

  • 4 nogi są w dolnej części
  • tło w górnej części będzie rozmyte, ponieważ zwykle jest to obszar poza polem ostrości

Określam kontrast dla każdego wiersza, obliczając różnicę wartości sąsiednich pikseli, kwadrat różnicę i sumując wszystkie kwadraty.

Aktualizacja

Niektóre obrazy z drugiej partii powodowały problemy z oryginalnym algorytmem.

upgoat3.jpg

Ten obraz używa przezroczystości, która była wcześniej ignorowana. Istnieje kilka możliwości rozwiązania tego problemu, ale po prostu wybrałem renderowanie wszystkich obrazów na czarnym tle o wymiarach 400 x 400. Ma to następujące zalety:

  • obsługuje obrazy z kanałem alfa
  • obsługuje obrazy indeksowane i w skali szarości
  • poprawia wydajność (nie trzeba przetwarzać tych obrazów 13MP)

downgoat8.jpg / upgoat8.jpg

Te obrazy mają przesadzone szczegóły w ciele kozy. Rozwiązaniem było rozmycie obrazu tylko w kierunku pionowym. Powodowało to jednak problemy z obrazami z pierwszej partii, które mają pionowe struktury w tle. Rozwiązaniem tutaj było po prostu policzenie różnic przekraczających określony próg i zignorowanie rzeczywistej wartości różnicy.

Krótko mówiąc, zaktualizowany algorytm szuka obszarów z wieloma różnicami w obrazach, które wyglądają następująco:

wprowadź opis zdjęcia tutaj

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class UpDownGoat {
    private static final int IMAGE_SIZE = 400;
    private static final int BLUR_SIZE = 50;

    private static BufferedImage blur(BufferedImage image) {
        BufferedImage result = new BufferedImage(image.getWidth(), image.getHeight() - BLUR_SIZE + 1,
                BufferedImage.TYPE_INT_RGB);
        for (int b = 0; b < image.getRaster().getNumBands(); ++b) {
            for (int x = 0; x < result.getWidth(); ++x) {
                for (int y = 0; y < result.getHeight(); ++y) {
                    int sum = 0;
                    for (int y1 = 0; y1 < BLUR_SIZE; ++y1) {
                        sum += image.getRaster().getSample(x, y + y1, b);
                    }
                    result.getRaster().setSample(x, y, b, sum / BLUR_SIZE);
                }
            }
        }
        return result;
    }

    private static long calcContrast(Raster raster, int y0, int y1) {
        long result = 0;
        for (int b = 0; b < raster.getNumBands(); ++b) {
            for (int y = y0; y < y1; ++y) {
                long prev = raster.getSample(0, y, b);
                for (int x = 1; x < raster.getWidth(); ++x) {
                    long current = raster.getSample(x, y, b);
                    result += Math.abs(current - prev) > 5 ? 1 : 0;
                    prev = current;
                }
            }
        }
        return result;
    }

    private static boolean isUp(File file) throws IOException {
        BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = image.createGraphics();
        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        graphics.drawImage(ImageIO.read(file), 0, 0, image.getWidth(), image.getHeight(), null);
        graphics.dispose();
        image = blur(image);
        int halfHeight = image.getHeight() / 2;
        return calcContrast(image.getRaster(), 0, halfHeight) < calcContrast(image.getRaster(),
                image.getHeight() - halfHeight, image.getHeight());
    }

    public static void main(String[] args) throws IOException {
        System.out.println(isUp(new File(args[0])) ? "Upgoat" : "Downgoat");
    }
}
Sleafar
źródło
Oto alternatywny link czy to działa?
Downgoat
@Downgoat Tak, działało. Zaktualizowałem wynik (bez uwzględnienia punktów bonusowych dla twojego awatara, który został poprawnie rozpoznany :).
Sleafar
38

Python 3, 91,6%

-edytowane w nowych przypadkach testowych

ustaw nazwę pliku na obraz kozy, który chcesz przetestować. Używa jądra, aby obraz był asymetryczny u góry / u dołu. Próbowałem operatora sobel, ale było lepiej.

from PIL import Image, ImageFilter
import statistics
k=(2,2,2,0,0,0,-2,-2,-2)
filename='0.png'
im=Image.open(filename)
im=im.filter(ImageFilter.Kernel((3,3),k,1,128))
A=list(im.resize((10,10),1).getdata())
im.close()
a0=[]
aa=0
for y in range(0,len(A)):
    y=A[y]
    a0.append(y[0]+y[1]+y[2])
aa=statistics.mean(a0)
if aa<383.6974:
    print('Upgoat')
else:
    print('Downgoat')
Magenta
źródło
3
+1 Dobra robota! Naprawdę powinienem wymyślić, jak zainstalować PIL na komputerze Mac ...
Downgoat
Dodałem drugą serię przypadków testowych. Czy możesz na tej podstawie zaktualizować swoją odpowiedź?
Downgoat
@Downgoat właśnie zrobił
Magenta
@Downgoatpip install Pillow
Assaf Lavie
16

OpenCV z transformacją Hougha, 100%

Moim pierwotnym pomysłem było wykrycie pionowych linii nóg kozła i określenie jego pozycji pionowej względem ciała i horyzontu.

Jak się okazuje, na wszystkich obrazach ziemia jest bardzo głośna, co powoduje wiele detekcji krawędzi Canny'ego i odpowiadających wykrytych linii z transformacji Hougha. Moją strategią było następnie ustalenie, czy linie poziome leżą w górnej czy dolnej połowie obrazu, co wystarczyło do rozwiązania problemu.

# Most of this code is from OpenCV examples
import cv2
import numpy as np

def is_upgoat(path):
    img = cv2.imread(path)
    height, width, channels = img.shape
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 100, 200, apertureSize=3)

    lines = cv2.HoughLines(edges, 1, np.pi/180, 200, None, 0, 0, np.pi/2-0.5, np.pi/2+0.5)
    rho_small = 0

    for line in lines:
        rho, theta = line[0]
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a*rho
        y0 = b*rho
        x1 = int(x0 + 5000*(-b))
        y1 = int(y0 + 5000*(a))
        x2 = int(x0 - 5000*(-b))
        y2 = int(y0 - 5000*(a))

        if rho/height < 1/2: rho_small += 1
        cv2.line(img,(x1,y1),(x2,y2),(0,0,255),1, cv2.LINE_AA)

    output_dir = "output/"
    img_name = path[:-4]
    cv2.imwrite(output_dir + img_name + "img.jpg", img)
    cv2.imwrite(output_dir + img_name + "edges.jpg", edges)

    return rho_small / len(lines) < 1/2


for i in range(1, 10):
    downgoat_path = "downgoat" + str(i) + ".jpg"
    print(downgoat_path, is_upgoat(downgoat_path))

for i in range(1, 10):
    upgoat_path = "upgoat" + str(i) + ".jpg"
    print(upgoat_path, is_upgoat(upgoat_path))

Oto cała funkcja bez wysyłania obrazów:

def is_upgoat(path):
    img = cv2.imread(path)
    height, width, channels = img.shape
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 100, 200, apertureSize=3)

    lines = cv2.HoughLines(edges, 1, np.pi/180, 200, None, 0, 0, np.pi/2-0.5, np.pi/2+0.5)
    rho_small = 0

    for line in lines:
        rho, theta = line[0]
        if rho/height < 1/2: rho_small += 1

    return rho_small / len(lines) < 1/2

Krawędzie Downgoat1:

Krawędzie Downgoat1

Downgoat1 linii:

Downgoat1 linii

Krawędzie i linie Upgoat2:

Krawędzie Upgoat2 Linie Upgoat2

Metoda działała nawet dobrze na szczególnie hałaśliwych obrazach. Oto krawędzie i linie downgoat3:

krawędzie downgoat3 downgoat3 linie


Uzupełnienie

Okazuje się, że mediana rozmycia i adaptacyjne progi Gaussa przed transformacją Hougha działają znacznie lepiej niż wykrywanie krawędzi Canny'ego, głównie dlatego, że mediana rozmycia jest dobra w hałaśliwych obszarach. Jednak problemy z moim pierwotnym podejściem od razu są jasne: na niektórych zdjęciach wykrywane są wyraźne linie tła, a także twarz kozy.

def is_upgoat2(path):
    img = cv2.imread(path)
    #height, width, channels = img.shape
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = cv2.medianBlur(gray, 19)
    thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                   cv2.THRESH_BINARY_INV, 11, 2)

    lines = cv2.HoughLinesP(thresh, 1, np.pi / 180, threshold=100,
                            minLineLength=50, maxLineGap=10)

    vert_y = []
    horiz_y = []
    for line in lines:
        x1, y1, x2, y2 = line[0]
        # Vertical lines
        if x1 == x2 or abs((y2-y1)/(x2-x1)) > 3:
            vert_y.append((y1+y2)/2)
            cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)

        # Horizontal lines
        if x1 != x2 and abs((y2-y1)/(x2-x1)) < 1/3:
            horiz_y.append((y1+y2)/2)
            cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)


    print(np.median(vert_y), np.median(horiz_y))

Oto downgoat8:

downgoat8 thresh 8 krawędzi

Kontury (nie pokazano kodu) dość dobrze wykrywają górną krawędź kozy (kręgosłupa), ale nie uzyskują całego kształtu.

kontury

Dalsze badania: OpenCV ma funkcję wykrywania obiektów opartą na haarze, która jest zwykle używana do rzeczy takich jak samochody i twarze, ale prawdopodobnie mogłaby również działać na kozy, biorąc pod uwagę ich charakterystyczny kształt.

Rozpoznawanie funkcji 2D wygląda obiecująco (dopasowywanie szablonów nie będzie działać z powodu skalowania i obrotu), ale jestem zbyt leniwy, by wymyślić OpenCV dla C ++.

qwr
źródło
10

Python 3, numpy, scikit, 100%

Ten kod uruchamia przeszkolony kozi klasyfikator obrazów dla pojedynczej nazwy pliku, wypisując „Upgoat” lub „Downgoat”. Sam kod to jedna linia python3, poprzedzona pojedynczym gigantycznym łańcuchem i linia importu. Olbrzymi ciąg jest tak naprawdę klasyfikatorem wyszkolonym przez kozy, który jest rozpakowywany w czasie wykonywania i otrzymuje obraz wejściowy do klasyfikacji.

Klasyfikator został stworzony przy użyciu systemu TPOT, autorstwa Randala Olsona i zespołu z University of Pennsylvania. TPOT pomaga ewoluować potoki uczenia maszynowego za pomocą programowania genetycznego. Zasadniczo używa sztucznego wyboru do wybierania różnych parametrów i rodzajów klasyfikacji, aby najlepiej działać z danymi wejściowymi, które mu podajesz, więc nie musisz wiele wiedzieć o uczeniu maszynowym, aby uzyskać całkiem dobrą konfigurację potoku. https://github.com/EpistasisLab/tpot . TPOT biegnie na górze scikit-learn, z INRIA et al, http://scikit-learn.org/stable/

Dałem TPOT około stu kozich zdjęć, które znalazłem w Internecie. Wybrałem te, które wyglądały stosunkowo podobnie do kóz w teście, tj. „W polu”, z boku, bez większego znaczenia na obrazie. Rezultatem tego procesu TPOT był w zasadzie obiekt ExtraTreesClassifier do nauki scikit. Ten klasyfikator obrazów, po wytrenowaniu (lub „dopasowaniu”) u moich kóz, został wytrawiony w ogromny sznurek. Łańcuch zawiera zatem nie tylko kod klasyfikacyjny, ale „odcisk” treningu wszystkich obrazów kozłów, na których był trenowany.

Lekko oszukiwałem podczas treningu, umieszczając obraz testowy „kozy stojącej na kłodzie” na obrazach treningowych, ale nadal działa całkiem dobrze na ogólnych obrazach kozy w polu. Wygląda na to, że istnieje kompromis - im dłużej pozwalam TPOT na działanie, tym lepszy jest klasyfikator. Jednak lepsze klasyfikatory również wydają się „większe” i ostatecznie przekraczają limit 30 000 bajtów podany przez @Downgoat w grze w golfa. Ten program w obecnej postaci ma obecnie około 27 KB. Pamiętaj, że „druga grupa” zdjęć testowych jest zepsuta, podobnie jak „łącze zapasowe”, więc nie jestem pewien, jak by to zrobiło. Gdyby miały zostać naprawione, prawdopodobnie zacznę od nowa, uruchomię ponownie TPOT i załaduję kilka nowych obrazów i zobaczę, czy mogę utworzyć nowy klasyfikator o wielkości poniżej 30 000 bajtów.

Dzięki

import marynata, bz2, base64, numpy, sys, skimage.transform, skimage.io
s = '' '
QlpoOTFBWSZTWbH8iTYAp4Z ///////////////////////////////////////////E4fAAPR
OCLRpIfAbvhgIAAAAJCgAG68fDuYNrAwQbsADQAKBIJBITroq0UyRVNGVqljJVSvgAAAAEgAAAAA
AAO7AABugXamjQYyCIABQ6O7LEQ2hRwOdKSFCVuGgF1jBthaAUAAEgKGLVAAAAAKKCVFBIFEFKVE
DQNAaNPUGjTJjU000G1PU0ZAaaGJoyDQaDaQxPRP0oZpNo9NRGaJtRoaYmmyammnqGAjTBNpG1Ga
mT01GRoemTFNnoRNPZCm09pmP9VVVBlIgAAAmgAAExNAaBo0A1MA0ZAADRMCZoAajBNMGjTSntAC
YJgGiYJjU0YNTTCYCYTANABMATKHInox / 7VSqoZMgGQaGRoADQaDTRo00YQaAGgAGQ000yGmjQNG
mQ00DRhNADCNAAGmTIZGgaNGmgMhoZNAZDIIp4EBNACNNEMmhUjTyJ6T0h6k9qnqbTU8NCnqDaTJ
oaabTUaNqG0jIyG0T0ID1BkaGj1ABoMgGgwIxNAGhkGmTCZA0Ghk0DCKUQECYBMmIEyJhlPTU8k9
TGmpP0NNU9tRomTaU9PSep6UeIGGSGJppsU9MTKbVPyFPZMU8ET9QmmnppiJp5TT0A1PNSeJknpH
qb1T1PFGnqeqeNTSemyaT / VUEKiJAQp4JtJ6iTZNQNMgaabUBtRtTymxDUaepp6mgemp6ag9I9Ey
aaM1NGQaaDQ9TNJ6hoDag00PUaA00PUB6gNGR6jagANHqDT1DTTI9J0gKvsPxi9r9nnM1WbDVUTR
nBgijNiWaqCjE4kzhxREVREZNmqgdLCqGJUXEg0K0IUotA0AJiVHEoUpQUI0CFDQUFAlI0FUjiQc
SjQA0DRTQI0jSJRTQLSrSjUQlFBRSBSNFBQAUo0lA0CYjECNjAjiEaVChEKBKUCgxAi4gVxAA4hQ
cQABGiIMAYEDMI90oGBe6yPBxuR2XhdxeZ1XL5AOe46 / lgb3BhDEJzJA3cev7vi53o25xTVTDRTL
S1W9eT6bsd7nyJqit + oxYIxWMYiKoqLGDERRMbmDk5 / f6rkb21xwxXFwxJYkqLFNSVjGDBjFGIiE
qiASEhEiLteHuvnMwqrqQgKhgZCZiYGIVCJEec2WyYMxkzjDibGEznHXdX7PtN84txMODGGnHFxY
GsFUZxYzGSoxZjnNNLO / 3fouWnGjjcYxnGCc4xVGycVFEZjDZsNpgzOM4UxIRQSGr + hhCVYTQEJB
MhACqGoDJDAR + C + VeBCIQEqhACCRSMAEqiA0MARCEZiZkNQiKEJACuhYhx6tAQhhet2tXbimsqnn
5qIY9C5JNHDqZp2rlRGwrWGuGgdu4FIYehsHhUKrgtTZWLIJqoOGsaUi5c7iYp2n + 46rbNtk8pSy
TJoqTh822poWQW92oaGuNk4 + Qil6VnzEKp6Lla + yUQqzH9N4p / vcI1WYVfBWLk53uwVcjn / iaf1x
kZJrY15LvF3c6bDSd7rtIF / CIeJ5ySSPDS8WpbhSth1jnyu1DFRb7ulLM6NlFMEVOCorVWdxjepR
5Nc0vgBvyASUIIJt9qydSewF7mdm76qnXx7NXCsl8ZDDG2 / 7KhXbsv3S1dTtXOitVYaUPrsnj + nG
R1MPnB8p7Hvdwe4eXxf1Bf39iVuyg9r9aweH4Ht / NfXOQ4IJ + q9UqxkeHy / Br1ixpI39nqf5 / 4gm
+ LgfXIgl7f372D + vf7 / 5D + t8jLCs + H23tsPj / lnZBkV + Xn / mfuvf + 2anyF + G + bGUypcqKqpb7iCo
QlBCSaYTfNYNeoXO19viV + uYu6lckm6OXj9Tp9QzdR204Lp87r88k9ULU01rhNPleSE5XK01Nht2
wB94gHbgH5aAB / 4hTt + y3OP41ivChK2SdsxThs4cw8p2uVsN5FTvdbYyDqkHKOdv6MDXJtk + fP9U
5DFrCIhv7UQqmETgJWZWhQhDBUKlJVKRuLBari0uxZtg9q6L3K42KgbA1aXeD3ypsAhWxqK9TK59
zuFDq1sYAWeBrNuydhlVPhwDoa7rs0xZkRXtSwuyYXtqIGsoWv3eglDKBjICrev + t / pew8 // j513
S4f9JIPxCWiAoDeb + iULXivpuL37uuEfiPr764B8OuKs1SrGVPUwelyHbu0yufCuMGLcP / 3fWryq
1UsZhJYJVQkrsEZBqJpkqWQiaYqbW9MsHsp75bTgxTNiy1cdasS2yU3GLG1jf1ajXwKd + 5HugAoU
tkoFOFTCSlQpUQxsyVjWZPGsCg9gt9j818V6Kvl7v5rK1tfoqfGfF1VAAENVQVVB + TUgAKqgAArd
D3XFc7OPq9D / bjG5yjUJeo + UtdmF4WweIIIipSUqVK2ISSVr93 + lkXLVyElqLZPL12cp3sc1CkPL
5IwHHuctF9lda56rrWDJy / ueRIKFF / fVB + EAAlCWZzg3ywLIOUexFPhVz68zMJ9jK2cpO2Kkma3p
StTr71R0nR / Gqfiqg8EojIZ3LNE7UrqlPVIysrlogNqiJzimFb6yLlGnVjHz2EdpNV6XZ8iv7IdT
nN0ut93cJpaqV0cEixL2TzSPqmoXvqB6IKDm + qmocLKnh2CWwsyqsMHtlV2 + rqNzX3nVoN0Cg6vL
U2OQyZ + xMs / gMc8yPKp5AIPqjjxNohmUc6ulA8IbleVQ2twH / Qc + H3QukwweIdinUphR6cPtB8oN
K8g / jbgfO3A9NhBQDKIg5IFDqBF2Yg / kQT0lA9NUPUVfVEfWEfpiJ + hQ2IB0oDtETMCZdHXCfrQN
qrthLhD0RNcJ6Y9EulXUgXS + u3LqAPVXav7EuHVO3DzA3D5IeUZxJ4DyIIPZ5HqdwIIAjH3M3O7T
zfUe5873xTd7r3pwJvknerhHzvPn6vzoOpfBAxna5nUVkZ3qsbqsQFQxLQ0IOKkjliCAI5znlbm5
ub29vY6oAAAAAAAAAAAAACqqqgAAAAAAAAAAAKqqqtGpqaM + fXWvZtXPp19ObTpWRyVSVzSaTnSZ
ISFlhCDkJ0WkkILE5KpK5pNJzpMkIm96uSToJKUXRg975M0XKsINJzoLBlWMGQ5RR0nKkFlFyRjJ
ISi6MHvfJmi5VhBpOdBYMqxgyHKKOk5QEVdE0FESD4xMmcIxOCC5hcb0F7mAXSUBk6EEkTEcoC8E
QpGkCSBCaqzovVoRckouU1WMZM95vNpRIYxVQaAgxG50kKDifCRxkiRWKxU3szGLmZHqkShKT2Fo
SIIujEYQg54EMjYyhJ5RKKM2hqbDJmxkz60YxYmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVVVVVVVVVVVVVVVVVVVVYygAwAAAA
AAAAAAAAADGQAFVVVYAAAAAAAAAAABVVVQAAAAAAAAAAAAAGMYxjGMYxjGAAAAAAAAAAAAKqqqxl
yZAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVVVVVVVVVVVVVVVV
VAAAAAAAAAAAAVVVVaNXYZ9XJsNOhzoPjATZ7gjJHGZqZvMIwUCNiA3ON7mg8lirSKUQUpMTI0ZQ
NxorCSQKMFRGdFjaEZPhGEoxlKUgAAAAAAAAAAAAACqqqgAAAAAAAAAAAAAMjNnzZtbNq6 + jLp12r
r2pm17IyPWUke6TzZ7lg4jfIoCcieoRCAEiwesXIqSVnqmaamnS1s + tra2wz6NGfV0Yy5cmXV058
2tp06cYYAAAAAAAAAAAAAAAAAAAAAAxjGMYxjGMYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqqqqq
qqqqqqqqqsZcgAMAAAAAAAAAAAAAAAAAAAAAAAAAAqqqqqqqqqqqAAAAAAAAAAAAAAxkyZcmTL5X
UABaQRUCkFAEpBFgkAQi / yAIiyQorG8kZN9kMZMAVA1v18dKaO4pB0771qIqBiQVUIgVU / 3hAHaM
ggEApEkkXUCueVjqIqwv0JBQVdUQaqXTh01SUQosK + NiU2qmwMHABALMSQ + BUXW + 7Xr09DlfqrdX
/ 939Xyc9YbJ / GjfTHd3mflYe8dzKSXup + r / HbwMZ8arVvHPdveWSm6FplrPLVmr2S843G4vE5O65
PJ4bC8DU5Luf + 6ve / Xp0 / g6un0Hj / Ty0fn2O99Haarbd32V / Dny / Qc5sNf / zt7 + dwOVu9 / 5Os2eg
0vXc5p9t4dmD1nVd / lairac3oOe0vZ9V + fSdb + LsOvxsPp9V1Gn8DR6 / Y / 7 / prP + 7La38WU / 06fv
fB8TVaynfbXa6qUu6xdNqP7 + FLY62em1Wnydnr9r3vjbDxtXkWeFrtT / nW63X6zw / E12s1 + Vsrk /
H2 + P5HkY97JyLvk5F6ud9cu5OTj3NtSvbUrsvL1VdclKMCEI / kz + htbPr46uEYwjGMeEz24bnash
zXEx3HMzQQZAQYBB9WpqbuxABZyiDy5zuTwGgskeZ3vWc + ABzZFn1QCDPvZ0J96TVM1ENETFMVSQ
UxNFVVRK + 690eoABfv372329iVOAmoTnKmXpwCVlmZmoABwwAAAORAAzRmgM6AZ85kAc2A6TpQ0Q
AAAClc5KO1AHe90CScngj / ABrQBsQABkXAGUWSrUqVKl9 / KLRiBAYDByIgJv / fkVEE72ARUN6QQT
av4jrx7Z + ANFQeu4oaOQ2IIM36MBCY6 + yU8Yn97qjqIuOZ + 9iKAou4t0QKchQhdMMYRhCMLUcv + i
EQABvlVN9lZZYknOaiSc9 / oX6WU4KgZgAAAAAAAA + kDkWdAOdAAwgB98aQAAB + 0dmnKykqA7gA7
w7yc1kqA8EDXNWbB4oANoeQLuMAF9XIowfMQzlqMIWrUYxtWoQjCNq1CEIxtWoQEKRQoVCgAKBEp
QKBWke4kA9PC9hAIHEkBD / IUkD2TCAb / pcCjmkJd2AO / lFXsIR35QfWwi + hkF9fKi9rCIie4gRfE
ym3CH3EHl4A9hKhxJ / BgXBwisStCQLDeYA3MB6cvZLeLKyyysFJWKUJMzOQHtgAA4gA + UAHIB9EA
M3ywBy + eAAwh0AA6MAAaU6tpQkUTAAArsropQAAA14AAMYAcbi0uNS49KlS + / Qygn9okGIUPw6YE
HpA8SfNPy / H4T7sxEA7lBBAPSIIKtIVMa6caqJDD5OfnbqJb71tVM6zwsIQAUgIwjvsAywAMqeVX
ve / 3pznOycydJCjgZyTUAAAAA + gD6gDlDlA5YDPAHOgAYTQDog6M0QBpANIB1z9llcp0nR2juO5A
AqropQAANca4P / DDADGACtWDBhnIQjyV3R / 66ny2rhCMIQh7rcOMh + Hynd5yMPK5XGdbifY0PcR3
GEI6fh9yjloxhCFqEIQhDeoQAD0YJX8q / evb4nOY3dZSk5qMzSmYnOgAADh3CgOJAA + eAcnyYAA5
gAz4wgAHSgNH1BogAFlcp9lMHagAWJaidO + AAAAAAMAMACACGBCHpIWoQhpIRhDP6HjctrOQ4o6Lzb
P2RpiYdZjQD626Ff / PHsBn37P5PufLqbcUiBgCMIb3CAG6gC / VVOq9esSkUoSpIcDKyb3GZoAAAA
cYA4sA44MAAMAGdObAwQYQAH4OnAA6sdWDsHX35Uk7GgAAJ0lJRqQAAAABdumMZDbAKxRgxhnIQj
nd0ydDouH8vuGWtQhGEI6jwOK7n50c9L + 2b4 / dPObGELUIbrGEY2oxhCEN5iPNAD1QXr053vTWWJ
SoUskoK67KSAcGOCe + AA + IAAAAPrAADmgAMIB + M6I6YAAdUAdZRKnYzoDthpgFKWUkoAAAAABcu4
wAFaQwOWhCEYQjajGHm / S8dGMMH4cYwjEqlQCFz / 4edmzVADd7iqXwZpGvJyp7vSne5fDfw4NF8z
DLQjGELUIwjHeYwAADb0pelJOe7ybtQr3idApNwGYAAAAAfHAAAABmwcwcyHMgMIHPujB + UAAaUA
TlNKgAfy0wEpf1SpQAAB4oABjDGAVF9WKMCHFWoRhuXv7UIxhCEPHtRhCEYww2BF2WXkBAfUgEZs
IrLoyRRB3bAo6GrNSCDMZkPwHld4ZXqqfWZHDB1X8PQ4ftgUL2AQAJykYAAZYWWTy9 / fJSpOc92m
KTpve9Ue0nOlFAAOGAcJxYAA4180ADNgcqAc0ADnsIAB0wAdSAAT / dZOUw7XtjtwacUknKYAAANg
AAMYAOLS43x0qVKl2ZnoEoqc2QDW8f + sTtv / A1AIMUSdJOliTgaTCVQyOZxmCx6B7fdTji1wIOcL
QRhAMA0qVKupUqVOAX / V36qqqUsnPelCuzfqTy4lKlJ0ODAAAAAAAAfXA5MAwQAMJz4HQh0gAAAC
yVkqKABpgJqamlGpAADXA8YAAxjyQF9WKMCDkBWBo / f3nATMdNqPaFDUAg3zQSIg + lM20pcyohl9
R0H1pgoVnawZP7O20Wvxf2LML7Co6hNxCoxEEREIRjG1ahHfoQAUpSlKZErMmyuuVlcrLLK8zfvV
11VZV / 2d + / Vl6qqqvd5iqqqqqrhaqqqqqqqqqqqqqqqquIqqqqqqqqyePyc1k5OTkfLyMjIyMfNY
/ KY + Pj / au3bt27dzdy5cuXLlzBuXLly5z9y5cuXLmguXLly5tOh2nRbTabLF / Nh7PF6XF0eLi4uL
i4mJiYmJiYlvY4eHsuuw7du3bt27du3bt2 + 3t27du3b4HA4HA4HA4HAu9rd3d3tLu7u7u7u7v2N7
7G93u93v1 + rB5b7eYRBmhGtRBsKy / GX7nOB1 / E3 / qXrb / 3FEEsgr3NOugQYgYIUoKUpDoMnMf9JD
lIjzOcRB8V0nUZ / J2IjPhEeewNb0JbaLYXfo9aPzTK2tiN5ALOEUiEbNlrUDRlB1MS + eEEA + BDJZ
+ PbCQi4er6zV6K2iiCwOAQHKAQEIQIgsK8HQmk + 3XIg4AbHDyyj2C7CQc / V3ix6TAb7qDRb / pOt1
ebs0bSBiBYgWQEG0bTBQpV + hi7qnVA6OEHS20 / p / 23o0fEmnxQ1qhXQkQrW1g1sHjQFrLvb + iAWn
lK9nsvYQQqUWDrj30rLLFl85s4pl / CToEB2gAIQS7ZrwaSB08E27237snaWSIO7jXauo4q + b4OeE
KpUKxq14UISbjbs2RBAPUU1ezYCbpVcJlh3K9CL7 + BIebaJl1OUvpTI + aCSVpzU + Il8r4GQDpJoG
gGgR2en5fPQIPnu4x7XtkzByrR1QKTpsdMNCQYt0AaIlc2aEEAs + qCTtZfBwYICmu4fpDGr2Hjae
psCC2AKD6apD39bTAGwy + BZPQCAWOGWUaOFSbfCbMAyqoOAQQ6SDoATyAMWOZrG0hbHBBALXsyTC
ZJA53b9jyNZQ5HHH2ZGk / 13f / C + I / ZDte08YgB0kqUgG7pXT1ABAMX41Lbt4d1gi3BVNUKwAEJgH
cMJzQVfUq / x8 + DBFgv12VXO1Fpoq5i7xXVgVAAuQC4o11H6opZ07GzB1kExvmeWckaqbkCRU1iJf
Z9gDVniZ8XUg + zZ2lCTCU5qcdLqgrZ84SqB + QwNCdQVw8ADECL + / t8zQ5LrNT52orqvD4m6lPzd /
YyoxYg4ob1W / JpNtsPQ0XLzgZ0JpghiJlAIcxjlbbdBBrtkMjU5 + BG6 / xqfFgvYmQAvbmxoIE / cd
BsOq6 / jpUl4ocf6meW7vnUb4YhSRX4MLMYHhJd9wgEGFLb6HBn4AED58IDj9n4vX7m2oFVibn7IE
/ RTRnVikkSXY2vf / PBBqNDu / IurbDfSqtJv8Jlu9zNdPEr / OYWQgrECDIzfa7rEYsmcgAJgIADr6
a0BBzs9qX3KO1uM9LgaY4 ++ vIxTUXMFjPWNf / n38HGXqT338kv9mub5gWYIhjiEEDd9VS / QgBAP +
UaOqjT0PBqd3W1BPQqlPNiXksoF4rxC9wqpwa6mmggb53q4Krla2yg58EZ5RfgS1wDIgOADyo6q3
fostZ4HbsAAgHi0VzeKH / GVqr7zs36oA4UJLASwUkjJSX9EH0vMrq4AQdzftzQzEGxjeY48XN7Ty
9eIT2vRrwdhb1lbJljEebtaNNsuay9FQ0NROIeNOzwh5q6ryfpvXeFFDpIApEPrRApghYIiEFM32
n1P8enSqlVMLPwez / + 7nkJ4qn04P7wd / 3UgIYz4JAQ9LZyKDz2ckVXrtJc3PJ23yu2 / pRB / VFKF /
PAnQeXIIa + BU2EddC7OBF4kAOGhK3F5nm8SACAVVZvxO7 + WQArXQrfXwkJA1bgDqrQsJlycegjze
PWc8RzzpycDyyCAe9lBwpbONqF3TcfVbssZBWPyz8cvfwXwKSAUhiQm7lasRaEEUiBQv4CRCK4r7
0 / ZmwmS5Ks77D9zvJPwJPrQK5P7QZpAF4c2zheVZwLVWQE4ke22p / W0HA3mMM + PEpCsluAVqaYux
hkHopROiQKJgKQH6Q5nUHhx7f4fynJHZ7fv + NYbllgCn / cNUv7bFd8 + qFgeAvwB ++ ga / JVNTGN53
IB4WRpQQoiVv4Q60XPHXjmIfMQdt67x / xno + j3jw8oPPoFGJxUoBg3qTG2R3KnRoEz3EJBVwrSlW
MSllN0Ngn4X88TZSSn4yioJoJgwC9nXql7l68n50Vw3ZeHSYAIB6GcHhVyOzlclfgFIXgBwAUD0Y
qE + y4AQ98kOYiVVDEicHJ7vf + f / 6Pgus8Aem43Udv3PNO76n0KYLyZ307TUBna8nIWynQx2oTMU2
dsCW8ItuBERCQQo2 + uuJ1R6nNjqxu / 4477i1foYXNYL16lbIPJgNFAcpJfRZqXv1an470C2sEQpa
qB78062iGy5rWmC7XNTOQxcOtlMlzPAzFbXONFWMAeiwgicygj / F8w / kEh / BCEBCCQe46qXWreFI
F9y41ErQyV + zGVIHdiQ8IdgeFAA0ENQYGGVvxJAfiSfaWnSggzfIveHGXibYPW7g3HaVONwuUpvX
+ TONfVrNC0QkhtuZpcGy9yii1YMQLNs9b6FS / J6TlPa2Vz8HueJYtMxYgxlY / Mxw9k5IIDkEFxsu
5pMlm13gS5cnrZoLfi4MJDy7AhQA7ANx / 1w4ckJIIn1dz86i4qNLZVG313vETqtgCatnCWfqa3 / V
aiFaoGPVKE5fBeh5cXum + GP0Xfqs7d84PpzfD + lVlIIQQECDekA + 1 + DmPqshl7IBv4LPAN9U4Hv7
kyOuussZHkeXSmLJbEG8AN4AdTJ8SHas8Lh2FO7IK8BfiEAB4gvFuCYAAMyCKwAWXxhlysSbKCqO
X5oEwpblsAcWnxmbYkhsCF0JLXTv6LC + L1sfO1IamFQALWhg0BKtHIG / pvBjlrCol / RGt72YlSDM
KgEqBPTxHR / 7gUAYIwM0ur / 11xYqAPq2AWj9vikGf8kgnEZfBftker7vGEYN54bYwDwMwPr / tvqe
78KJRD / L9r / 4vUG + s7zBC + skgZJj9JWq / MFXMd + t6sAYegSGJ / eoe4SFUEahIxBDBTE1BCJREQVU
U0UjMRUVIxMyITQjQkMTAyRFUQSoRUTVDUSECFCWoyBFIyERREIlav87qAiIuAhiVRkggn2JmQqC
mggioJIIKaihqQIgCBoq1EiKJoimJCBiaiKiImZGoCAlsO4Q4pEERVJBBCI / bsYKjMokiGDVMgMx
EqsbGCiCqZqBiZMpmSEjFX9 + Yie5UICUkMTNDEEUEGpUQkQkU0QCIiQqs6pvC42JIkSiuAMLIh7h
cZKxi5zGRmbjBlfkNThAh3PMLQJO0kJFVCY1ZjISNVMFUb45gBSU0FGZ7GfzasbgNWogMLEOxBiA
3rG1wILGbnCpq4xVjARnwHiDkCwWAfxaEYGKrctSJxcgkTHw352YiOM7UEkEEZEV5xcYEqgL4vLJ
lQfG5rWIQgJDVv4WZ0YH / UOBKMg1ICQJ0ScqApiagQi9UNwQOy2OOgHJgSAiipcTFMahJWqVEPAy
OgyCCIMwImrAQMwd8AsnblR6VGR1PQzA1mxoQ1UpndpufbhB + JREmjK + ZhXCwnQCQUzyo + ux9AvZ
UqRpnWxAQiNeRA4T0JrBB3hgs0JhpI3389AitYytiEql9bdeYIA32QwUAiJqUTuVAMSFRW5ArSph
X2wycKGIQL + 9E0FImZkAju3G9BgTj58nLbRhhLLaavCpqgl1lhwYHDMQ6IdqAJEDXb2mC1DAiQwu
QgG3tDdTjjq2TxeQQ497iMECvvmdg9JZvQQiuupKGgwcAkAiQhiRAICJkBCAliRoQXZi + 4bX6Qqf
54BVUVW3svV1AAGoDVQahr75ooWMQGImINv4HV + WYAztv1YFAagjjqoR7LekYVBEc / tyMXCATHiN
QB24EH3ogGK4HUFw1QCh9l + zAG4DhDKul9eOWUwAFzoYVdFIAuEKpeV2N0bXVBO0 / pp8g6 + hDST9
JX3d6Pdlm89PwsACoAq / D8FdDO3hpyAAAKkpodVQCtghHXJ2RErK5wbs9XElajJiizuE85V + ZXLd
Pyvy8xJOupddZ5jUTHTGEXWTNrTVo1k / u32XuDO8fpJRFzX2UKHpyclub3QmrRtnLDzSjvgEvvB7
q4UBLarBSmLSu2vTbrO + ZSbdTc6xrbEWwt8 / 5hTso2Ra / 0agqAKv3nwdsPoXaRpJHO045EGgd72y
WrVszfzjOcVKkDCwEOIem7nv2gYPreIIBQhFovlA5DCNUYscEiTK / 4V0YQc4zKxElKSGrEyJBRhK
PAJTLfnOXThkGrlpBRKpW / L4WpuVL56myXL4dfGxr + vy6pUp5lp8s1 / 1ouKjtqrb + 04ioBfm1QTY
SIn9ANIpl1WXToEahjlGBu / sZIfKlLC8pB0gZbEbvzUFDMBqCIh / tfpxRwgnx1 / 0uHx7RPieotiQ
BYWISqFAQhLiZnVVycI8REQ0Hj7lTBFPuQjs + e96w9Ab5IiX38YHg / 3D50OJjzJLSQ7bunfZDrrG
EZn35Y4fYQOEYd1c5j2D4nxPMEKJfASSWsDZqBSRwqEYNCt + 6A3vDCJarMOHPPGt513RieQyBFjJ
Fu8a7CsC / pILluBQEK4XWubGgI + Wdp5FnlHAGiMrrzB4ZyE2tsGErymPlkGDldc1J56XsL3uXUGN
4DjdYteEsK6aeWGMHQvhmLq81RyULQkPDuY7gaQmBYkohgb3TU8TrR5gannWpg + WTydjeeD3uUNA
2z63a3xxMLTHLNaIGAYkiPGHTIkiuaTyjnXCZcOvY7rJsRK / kyXWF1YxE63khaonFdmIOIZCBo1S
8dCHtB / bqoGF12thHvgxOedj4ypXY29TNWidorby8bsa3O2fAMszfcuZdsYrMudt7PnuznKXFGfC
bs9lNAERIRIXFE0ZCnSD3VOodpzSGr3hO1zz2dgHNH8x1rpXbTOd4vwiltm22MIboTvvrrnaMgO2
aUPDivhZZyHjo1YkPjedqKEBPwIL1YXRJUN4K3AIa7Q1hIQhJ8H0mrnO751mGYvCvCzd2vFl2Kdz
OdQLw7uFY8JdlkTe6SrYxCCC0N + RR6sLKYFQXlbMGu3PePLKtVuWMIndDDyJhyYbAjMxJPCltbXQ
dCxqKt047xOWRCvM3MGssh3L0aVfDoQa9SUWfXJ2 + tB1ijxCMXFzN68Awy42A7yi6e / I33xXYdoT
Cum8d6bbsI9eT8sLTdZwmoT4dvbzhMccnztjnbArbS28KEPdhw2LxfhaIiQwEdrrGa23skoq + b6x
yQCRmx8d4NB4u7B3d + 1Ad2dt4TgUDHclmXZmD548bQgbpOTc + h89SFWFt + / dEzQZAb3K6b4FHr0P
SvWlxxUdgdRuFlIZb511xtsmHbaRIXHDLYa6LDjd2dd + XDnae3fkT + RhowXYlgvFLlArArdxprHB
2ei2bilLTbbjnrpPpjG4u87QzjfHDG2PQ4c + UoQoPQi1GAhYtCooqWg + D6NHfW98ZvmkXajLdOVL
OaV6zv3Yd + UOljp7RCtbu + lqpqTkvZOe8quNevfvRQr28tcNtN3djfIsNOr9N9rZwMCI4AwwZY3r
Q91OyY6Q0jpdcnJHNGg3wySm / Lpkcl48Uvzz23RAN6SUkZt / CCivGGMsQy3pe7grdOzhlr06hTIL
77MKXMyjnhJCpHpf0w1vhmIcRDqWPZgZpHY8hztyj35vV2lIQA6cuV1fHcdfO7ud3c9m8Oeuwhll
iFMK92ZjaMsN3TVq / G + zXvyhy3JyyyfQuVu2Y5gelrdnZ3T7Z + Pbj02HeA8dy6lvQXj1319C0s0G
zbp26Wd81DXlZ5Y7dl5Wk / p0xw5deRdeuXVvBQ7bKWDTinWO ++ za9Dl1UuJXX + V6 + RKp7Bfv58pv
7szEu293TG0Lru9g0jod / fLWW1A403 + VuWVfHw0vs6FXdDJewpvINawyIumNm3b3ZO6eWG0xo1XE
cJUttx1xnV07u0516EASDuy4aEZdfJsqePd437m32UG / y7e6mW7yV7hDndG5PDC8e3m2GEOImshk
XECUiDG7xwR1pC / ciBt3gQPtrMmIQ7xRKGCJWOC3YQCrGON3BFqQEAicIEIlzgdVVQBVrmgd4xAR
AOpkqZKQphqvHn5Bu2ivV6CMZaMmkoGXCUFcRDC3sK5VM2QUZLe5VxQLXLXTu5W2B4Bj4XePi01m
5xwxtdx4U8nrloM8OnjTyV9T35WDaCCGu7jUmYhk5SZCcUDIuN17rXB + c + VqCqqoB93 / 0 + AXy4cx
I4e + q + Rd + K30f + 95q4G + nT4Rv2MSf5wwSskQvMBCgTSN4jJ80RzxSTKq1NU5AFVpsg4AW / CmH1Re
pH1Pn + flms88wr + lKrN + 5N8RQXZfBc / TxbCjqbDwu3S2f3kk7C12cg / Cep4AOctOLvDbOWWGeMx7
LoaV2y1gWvwu63hPC6q4moW6XZ1nyryWuB7wZLgy3EDoXaaJYHOuxlwvcm6utwbo9q8DXKOG22mG
T09MXGGAxGkGt5ZzkRPtZTqhMYNi6l9j5rW / ohKV1dU3mteKvfSyL3uC8mihsFDw1jCt9bHS5ZQa
VIM05IlNE4ts2rGNhrpaYNzWnPme784eXaqimogkoiA + yxCQQwwGC / N32IVAM87DPdj + NQHqb3XH
MW + GtR3ZimnCBDwLaQX93rD5D6Pw4spVuZ9JRMjI6vQH0SE0jBgdWEHClL7KNYVAQYm0XXUBozO4
mthCt0gUb7K4vrlUpkjr / isfW5TseFDnDKusXXndg6ulr2yuvtJ5MkBrxMKWmN1uRWDgqnWs8mxM
67xWEqQmWSTuD5b / OH8o / eff + 7T / 39M37r + R5ZYZ5es29pvL0vZ7ezGAxGQze04l6pvaAIfElD1j
uyCH7BJW + ld3zg5AvvlZ4z + D9K64KWyiVUyrs0xR3pejvdT1tL7vah6PmZNWILx3wpnT2J2 + w9o9
9fCuoRHQO0QLlG5 / peu1W1 + 3aXxkPXmHfam6KyERpdVrPu5pnGHOVOc3WcQgcPaNzxtyaPPLKTht
7DHvt9q0ekL9 + WEluHldE34YQEFrKwd2PY1dpTRXuZAWFY9L8MOodeuGD91x19mmWSdmA8DDQYZZ
5SVe / U4Qz7cGvBwR8Onst + nLN9d2yVrySLwjTfAE4XUSWoakBBkcOFRabcn6x4dkVtBu09nsG / fo
wdk6w5YLFCjQzPZd + qZ4boVhDVya8A7t177LqyDwxCN9cd / dHbnJ2kHEG98rUjEIxg / tEQELsG7s
tqXWlZrCZSxIFvdDZLtMjK6ImxEd4V5IVb9VfKb8uV7WcqBdXMRWI5b3cd1RMN + AaYsFEbMJQzzW
0XjyZ4ZZynCyOGN3s / a + Ol1WQ7nVrJenDHnqQW5ZGFw0AZC6unfWLmJswO21wZFFdXzE9drW2XeL
+ 3x9rupyyfQLsDCg4vv7e7Ddm + jm4Yr3Z4xjwXE3jxBxEOqB6X0HU9SRUtBRRLHUtShCEBAQgIV2
HnL8NMveS9WDDgYzXXahUuBzXMAgkaFrxPU7dAIQCKmanmTNUEd + POeU5vi9BQ8F8dwczygPKPI3
k97ou + 81vojxP1F0z14zxt15v0sO7fY7PDVVUDAwMDAwMDAwMDAwMDAykpl56wiDx + X7nyOu / w87
VVRUM1U1BcIYqqpYjnFAdIO8 ++ 9Z + 18123c9z4ncBB5KCebkR / F9t93ke68F4N5YPxPrNOXzAgqK
IkgOyEIQgoQ4 + Ecqf7fxiQMzG4Dzq + Tc15Pv8WfiwVYdh2AY / Eu3ZEEQREMFlDDBEEctvdqXny + h
7vHA3fh4vt8xfP8ePyd5LynAFJ + 3Ptf8e9 + S53hiKGmeFogi4N7lelx3fFP1fQXfeclgOhp + L3qf
LPR3UIgwiDUtmDYZbbuXRBlskgmkgpuAigqeg4x79wc76P5f1Hq / bPZHv5zz9fu + 0yY8N0fTgekP
CPCnCegGfRS + NOdThqp4QiqIh5MkgA2srhW8DlKNBIAOIHLdehZ1rhPaf86uqfIIdkjao ++ JGo9T
w86Q0VNwuIOHhUB8Ry / M9Z + Aes / j1 + UjkpzxyapKT / XCGk + p38OyE46gPHBKFWigCuPMZT2NyOQo
D0g + i0R + w6YMHnJez6v2mTvUkcfAHuwFa1AQEhvRgwP0cDQZiTyfuZR0ZzJazX8HT + o + d2HEQEHl
dD8wgg + V8C8JhMUV5CznBU0kSTFERRQVZcOJiimqZrOcll0AQfTfyZ77AIPSgg752NKA73Ve41QQ
c7pv0hiRG0RBwq9jnAxxrY9SzghQwKEKGOIiDhEcojKLDJUWG9PbyPgpTThagHYQEaUAgG1dSTs +
oXMbdx8Plo4sCrEWA8F9QQO8geXGDtv7l0hCCEIQuceSfAsjITnK8GtEtmNfDVrtswvLoA3oggGy
AEAtnDgBAPbysjLWUGRMP39lpcPFNubLXsAjCseDiKeGPHtSnG1B02A3FnzXY27bkRFwFyfBj8 + R
fuwrJsqIOYmIgzgQZgIOB + LqPDsew8DsvYy3 / tWhPG1B6P59leYICPhlNfSOMAw5u1vbjyqOPIdq
Grwf9m8LwA7VLERweIe1VIJJJppJpHBWoW / uFtYQofc580Z / baLY / rcbfloWBaeQgga0RwJdh2h2
kh2hJMJ9BuuswPpY6f9sJKTg + dtQrAXANw5QCE95VQbSHDlyhDlJCaSSSYSQhdfEO0R + 71MVS0H1
fr0Iq8eT7dnneHUdxet // 3FjLpVfxuPA5vK6V66FHLYm / EAkgC9y12MngqL8pCfqfWqrN7gVMopA
Ktl7KyD + VWq1XQkhAq6E00wkEk0ZQ4WN8 + x7qsxHnhJ3HOftGUU00FkFk7wUhCp38yurSK + vlliX
KTHkDTt6gAHL4zVXzzuAZrZdF3fi7g8wAOq0IcQchff83wLVvY7OEtFGVkx / y73fCdEl8lkllliG
X5vZ / cKi932 + qA47TNANYKwHsxv1 / CrdJ4GU84VkMQH6ZRfkAiuqXpKo2B8E0LUFD + ECeVAMfd2U
lGnUr1SN96bNl2RIKsi5Vv8qKk65ZaTEWYHdMJz8GQMADTIBsOtgYFCAEF / wFR2NE1YzWGjZvIYm
JmwN3tt9sgI1 + CA93wQhG316TjKJdbFQD1QruumCcXb / 1T89JcWGYUqGC1vWFPOY2VNL + VnGUaxe
qU1ZldElC24hK / Zlr / kn68N99aonkCAc5kIDFRg / u3y5Ca0lAKQEBg0EjitScs3s9UWFaODgGFoQ
SoAYAUyD60EK6hyVeJV2nXubIAQC2rcrXMJgO7Wrzp2jpMkUEn21JIUvACkOwhpC + Cj37Cr7ndLb
VyHWC2rebx97BJWkOSHJFzQTpnLpSN1uL3qpGRkJvFEgA6 / 0UlFtKy9MbwFF9Dch6Cgh4PkCgEJH
NB / 8mNdfaF3vfr7au3g2e9QY / ZvaEXsTXoqrr6Ki8AKcZQefTZ7cbZoAF30kOXAyCmFuIwF4UdLz
0wdAG + BTCHux7Zn0gDjB2MdwuKkEwkFDm7BQVG9SwqBBobKAEIC2 / qAkLTiVLH2CGVhR6w7 / zz2G
4qJvs / 4A8vRolTkZVBMIEHk / 0DY7B9m1HviOXEWHb1g0gDX9c4k1IQgnCpZb7goDH + EsATHHfW5r
LbGMAWAUPpdrgI6VRESgUKyE5IhMCUSAXF0OPutXyU / nldne719lra1OAJrDNoAcxAbsbiGwnR87
b212K + 23VUvGA31TynD + GeTAQd57WVlBBwfMfH2G9vVKnFU8bad5XlBJJEkiFq7dOqXbc5 + hUgkk
Yaa9Fz1zAwu94 + Q99j5oAB5qtyAfmp7WcjfbM6KL1gjHoSxkv5u99UuF6vpKFrj1olNXufcCSOvh
fd2xlKG / 7XF43ZAvvtmmwMSQEIN5zZVXBSEhR0z6p1kwQQIlMXAxuO6 + Sz0KSzlMz5WwmOP + 5Kui
qyT4ndILsW9CQYQcAodKQlQArUr + ssfHwtTCpILqqo / RgAn + lIAD9mt / zGlUfQ + JjFIiPvA + ZQBp
NWASGpwEHoHCMCERNb2spMiGGdf5lPB2LP76yDuSA / RcQA3CtAvgCE0AXFBfnEx / JLzDNpVN + Oeh
RDKQomGH8ZM0NuzZmn79Hfc4YPup93UHasgv4QggaJHOn1EwiDfAQG9PJ / HYVl1QopWPXywz / U9S
rhfVhWVmAAfiHRe7oEgno + nUBx0EdXoiDg64EG / DBBsgg9aCDM6Oqmz0QfL8dEG6FBrs0iDwuvBB
7bTyRdep5XiYJve8Jn9x2GM6qbeJfw0cfXRFa2WDuk8 / cby9fjROB5FpqFykYm1PLvHxr9qmCrlQ
3MSgOa1zfHbyfH4DKpqUvbDard1K03Worpp08ulIiD8kBJ + xixlpbofFrr5GvR995mAmIg7LlEQa
3spgCDg7 + CD + JEG9DhQQdgIPiwQfIQIPnbrfHZznOc5znOc5znOcg5ByDkHIOQcg5ByDkHIOQcg5
ByDkHIOQcg5ByDkHIOQcg5ByDKpKpKpKpKpogBBm6P8NdyPJefd6HrvsfT2 / rcLO5bK + Fd / 6 + LKK
KpzXN8vf85ofW7rtf77ynFU + GBH + cLn4VHj6WRcDVAg1NyoDLWqA / xzf0dnh9zw9DlqpVTZe1pvE
mKqfhKA + RUB + LOQedMIYxhoxjAYgxJdnGCsYxiMlmDFioJrGMFNUUZYxeY8t57CYorx1nOCppIkm
KIiigqy4cTFFNUzWc5LLuin9UAB + PfrIJy6Pr + eJnNDmJ6W2MzCjO6nVR5IAKGIFqf5QCt0NykAp
yABANmbqCWA9SHG3q / fyKs8ArifkXP + VZ0r6UUrSp1SWcynqM3JfGGb + TCzdL5gKXUGufguANt9v
Ggo1uIp7qnq8YPU1TcaHnc60 / WXABx + o3NBl6vpnESB7Qe3arD7KR7GaKE0 / 6fCWKsuwc + gHKXeR
4VsFtjLcunY8sAnADS2SRNmAretAvcMkVr2Schn81VtvUD / snTAHjMfR7ALTtdjoLsSwiQDcVyUB
AkPgLWrSIdgKC8A5oVqH / dhxxcHj2dYznw2OcwKpyAXkyo8wkxm6GWybIiwjlYlJ8WGtsjSTLmfQ
YoltFrJy4WhtlrPd0fJ + 5RV7zyAmXXmQBz0Eb4IGo4j + HFyuxC5hmA + Lp7XGM6VBwKRtqmGZKoL8
Q + g1DSBrpMd / IFp + MvHBZ + JBE6kC6ANXFK2cfW8wgD0q5KxwFQVFWgB / mQaMAd56DKEBg3yHHvMD
hOJemYWGWffGSSokFQhNBj6gfMGpzy9QO7zQ5a8CFA98Mv0CgfzfuHIKdPp6gAPD8ABDIPxPP997
00/1 + 7Vf0M + oxq9jy5ThAnvnJmTj + 2OYOg5CZyJE8f + 3uXucXvDP9T + SDeO + ISnbL4bpVUABUYVL
7gXNX / LoTNp / Ym9nFudlNVYiRURymZRUkQUSLMrFBnwVWIkVPTq / 8u5U7jm / + IbMP2rfw3WSe55f
07XWWsziJFRHqfxqHNvwzZRVf3qVwc5C51vnWzfqnmasiQUz + G / fJRxMwp65aQcziraVQVB51ABt
gNsB7zG2DaA2gNsBtoG1BtQehOlzvA22DsB7TaByDxgdwHdyDsB1BgdQZoNUDbjTAzwZwM8GQG8A
zwaIN4Bmg6g7AcA4B0B1B0B0B2gdQcg5BwDug6g4B0B0BgdA25O0tgO3vA7gNEGgDRBkBogzwZ4N
MDNBmg + zPBlByDqDsB0B0B2wdNQdAdAcA7oPY7QOQfqNgOwGB2G9IbAd8HdB2wdgO0DgGeDOBxBT
g1IMp86u6Alr2mBsKcGoBlBpzpeXoGzgiImdM8ryUktMDWAzweZw + DqgayrvFO1YNiDUA7QPJ0Bg
yDv8AM8aHdnQHV2gddbaBtNAdm1zITAPADug7oOwHcBwDtA7AeODwA6g6g7AcA4B0B1B0B0B2gdQ
cg5BwDuA6g4B0B0BgdAdAdwHYDmyeDOBngyA0wM8GiDTA + / OBnAzwZAcA6g7AdAdwHUHIOQcA7
wOoOAdAdAYHQHQHeB2A7AdQdgOAd0HYD6HcB3gdgOwHaBwDgHQHYDoDoDtg6g5ByDgHdB1BwDoDo
DA6A6A8xTqUKCmvoqUFKKkilQpRUnqU6k1SapOUkUkUmKTVNpNUmqUVJyl / mKT5qminEU21MKaqd
tqphT + HYG / FV2rh21OMpUKUylBSipIpOUnKU6F9B / UDkrP6SW8gOEGEPQ6gHrR4h7AexQYQd3VOQ
Q5kCey9eYBCoBYBqCcoNtZ7 + lYPlQ5EZ0fwSB / St4j013Mj796pDwCEQBm0CAgEgpnKuxT6CMeKs
kFMsHy8JQdwgqxELgrIaSywjHJLQubX1C7AyKXGirdk5H946pfTw / XYoSR63vmvLksL76P1OwACB
hBDWusoCggvyACoQSAQYV7Cw4CBAImcuMkZG4aSZTgFkAAn5LcC + a1Wyqo9CCRnc + Rc3 + qLLLcNC
uZhQbgEGoxHOdhRL8F8 / liQxLf5kmp4qe / 33a2Ifbq / iyvacKwD3hAA7GIHX6MvCCKFUwFNBw1zw
cTvqiTlOP9KfzgEArnQBgcS4FPROW2ZdJoXbJGrzV5UeSjVBoAUD6v679RAg7XKhiQOmHHRc / VE2
HDegnENsOjHiz4yDYdb3eNY + IESh7XQgJDIrVA86uvicQoRfF0tFcwWrKLp7OoFr1ihsAblmMh4p
uIrW8exXq82p07OUnfw07QdCJDy2FaHZNmQRhcreIP2XUqHdfXen8krrwwQP9yOMxwVANQC / z1vJ
IEQQGcJdetX5q2ftJ2wM9Kj + rngQmC3PhSSAEmj874BadAqcp0TGT0FW1eKpIrD1rh6p / IJJG101
lx8bL5xf6gL89IEAI4IBCSWG3OiOaUqTo3bqCg7zKA73qrQ7zbC8hH + 97gcnxYaafW7KAkLqugCA
AOkUapcHf2jLhgHHedVmxgdas8utk5eurnq4DLQm5q9Zq9R4uy5ME3AwoQJ + 6RGRCECRkJYCAgIZ
WWAkJaBBhQamA5 / H9TiredrDtvtaCZhRb2knhxvtzJ8wLQHVS5nbSXjlMCAwURQ84JAOtPEFc5vL
tA3y2anUm4 + KCWSD6Ao8fRFuTr9 + d7V1SYFXxsFzbPofblVbcAo21b3u / UfU2qy6y6f54Hk0IOq6
l + 722ZguAaRAOw7vtUyqOshz1I8bzS2O29n0Xe2YHPQYcXgIPAQAGt7kG8x7K5JoQiaRM7VI0tLv
IzZtYzZutjNm1jMDkGByGtFc9ZAdQYHUGB2bNSpoyA6gwOoMDl0JIwA6AwOgMDnQoiKwA6AwOgMD
nSCrY5AdQYHUGB1dZijADoDA6AwOmjVMYVNFIUmKQpLMidOlIlZgpOU + XtLcv + Ro9X10bv9O23QX
7SXom0IIf3783F4pv39Hd5mApYbSLiSSEgXwO2Bh / X9Klcmc4s7ur8hIODmidLHN8l2Dzm17p770
nm / v / ZeD8VVVfAxiMRiMRiMRiMSOAYBwDAOAYBwDAOAYBwDAOAYBwDAOAYBwDAOAYBwDAOAYBwDC
mFIUwpEiHp5hzUO9MUV + evADDkP70VrEkDS4YAtctpD1 / JLDtsPG0gb / UlKiuB6Lor3bc4AayABK
2H6AIMv5AEGw2nTf19GskBAMPpNPm79DmTgiArc9l7a8F5Pn0kKRAg6tAEEgJwcU0AofWwmlBCog
aSYorSvhH0eTViKn + EVEcUjVghr6SMacICSGQegFn + errwXxHkzS2eAFY + kDmfhyVYaAHfOIWsON
2vIUbiBJz4GGp7J7ZmrAl + 8znekOADQSzQMmnd2djdZ2M42gxyWkpoS0Z / vz6iraIOyH / wgFImr9
HwOFslabdTMuJGdceuFXIz3k / HiqW5CEEzz + / 68Cx84flyhY57tPmrL3W6XTk + HBMuD4dNfyseDx
IEUgAJiBFRbZ6Ya4GTBJCu3aIgUNlzPNZMwD7e3doqIb0FU5NKLU36M1UcrY9Zasd000667DsA9O
2fezKtc0ACZ96QKVVd38AfCG + lH95C0jZTjIjz6LPCndEesoM8EAfJwBU3FtBf8LbLAs7jxvq8Tv
qoHj1Pae3itldbEtbHIcaQtA / T2gM7Xy / l2IIPrHIq0YFyTSFAEghN6EW32UGufZb2p5n770BUPd
gGLMAfmgBuHBIQ6QPrac9eHQrlo346lJmu412dxLYjEIJa27hVHQ5WBrsGxeZ6EW1JTf / Ds66sUn
LNwD3st9HUGCOrbltE3rSsVdTqt / 46l9lVHB9IcEhNC5jmbvWBY8 + rEt2c + Jz1c0fcK2A9FcYwTA
A33hM4C9W90n4jy9xX2gHpQRxa1Z1rQoRoUACz3TvKeTcgKk2UWvmRgkhruhdfJB1LFUmlBvONB +
ogvCBXwgvXE9OtwY00kuCfLAeAC2cHo3Sc7Wnhg3AN3 + ejuKl274 + XOx8kGQLf5 / PZW4YNShBAzn
ZvPOAUxyGtz0Xw7uwfqxzoni0IADC8UZcAGqKHfASyl0YOKxbQqmJ6qXPBKbgCBkbvJZe38D4TnS
EnhPRyyWUAAZhmLoo0RIFrpIKAeTvE4ZGApO7IHx8pmW4B0cIyysrDK9RK7ldiJ2xH + ZDOqZwrV5
IWgGf7BHacj / zjs8ZbVBy9AUjrtH9JP5V7NZBeS4AMLFxmnU / 8Pc5vhAGd2F5ye7J4uNqoBwjEtg
DyjMXm4fg1m / FN2ThVG0YUeHwfbViOzBSZFVGnmIt6cOggOX3cQ / XPHXWqW8EBV1hQYuqCtAef2q
5lDUA / hV + a4U ++ br2PUbhM0H3qP0gPAmWccEEjwi1 / j3N8RrT61o7DlIYADnWrl2vfanPXD + wVxc
AHX9CiUM7sTo7RMmFosktmAOVS900leEdROCR9iM7Zs50p6PyHffleg / IdqBEVdI78gAIEBcqbA6
FAMB + eJr253U7kbWK / dO + z3uLlW3WIGwx / + / 6px9fuOCCh + AL6daPfFXSr5Wvz2rr / c2cTW1uwRt
Gc7N2jfXIEC6LC8JHoQxn8UlslTC12NnCT7 / p84AcFqB74ViQ2B / NAOGsjL7wADq1Y1idU0gXzgJ
z88WoQzkNP8l1JN28Y0ueW4fW + 9OnI4BN1nsGYrVnP0EK0Du4DLYPxZ6 + H5IAbEsqbE / AAaflq3m
9v0nPc5TZKryWWwAqe0CQEPCBjwEFLClfLzMRDf7YJDSfdpIGNKxkMtxQz5mVUuH8nmz7sA7IKm3
q3zz6tTuHKxCLJjIbhWPoQw7RfzxvrXlyRV0ErIXEc3z5Nu142U1ZEhrdds99MbUNgDHyT3MIDTc
WTHq8NW7x1yYLqK2g5n2gCWIAVQkoB9Sd9R8ml0EHnCQpfuAgkVF7p + hcu2sGhA7UHFb / K54c7kH
JT2rtbgiECCaa7y7a3AtQ8x / YnegM1rOa3AEgILxcTuc8NXIGxKAcmgcqncSKFm0 + GVh34Ks4Ca6
mLKrDRkAdJE7fvjlTdyZd6IZeFjoMBUr0Oss8GZIEA8JAX15gXYBSulrshIayPQtWZJwK94rAkiA
Nvf3mmAvGj + 2mK + I1rAkorUZmznVe7sM7WTagTq8TvPdsJyJVh2sWSQmeunSj + i4dOY2YDXzsRBY
H6cxvacIkQXGZKDpUABP8UcNslpUMFyWYINks4Ais3c7LfRktkuLU0SAM + ghrSZPZanGAPMBE + Hi
QzPqo1s6rtlvFSGZlBGUAXB2B60DBoBjeP / vQgJa5eDI + 1T62TF / 7V0mY39eHq8fr17sAovGM3EV
p22sGHrTr8poMFo9rlh / ekPpVtgDqcIkmx4AD647TveT4tgtIJd2RoIIYliGIUPWwPloB9vIeqgf
cw8 / AHwYBCsMtKPj4Q3YA1nsoFDkynvLIdBCq + 3j8iPGQAUCPgQqQBEVeYvbBQQdaLoAUiXpA06D
iU1wwBubeq + awjNNYAf656ARWVRVg2yabvpLxuu8n / 0m6PRdwus5QSfuMQDn9jvw8JQthjErS5AO
4 + lodu6lZ9t1fwXjTfZTx855NErDYAuLtSqJlQ2sWDiuttyP8zoxnrf4xtV + XlA / dKJXjYjA0q2t
41b0lD9 + Qk0 / iefL3GlEkAqkJFBsxOdndROfSLc5CczexkbfYo / qReS1WrbuyAHQB / Cr72MFRKrK
eZ21Cj22nfpw27rIBTJaAHU8e3azXR3SvGMvWvcZcAgHNdyu2shZN4zZOf3h6 / rlEPSuSEw + IEog
pci7XfcxtvD2oORT / k / YLQ / Cq7nu1pvh4xIZNAdgG0H0ubtGvpfo8bOEPr72fBeuzS4G7jMAcwWF
27ALsMvbKdiYk758z1fhZ2tpE582oakBCDQpYLb2f9aaIVde2s07g / YmQ7K / 4hMXjjv / vydgAAHQ
so9Qndo / gHfAMfN4bq559ZJvb9uXqspwG9t85m88P5 + Rr4aOSdOAgVlikVHyd2Ti3HmKBi / MqaDV
sL1wnAE / ugR4gKqO / JC8W22Jze5spAZvgZQABAHIQmjKk5GtKIji7fxd1TQdaKuPNiFUFWJS65ED
L8z9yBQjcAwxBBYEEAsH4A2Pyua7maKaRlLL19G3fKeTethH7eWqznAFAdIQSJG4csaHOYnnS / OX
OOHLz8lZFKrC0uU3JBd6GNU6sAhYHhEwgm3AHBDEV3azVaxnDsmWxXKeZ66gYHfVOQvAAfnmKdAA
BIEPC8QQIyeprt8vV1H5c5vaVWRPLOCM3l6atqSeFYOWBegAvIdAGgntK2Uj5vwFD4VlJKRlVC0J
z7nP3MFqAVT7rRek8F / zY / 7AZCE1Mrg2UBFgb3MwWGPk5njynv4mSpr8zgQJVAA5f2WelIGtynR4
eJZ7SHrvi9vVrAGyAhaHV1CaAlNhwXdaqGcgCRBIub9r50A4P453oWfa3 / f1novK3rrL0cO4g3SK
af + J5ll / sWAnU9Qg + 7GsMugb1ndCCCgnUIOCxgAps78HV6c2X8Pd5fpr99Tvf99P7ZH36B82eSfM
zPMVDxYTi4TULbd9 + HWsQBk0FnkMj7sKQBqbHS8W2fZvLjn9dn2ne1C8G8udbkfmv4OO1Hv / zKzf
W / 4AYYDnjbtA5BuaCPj + 2deggY + 4fpvWkp / 7SWnnYjYdHgsPzmsdvs2BQGr3 / iVIjIZqXj5dxXoc
QxEggGx2rMV7F88Te7m62r2tH5M7Oqu1 / VivenvOV7fciH0WjqZr5PVHvRXfy / olOD8Vzpv8KmJJ
GVj2z9btv797opNtz / ++ ljzO6sve + 7v7PIf3X + zwH / 490QCcPwaLh2wUq6aAI15GitK8xY / VBAmD
Y4wECM2NzkWYHL1PCtuLlm + KZ0fN2uW2X8QbS7RlG / 1hWL7ZKPCigVz + ow7W1exUYkkdhiNj1rhS
6aPGP03 + 8vp81Tydlr9vBVV3dtdNQeC9a7swGT7LHEKLnz6TbcjF6 + sEDIrebWc6 + symQJF8 + 0DA
KIQE9316H0UJ3jfRil5W0zvf / m96XYOGW5m + RA8DZsIaS1NVa8 / qbc03xlIbFgaf8czo6TwZy4Qq
6oXJBwkckTuv3j + TEAfx2s2lorR9max28vusi + x + UFAWnWPsdzOw5p6pTVKjx + FrlFxff + 0 / W8Z2
Pfx + lo5 / jvwnM3Ej6fonL / tG9Jb5lthrAMgMbgqyDfUELd7f73lQZ + kBSgP + 5d58WT9mRlNjT2vN
/ K96yv + uCy2PxPsoZa1Qdn4X + deSU7Mn1VyW9ymSQKfkeYaeSFbQQKvLHS8ezgjOE31LchHRplOT
7nwrrfv / VQ6Cevfo + 9LEdvZfx0PjiVPWsMq3S0cR9sC5ulgk4 / NXKv4aNFQ8 + z6Wja74nE4aVJGN
hax + pB0f8 / CranGLVF51Dbk9HG / bYcCo2Yk9bMrOGN6uh7hZNvCgkcjaEdUbMrNL3sh / YGOpv5I5
RL6cXmOAqaCWoDU7jrXfDDSWkkHmkoPI2H0TFz3N + ooK6ez89y49HZqWWzn / prn7z29WzUMpP87P
dR1vh97fLrzLn8QJe + FggQcJyeQOqBBoH5zrPAnmGE3UVGwbVa6 / OAt / wImAcgKDCXq4 / vZJLZ8C
O / 7L3zaNrYvjeRX7FlrJVvRe46TEw9oJm92C15gE5MLfFg4MgSJIqsL85CjaswBXvKP6gf8DQjpb
7bohufI7 / Lfve / V68Rt59Vw6Ccwklrdt3fWv4U9Ieqm + 6TBAt4H6i12qZuCaoPl01QZoD7VYqZSO
qqsZxhuB14YOQ2TqJDxrIupeT4ezp / e96svnMfH4HSdX3WTqPRTyrIkgdE / xfn3mdk3FEiamcZz +
aN4bEDzY8kLcViBKY0ADNEfO0FMDk3s + lVIqehPUL9zsT + 7fOu / szXSwUQiJiwSF6XxxN + zGIqIh
b / 2eZF1JSQHRInCCL8Kx0wCBSFH4kIOVRkINIwSrZp9MLEur90Z / lHg + FIfX25SLhuiTEUsxgSBC
W8u7Vyoa5oyOt47 / 9LW1jCRvAt9QHWIFoA4hPiHxKjYD4RKsZYYCEix / 7v4jmv / BoJ + w73wg17ei
UfWmS9Gym1Mvk6DIVAAA2dVFlODLKZ7CKFV2XZd8JfU3aSJdOX / 7CXC82dxOaGh4 / t2OkzHr1feY
3uchvA0E3hbXh / e9xL5f1NrmhDcAdYFaN3xjnWcGtPmboVrR + 425TFUK90qF50vGqFIQ8vB23tgr
hk + BmYDp8XppnmIAFVtly8H9CdxfBVdKDAfpCHsxgOg8r + s7TmgnFB4khuezp8eGRNAbDeBYM3k +
S0tX6iPW1vuU6PkPBhiN6ZbuWqEi5r8EgQxRdSMDrm2C2wJ35ItBOIAwgNYAwhzwCnjFYaYLe4zo
hUtVOw + SabSJGRo5RqKoG1ur7sug33u7uA6fT6D2EgmX863NQOqpYgC7iC1XuSDmQ8ejtoHUz7Xj
+ wAwK2VU4shsdl8KjrAD3Ec / x4iIhh6cfC / Ux8CaDwWCCEZNWAhBRwF2aHZQhCA / mrFo + vN / Qvbg
cEblqZlCXbDMb60IqgnNJUAjFwAQhCHd9gYGJYuGvqlApR6hSmG4n1IQIfSXftZLPZhCwOdMJARs
b2u4hFb2ggZGvjM / hJSUNI / 3 / f6ycRymgi4X / ka / HWKpUDqAw / j / jx7eXc76q1P2DLJI8S / a / nV6
S5Ud56prWfrixl + o6wtGlyvkOqos1oPAECxSyM5KaMCjlLFIDx + ej59Fkdf8jAjUZvw / yBT6ygvL
oeLO214jL3sfoifigCAfaz2OsowBeZ + rf2 + 6nkyfF09j90 + bpttxO3Hx14I5ct4Q9jH93j + nVR2g
hEAxAMQqkMKV / kWXwzc77t52KJz2q1zmq1S4G1x69Kz + oA / QU4AB / 2y1xh6PhsZtoWaHyDu0S5IQ
aSh8LeP31 + wIGbyOZVPkM2n / YjAUNv2bEHXhYHIFvQT9yf8RCXZd4vl6ASrnKLI + d2qYnNrUW1Ng
NlAj0rf5VkhJxFSCanG + iIcyyqL4qX9xHgAA + mluM1rrwAtLkkd1ofd87lZem + qk1KEb5dpDbi7q
hgDK2LduIJ5owSR57uIuyP6wkaW2JAprdq9w9EpFoUeSbl7a7F9PjWsiBmAgY3r3ePVBlVEoMPe3
O9cQdd6PWGEBoO37at + 5dF + QAGt09ODv53V / e5D1ECNjYVlx / s + fB3 + yrXxFKOWJJzXMe / Qtqhjg
IljLH0OeH6nbW3U5Tf78Q0uY6rxzGXG + F8C0K4a + E4nmxVxbwit7QEFOk2lfszEG + ggIIlbcPP6G
S / 84Fp8AMwakQn2JjdgACpWYA7bjIRrLGtDkL0EC / wzdu + ttg9GUvl3BuQv7OJHS9NQBw + YEEKV0
BXkDbI36AKvsHLP1g1 / iP8aSBr8podjCY2fuLHnOOX4sthQSSyUwlC97ntxmDg28dRtbzRuQ0wP8
3V62hO9nc7X9fb9fznzhRBNYF5IFEEjJQBIKIJmK7I8Xh5Pn9xywJX7HKInI9jqQQbwACgmCD1RR
BPBFEE // F3JFOFCQsfyJNg ==
''
print (['', 'Up', 'Down'] [int (pickle.loads (bz2.decompress (base64.b64decode (s)))). prognozy (numpy.array ([skimage.transform.resize (skimage.io .imread (sys.argv [1], as_grey = True), (24,12), mode = 'constant'). flatten ()])) [0])] + 'goat')

aktualizacja: na żądanie tutaj są dane szkoleniowe, zmienione na 24x12 i połączone w jeden obraz w celu ułatwienia przesyłania / prezentacji. to ponad sto zdjęć. http://deeplearning.net/datasets/ , http://www.vision.caltech.edu/Image_Datasets/Caltech256/ , wyszukiwanie obrazów duckduckgo, wyszukiwanie obrazów google itp

dane treningowe przy 24x12 pikselach

Don Bright
źródło
Czy możesz opublikować swoje dane treningowe?
qwr
niektóre oryginalne obrazy, których użyłem, są chronione prawami autorskimi, więc nie mogę opublikować ich wszystkich, jednak zmniejszyłem kilka z nich do rozmiaru używanego w systemie, 24x12, i umieściłem je na jednym zdjęciu powyżej, co powinno kwalifikować się jako „ dozwolonego użytku'.
don bright
6

Scikit-learn with Random Forests, 100%

Wypróbowanym i sprawdzonym podejściem są sieci, ale losowe lasy mogą działać bardzo dobrze od razu po wyjęciu z pudełka (kilka parametrów do dostrojenia). Tutaj pokazuję kilka ogólnych technik w zadaniach klasyfikacji obrazów.

Zacząłem od 100 zdjęć kóz do treningu, które znalazłem za pośrednictwem Google Images (AFAIK żaden z danych treningowych nie pasuje do danych testowych). Każdy obraz jest przeskalowywany do 20x16 w skali szarości, a następnie układ jest spłaszczany w celu utworzenia jednego wiersza w układzie 2D. Odwrócona wersja obrazu jest również dodawana jako wiersz danych treningowych. Nie musiałem używać żadnych technik powiększania danych .

siatka kóz

Następnie wprowadzam tablicę 2D do losowego klasyfikatora lasu i wywołuję prognozę, aby wygenerować 50 drzew decyzyjnych. Oto (niechlujny) kod:

RESIZE_WIDTH = 20
RESIZE_HEIGHT = 16

def preprocess_img(path):
    img = cv2.imread(path, 0)  # Grayscale
    resized_img = cv2.resize(img, (RESIZE_WIDTH, RESIZE_HEIGHT))
    return resized_img


def train_random_forest(downgoat_paths, upgoat_paths, data_paths):
    assert len(data_paths) == 100
    # Create blank image grid
    img_grid = np.zeros((10*RESIZE_HEIGHT, 10*RESIZE_WIDTH), np.uint8)

    # Training data
    TRAINING_EXAMPLES = 2*len(data_paths)
    train_X = np.zeros((TRAINING_EXAMPLES, RESIZE_WIDTH*RESIZE_HEIGHT), np.uint8)
    train_y = np.zeros(TRAINING_EXAMPLES, np.uint8)

    TEST_EXAMPLES = len(downgoat_paths) + len(upgoat_paths)
    test_X = np.zeros((TEST_EXAMPLES, RESIZE_WIDTH*RESIZE_HEIGHT), np.uint8)
    test_y = np.zeros(TEST_EXAMPLES, np.uint8)


    for i, data_path in enumerate(data_paths):
        img = preprocess_img(data_path)

        # Paste to grid
        ph = (i//10) * RESIZE_HEIGHT
        pw = (i%10) * RESIZE_WIDTH
        img_grid[ph:ph+RESIZE_HEIGHT, pw:pw+RESIZE_WIDTH] = img
        flipped_img = np.flip(img, 0)

        # Add to train array
        train_X[2*i,], train_y[2*i] = img.flatten(), 1
        train_X[2*i+1,], train_y[2*i+1] = flipped_img.flatten(), 0

    cv2.imwrite("grid.jpg", img_grid)

    clf = RandomForestClassifier(n_estimators=50, verbose=1)
    clf.fit(train_X, train_y)
    joblib.dump(clf, 'clf.pkl')

    for i, img_path in enumerate(downgoat_paths + upgoat_paths):
        test_X[i,] = preprocess_img(img_path).flatten()
        test_y[i] = (i >= len(downgoat_paths))


    predict_y = clf.predict(test_X)
    print(predict_y)
    print(test_y)
    print(accuracy_score(predict_y, test_y))

    # Draw tree 0
    tree.export_graphviz(clf.estimators_[0], out_file="tree.dot", filled=True)
    os.system('dot -Tpng tree.dot -o tree.png')


def main():
    downgoat_paths = ["downgoat" + str(i) + ".jpg" for i in range(1, 10)]
    upgoat_paths = ["upgoat" + str(i) + ".jpg" for i in range(1, 10)]
    data_paths = ["data/" + file for file in os.listdir("data")]

    train_random_forest(downgoat_paths, upgoat_paths, data_paths)

Oto pierwsze drzewo decyzyjne (chociaż ponieważ model jest w zespole, nie jest szczególnie użyteczny )

drzewo decyzyjne nr 0

qwr
źródło
to bardzo interesujące ... twoje dane treningowe wydają się o wiele bardziej różnorodnymi rodzajami zdjęć niż moje.
don bright
@donbright Zamieszczam moje dane treningowe, ale folder ze wszystkimi moimi zdjęciami znajdował się na dysku twardym, który zmarł. Jeśli ktoś jest wystarczająco ambitny, może skorzystać z wyszukiwania wstecznego obrazów Google i znaleźć zdjęcia, których użyłem.
qwr
to super. Pobrałem kilka zdjęć, ale spędziłem mnóstwo czasu na ich sortowaniu w celu uzyskania „czystych” obrazów. ciekawe jest, jak można trenować w oparciu o bardziej „brudne” obrazy, bez konieczności poświęcania tyle czasu na sortowanie.
don bright
@donbright Wierzę, że więcej danych treningowych i różnorodność są lepsze. W przypadku „czystych” i „brudnych” możemy użyć powiększania danych, aby utworzyć „więcej danych”.
qwr