2017-05-31 6 views
2

Ich versuche, Markierungen auf einem Bild zu erstellen, die es einem Benutzer erlauben, Farben auszuwählen, Features zu markieren usw. Letztendlich würde ich gerne das entsprechende Bildpixel für die weitere Verwendung über opencv haben.Warum gibt kivy read_pixel die erwartete Farbe nicht zurück?

Ich habe viel Mühe, die erwartete Farbe unter der Berührung zu bekommen, und es gibt manchmal Farben wie Magenta zurück, die nicht einmal im Beispielbild sind.

Ich bin mir ziemlich sicher, dass das Problem darin besteht, wie ich die Berührungsposition in die Werte umwandele, die ich an die read_pixel-Funktion übergebe.

Ich habe viele verschiedene Lösungen ohne Erfolg ausprobiert, also denke ich, dass ich etwas vermisse.

main.py

from kivy.app import App 
from kivy.properties import ListProperty, ObjectProperty 
from kivy.uix.image import AsyncImage 
from kivy.uix.relativelayout import RelativeLayout 
from kivy.uix.widget import Widget 
from kivy.uix.screenmanager import ScreenManager, Screen 


class Marker(Widget): 
    selected_color = ListProperty([0,1,0]) 

    def __init__(self, **kwargs): 
     super(Marker, self).__init__(**kwargs) 
     self.selected_pos = None 

    def on_touch_down(self, touch): 
     if self.collide_point(*touch.pos): 
      print("Touched at Marker: {0}".format(touch.spos)) 

    def on_touch_move(self, touch): 
     self.set_position_from_touch(touch.spos) 

    def set_position_from_touch(self, spos): 
     # print("touch: {0}".format(touch)) 
     self.image = self.parent.parent.image 
     x = spos[0] * self.image.width 
     y = spos[1] * self.image.height 

     # setting position of the widget relative to touch 
     self.pos = (x-self.width/2, y-self.height*(2/3)) 
     # self.pos = (x, y) 

     print("widget position : {0}".format(self.pos)) 
     # converting widget position to pixel(row, column of 
     selected_pixel = self.image.to_row_col(self.pos) 
     print("selected Pixel: {0}".format(selected_pixel)) 

     try: 
      self.selected_color = self.image._coreimage.read_pixel(
       selected_pixel[0], 
       selected_pixel[1]) 
       # this skips conversion and just uses pos 
       # self.pos[0], 
       # self.pos[1]) 
     except IndexError: 
      print("position out of range") 


class MarkerManager(RelativeLayout): 
    def __init__(self, **kwargs): 
     super(MarkerManager, self).__init__(**kwargs) 
     self.marker_mode = None 
     self.features = [] 

    def on_touch_down(self, touch): 
     if self.collide_point(*touch.pos): 
      child_touched = False 
      print("Touched: {0}".format(touch)) 
      if self.children: 
       for child in self.children[:]: 
        if child.collide_point(touch.pos[0], touch.pos[1]): 
         child_touched = True 
         child.dispatch('on_touch_down', touch) 
      if not child_touched: 
       print("Touched only Image at: {0}".format(touch.spos)) 
       marker = Marker() 
       self.features.append(marker) 
       self.add_widget(marker) 
       marker.set_position_from_touch(touch.spos) 

class SelectedImage(AsyncImage): 
    def __init__(self, **kwargs): 
     super(SelectedImage, self).__init__(**kwargs) 
     self.allow_stretch=True 
     self.keep_ratio=False 

    def to_row_col(self, pos): 
     pixels_x = self._coreimage.width 
     pixels_y = self._coreimage.height 
     pixel_x = (pos[0]/self.width) * pixels_x 
     # pixel_y = (pos[1]/self.height) * self.pixels_y 
     pixel_y = (1 - (pos[1]/self.height)) * pixels_y 
     # should correspond to row column of image 
     return [int(pixel_x), int(pixel_y)] 

class ImageScreen(Screen): 
    image = ObjectProperty() 
    manager = ObjectProperty() 
    def __init__(self, **kwargs): 
     super(ImageScreen, self).__init__(**kwargs) 

class PointsSelectorApp(App): 
    def build(self): 
     return ImageScreen() 

if __name__ == "__main__": 
    PointsSelectorApp().run() 

pointsselector.kv

<ImageScreen>: 
    image: image_id 
    manager: manager_id 

    SelectedImage: 
     id: image_id 
     source: "rainbow_checkerboard.jpg" 
     keep_data: True 

    MarkerManager: 
     id: manager_id 


<Marker>: 
    size_hint: None, None 
    size: "40dp", "40dp" 
    canvas: 
     Color: 
      rgb: self.selected_color 
     Ellipse: 
      pos: self.pos[0]+self.width/4, self.pos[1]+self.height/3 
#   pos: self.pos[0], self.pos[1] 
      size: self.width*.6, self.height*.6 

hier ist mein Bild habe ich mit "rainbow_checkerboard.jpg"

my test image

+0

Funktioniert Ihre Funktion 'to_row_col' korrekt? Hast du es manuell überprüft? Vergiss nicht, dass dein Bild wahrscheinlich so gestreckt ist, dass es die gleiche Größe wie das Widget hat, in dem es gespeichert ist. Betrachte 'allow_stretch: False', um dir beim Debuggen zu helfen. –

+0

Ich erlaube Stretch, das Bild nimmt den ganzen Bildschirm ein und du kannst nicht vom Bild abklicken. Ich denke, dass die Methode to_row_col funktioniert. Das bedeutet, dass es die Position in Bezug auf die Widgetgröße normalisiert und dann mit den Dimensionen des Rohbilds multipliziert. Ob das genau das ist, was ich tun soll, ist eine andere Frage ... – mkrinblk

+1

Wenn du noch Hilfe brauchst, werde ich es mir morgen ansehen. –

Antwort

3

I zu testen, mit glaube, das ist ein Fehler im Kivy selbst. Besonders denke ich, dass line 901 at the kivy/core/image/__init__.py

index = y * data.width * size + x * size 
    raw = bytearray(data.data[index:index + size]) 
    color = [c/255.0 for c in raw] 

falsch ist. Es sollte

index = y * data.rowlength + x * size 

stattdessen sein.

Wichtig ist hier, dass Bitmap-Bilder im Speicher aus Leistungsgründen an 4 Byte Adressen zugeordnet sind. Daher gibt es ein explizites data.rowlength Feld. Normalerweise funktioniert diese Zeile gut, weil Bilder normalerweise gut ausgerichtet sind, so dass data.rowlength = data.width * size. Aber Ihr bestimmtes Bild ist anders: Es verwendet 3-Byte-Format RGB (kein Alpha) und seine Breite ist ungerade 561 dh data.width * size = 1683 und so sollte auf 1684 aufgerundet werden, die es tatsächlich ist, aber dieser Code dauert es nicht berücksichtigen. Das bedeutet, dass Sie häufig Farben aus zwei aufeinanderfolgenden Pixeln lesen und RGB-Komponenten zufällig rotieren.

Zusätzlich, da Sie JPEG-Format für Ihr Bild verwenden, sind Grenzen zwischen "Zellen" nicht wirklich streng. Wenn Sie stark zoomen, können Sie einige Kompressionsartefakte wie diese von der unteren linken Ecke aus sehen.

JPEG Artifacts

Diese Artefakte, die durch die oben genannten Zufallsfarbkomponenten Rotation Fehler geben Ihnen sehr seltsam (scheinbar nicht existent) Farben mutliplied.

Mögliche Abhilfen

  1. Ihr Bild ändern, so dass es vororientierte im Speicher ist und der Fehler ist nicht beeinflussen (zB mutliple von 4 als Breite verwenden)
  2. Kauf Bug oder sogar eine Pull-Anfrage an den Kivy und warten Sie, bis es behoben
  3. Patch kivy/core/image/__init__.py Datei lokal (ich habe es versucht und es scheint gut zu funktionieren).
+0

Danke für die Antwort. Für Workaround 1 würde es funktionieren, wenn ich gerade das Bild in einem anderen Format speicherte und die un-strenge Grenzsituation außerdem vermeiden? – mkrinblk

+0

@mkrinblk, nur anderes Format könnte nicht genug sein. Ich habe keine Quellen gefunden, die tief genug sind, um herauszufinden, wie genau das Bildformat (3 vs 4 Bytes pro Pixel) bestimmt ist. In meinen Experimenten hängt der Erfolg beim Ändern des Formats nur davon ab, mit welchem ​​Tool ich Ihr JPEG in PNG umgewandelt habe. Sie müssen also möglicherweise ein paar andere ausprobieren. Auch erwarte ich, dass die Änderung der Größe zu einer Multiplikation von 4 in jedem Format (außer Schwarz-Weiß-BMP oder etwas ähnliches) helfen sollte. – SergGr

+0

Sehr geschätzt @SergGr. Ich ging mit # 3 für jetzt, während ich finde, wie man # 2 macht. – mkrinblk

Verwandte Themen