Heim  >  Artikel  >  Web-Frontend  >  Eine grobe Implementierung des magnetischen Lassos in Photoshop (basierend auf Python)

Eine grobe Implementierung des magnetischen Lassos in Photoshop (basierend auf Python)

高洛峰
高洛峰Original
2017-02-18 13:38:093181Durchsuche

Wer häufig Photoshop verwendet, sollte mit der Funktion „Magnetisches Lasso“ vertraut sein, einem vom Menschen geführten Hilfswerkzeug zum Ausschneiden. Im F&E-Bereich wird dies im Allgemeinen nicht als „Intelligent Scissors“ oder „Livewire“ bezeichnet.

Ursprünglich war es ein Python-Framework für die Algorithmenbewertung eines Bildsegmentierungsprojekts. Ich fand es etwas interessant, also habe ich es ein wenig erweitert, eine Shell mit PyQt hinzugefügt und das magnetische Lasso in einem sehr einfachen Fall simuliert grobe Art und Weise. Warum es einfach ist: 1) Es wird nur die grundlegendste Kantensuche implementiert. Es gibt keine Pfadkühlung, kein dynamisches Training, keine Mauspositionskorrektur, geschweige denn Kurvenschließen, Ausschneiden, Alpha-Matting usw.; 2) Es werden keine Leistungsspezifikationen berücksichtigt, nur um das Schreiben zu erleichtern Qt, und ich kann Signal-Slot immer noch nicht schreiben. Ich weiß nicht, ob die GUI vernünftig geschrieben ist. 4) Kein Debuggen.

Photoshop中磁力套索的一种简陋实现(基于Python)

Grundlegender Algorithmus

Ich habe keine eingehende Forschung zu verwandten Algorithmen durchgeführt, aber ich glaube Dass dieser Algorithmus der einflussreichste Algorithmus in der Anwendung ist, stammt aus [1], das auch die Hauptreferenz dieses Artikels ist. Die Grundidee besteht darin, das Bild als ungerichteten Graphen zu betrachten und lokale Kosten zwischen benachbarten Pixeln zu berechnen Daher wird es in umgewandelt. Nachdem das Problem des kürzesten Pfads nun gelöst wurde, besteht der nächste Schritt darin, einen Pfad basierend auf dem Dijkstra-Algorithmus zu generieren, bei dem es sich um die Kante handelt, die extrahiert werden muss. Der Algorithmus besteht aus zwei Hauptteilen: 1) Kostenberechnung benachbarter Pixel; 2) Algorithmus für den kürzesten Weg.

Kantenerkennung

Der ultimative Zweck der Berechnung der Kosten benachbarter Pixel besteht darin, Kanten zu finden. Das Wesentliche ist also die Kantenerkennung. Die Grundidee besteht darin, Kanten auf verschiedene Weise zu erkennen und einen gewichteten Wert basierend auf der erkannten Intensität als Kosten zu berechnen. Aus Sicht des kürzesten Weges ist der Kostenwert umso geringer, je offensichtlicher die Kante ist. Der Vorschlag in [1] besteht darin, drei Indikatoren zur Gewichtung zu verwenden: 1) Kantenerkennungsoperator, 2) Gradientenstärke (Gradient Magnitude); Die Methode in diesem Artikel unterscheidet sich geringfügig von [1]. Aus Bequemlichkeitsgründen wird der Canny-Operator in OpenCV zum Erkennen von Kanten anstelle des Laplace-Nulldurchgangsoperators verwendet. Der Ausdruck lautet wie folgt:

[lleft( p,q right)={{w}_{E}}{{f}_{E}}left( q right)+{{w}_ {G }}{{f}_{G}}links( q rechts)+{{w}_{D}}{{f}_{D}}links( p,q rechts)]

Canny Operator

Die Grundidee besteht darin, zunächst viele verbundene Pixel basierend auf Gradienteninformationen zu erkennen und dann nur den Maximalwert jedes verbundenen Pixels zu nehmen und ihn zu verbinden Setzen Sie die umgebenden Teile auf Null, um die anfänglichen Kanten (Kanten) zu erhalten. Dieser Vorgang wird als nicht maximale Unterdrückung bezeichnet. Verwenden Sie dann eine Methode mit zwei Schwellenwerten, um diese erkannten Anfangskanten in drei Ebenen zu unterteilen: Stark, Schwach und Keine. Wie der Name schon sagt, ist „Stark“ definitiv eine Kante. „Keine“ wird verworfen, und dann werden diejenigen, die mit „Stark“ verbunden sind, aus „Schwach“ ausgewählt Da die Kante erhalten bleibt, wird das Endergebnis als Hysterese-Schwellenwert bezeichnet. Dieser Algorithmus ist so klassisch, dass ich nicht näher darauf eingehen werde, sobald Google mit vielen Details aufwartet. Die Formel lautet wie folgt:

[{{f}_{E}}left( q right)=left{ begin{matrix}
0;text{ if }qtext{ is on a edge} \
1;text{ wenn }qtext{ nicht auf einer Kante liegt} \
end{matrix} right.]

Tatsächlich ist die Berechnung der Gewichte mit dem maximalen Gradienten etwas repetitiv, Denn wenn der Pfad entlang der maximalen Steigungsrichtung gefunden wird, besteht er im Wesentlichen darin, die Rolle dieses Elements darin zu sehen, 1) Abweichungen von der offensichtlichen Kante in Bereichen mit großen Steigungen zu vermeiden und 2) die Kontinuität der extrahierten Kanten sicherzustellen Bis zu einem gewissen Grad ist es auch garantiert glatt.

Gradientenstärke

ist lediglich der Modulus des Gradienten. Die Quadrate der Gradientenwerte in x- und y-Richtung werden addiert zur Quadratwurzel. Die Formel lautet wie folgt:

[{{I}_{G}}left( q right)=sqrt{{{I}_{x}}left( q right)+ {{I}_{y}}left ( q right)}]

Da Kosten erforderlich sind, kehren Sie um und normalisieren Sie:

[{{f}_{G}}left( q right)=1-frac{ {{I}_{G}}left( q right)}{max left( {{I}_{G}} right)}]

Verlaufsrichtung

Dieser Artikel ist eigentlich ein Glättungsartikel, der den Kanten, die sich drastisch ändern, relativ hohe Kosten zuweist, sodass die extrahierten Kanten den Einfluss von Rauschen vermeiden können. Die spezifische Formel lautet wie folgt:

[{{f}_{D}}left( p,q right)=frac{2}{3pi }left( arccos left( {{d}_{p }}left ( p,q right) right)+arccos left( {{d}_{q}}left( p,q right) right) right)]

Unter ihnen

[{{ d}_{p}}left( p,q right)=leftlangle {{d}_{bot }}left( p right),{{l}_{D}}left( p,q right ) rightrangle ]

[{{d}_{q}}left( p,q right)=leftlangle {{l}_{D}}left( p,q right),{{d}_ {bot }} left( q right) rightrangle ]

[{{l}_{D}}left( p,q right)=left{ begin{matrix}
q-p;text{ if } leftlangle {{d }_{bot }}left( p right),q-p rightrangle ge 0 \
p-q;text{ if }leftlangle {{d}_{bot }}left( p right),q-p rightrangle 90be985f5373491494516d25e13707ea3) sein, daher ist die direkt darüber liegende die richtige Richtung der Mindestkosten.

Suche nach dem kürzesten Pfad

Beim magnetischen Lasso besteht die allgemeine Verwendung darin, zuerst auf einen Punkt zu klicken und dann die Maus zwischen der Maus und dem ursprünglich angeklickten Punkt A zu bewegen Zwischen ihnen wird automatisch eine Linie nahe der Kante angezeigt. Hier definieren wir den zu Beginn angeklickten Pixelpunkt als Startpunkt (Seed), und das magnetische Lasso findet den Startpunkt tatsächlich unter Berücksichtigung der oben erwähnten kantenbezogenen Kosten Teil. Der kürzeste Weg der aktuellen Maus. Wie im Bild unten gezeigt, sind die roten Startpunkte, und wenn Sie die Maus bewegen, wird die Verbindung zwischen dem Startpunkt, der der Kante am nächsten liegt, und den Mauskoordinaten in Echtzeit angezeigt. Aus diesem Grund wird auch das magnetische Lasso angezeigt namens Livewire.

Photoshop中磁力套索的一种简陋实现(基于Python)

Es gibt viele Möglichkeiten, den kürzesten Weg zu erreichen. Im Allgemeinen handelt es sich um eine Implementierung, die auf dem Dijkstra-Algorithmus basiert , gegebener Startpunkt. Schließlich wird der Dijkstra-Algorithmus ausgeführt, um alle Pixel des Bildes zu durchlaufen, um den kürzesten Weg von jedem Pixel zum Startpunkt zu erhalten. Nehmen Sie das Bild unten als Beispiel. Nachdem Sie jedes Element mit dem Dijkstra-Algorithmus durchlaufen haben, zeigt jedes Element auf ein benachbartes Element, sodass jedes Pixel einen Pfad zum Startwert finden kann, beispielsweise in die obere rechte Ecke . Die Pixel, die 42 und 39 entsprechen, folgen dem Pfeil bis 0.

Photoshop中磁力套索的一种简陋实现(基于Python)

Der Algorithmus ist wie folgt:

输入:
  s              // 种子点
  l(q,r)         // 计算局部cost

数据结构:
  L             // 当前待处理的像素
  N(q)          // 当前像素相邻的像素
  e(q)          // 标记一个像素是否已经做过相邻像素展开的Bool函数
  g(q)          // 从s到q的总cost

输出:
  p             // 记录所有路径的map

算法:
  g(s)←0; L←s;                 // 将种子点作为第一点初始化
  while L≠Ø:                   // 遍历尚未结束
    q←min(L);                  // 取出最小cost的像素并从待处理像素中移除
    e(q)←TRUE;                 // 将当前像素记录为已经做过相邻像素展开
    for each r∈N(q) and not e(r):
      gtemp←g(q)+l(q,r);        // 计算相邻像素的总cost
      if r∈L and gtemp<g(r):    // 找到了更好的路径
        r←L; { from list.}     // 舍弃较大cost的路径
      else if r∉L:
        g(r)←gtemp;             // 记录当前找到的最小路径
        p(r)←q;
        L←r;                    // 加入待处理以试图寻找更短的路径


Photoshop中磁力套索的一种简陋实现(基于Python) Der Durchquerungsprozess gibt dem Bereich mit den niedrigsten Kosten Vorrang. wie unten gezeigt:

Nachdem der kürzeste Pfad zum Startpixel gefunden wurde, der allen Pixeln entspricht, zeichnen Sie einfach den kürzesten Pfad zum Startpixel direkt beim Bewegen der Maus.

Python-Implementierung

Der Algorithmusteil ruft direkt die Canny-Funktion und die Sobel-Funktion (für Farbverläufe) von OpenCV auf, und die Verarbeitung von RGB ist ebenfalls sehr gut einfach, direkt angenähert durch den Maximalwert des Gradienten. Darüber hinaus verwenden sowohl die Kostenkarte als auch die Pfadkarte aufgrund der Faulheit direkt Wörterbücher (dict), während die Aufzeichnung erweiterter Pixel direkt Sätze (set) verwendet. Der GUI-Teil verwendet Pythons Threading, da ich nicht weiß, wie man QThread verwendet. Klicken Sie nur mit der linken Maustaste, um den Startpunkt festzulegen, und klicken Sie mit der rechten Maustaste, um ihn zu beenden eine grüne Linie und die extrahierte Kante ist eine blaue Linie.

Photoshop中磁力套索的一种简陋实现(基于Python)

Code

Algorithmusteil

from __future__ import division
import cv2
import numpy as np

SQRT_0_5 = 0.70710678118654757

class Livewire():
    """
    A simple livewire implementation for verification using 
        1. Canny edge detector + gradient magnitude + gradient direction
        2. Dijkstra algorithm
    """
    
    def __init__(self, image):
        self.image = image
        self.x_lim = image.shape[0]
        self.y_lim = image.shape[1]
        # The values in cost matrix ranges from 0~1
        self.cost_edges = 1 - cv2.Canny(image, 85, 170)/255.0
        self.grad_x, self.grad_y, self.grad_mag = self._get_grad(image)
        self.cost_grad_mag = 1 - self.grad_mag/np.max(self.grad_mag)
        # Weight for (Canny edges, gradient magnitude, gradient direction)
        self.weight = (0.425, 0.425, 0.15)
        
        self.n_pixs = self.x_lim * self.y_lim
        self.n_processed = 0
    
    @classmethod
    def _get_grad(cls, image):
        """
        Return the gradient magnitude of the image using Sobel operator
        """
        rgb = True if len(image.shape) > 2 else False
        grad_x = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3)
        grad_y = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3)
        if rgb:
            # A very rough approximation for quick verification...
            grad_x = np.max(grad_x, axis=2)
            grad_y = np.max(grad_y, axis=2)
            
        grad_mag = np.sqrt(grad_x**2+grad_y**2)
        grad_x /= grad_mag
        grad_y /= grad_mag
        
        return grad_x, grad_y, grad_mag
    
    def _get_neighbors(self, p):
        """
        Return 8 neighbors around the pixel p
        """
        x, y = p
        x0 = 0 if x == 0 else x - 1
        x1 = self.x_lim if x == self.x_lim - 1 else x + 2
        y0 = 0 if y == 0 else y - 1
        y1 = self.y_lim if y == self.y_lim - 1 else y + 2
        
        return [(x, y) for x in xrange(x0, x1) for y in xrange(y0, y1) if (x, y) != p]
    
    def _get_grad_direction_cost(self, p, q):
        """
        Calculate the gradient changes refer to the link direction
        """
        dp = (self.grad_y[p[0]][p[1]], -self.grad_x[p[0]][p[1]])
        dq = (self.grad_y[q[0]][q[1]], -self.grad_x[q[0]][q[1]])
        
        l = np.array([q[0]-p[0], q[1]-p[1]], np.float)
        if 0 not in l:
            l *= SQRT_0_5
        
        dp_l = np.dot(dp, l)
        l_dq = np.dot(l, dq)
        if dp_l < 0:
            dp_l = -dp_l
            l_dq = -l_dq
        
        # 2/3pi * ...
        return 0.212206590789 * (np.arccos(dp_l)+np.arccos(l_dq))
    
    def _local_cost(self, p, q):
        """
        1. Calculate the Canny edges & gradient magnitude cost taking into account Euclidean distance
        2. Combine with gradient direction
        Assumption: p & q are neighbors
        """
        diagnol = q[0] == p[0] or q[1] == p[1]
        
        # c0, c1 and c2 are costs from Canny operator, gradient magnitude and gradient direction respectively
        if diagnol:
            c0 = self.cost_edges[p[0]][p[1]]-SQRT_0_5*(self.cost_edges[p[0]][p[1]]-self.cost_edges[q[0]][q[1]])
            c1 = self.cost_grad_mag[p[0]][p[1]]-SQRT_0_5*(self.cost_grad_mag[p[0]][p[1]]-self.cost_grad_mag[q[0]][q[1]])
            c2 = SQRT_0_5 * self._get_grad_direction_cost(p, q)
        else:
            c0 = self.cost_edges[q[0]][q[1]]
            c1 = self.cost_grad_mag[q[0]][q[1]]
            c2 = self._get_grad_direction_cost(p, q)
        
        if np.isnan(c2):
            c2 = 0.0
        
        w0, w1, w2 = self.weight
        cost_pq = w0*c0 + w1*c1 + w2*c2
        
        return cost_pq * cost_pq

    def get_path_matrix(self, seed):
        """
        Get the back tracking matrix of the whole image from the cost matrix
        """
        neighbors = []          # 8 neighbors of the pixel being processed
        processed = set()       # Processed point
        cost = {seed: 0.0}      # Accumulated cost, initialized with seed to itself
        paths = {}

        self.n_processed = 0
        
        while cost:
            # Expand the minimum cost point
            p = min(cost, key=cost.get)
            neighbors = self._get_neighbors(p)
            processed.add(p)

            # Record accumulated costs and back tracking point for newly expanded points
            for q in [x for x in neighbors if x not in processed]:
                temp_cost = cost[p] + self._local_cost(p, q)
                if q in cost:
                    if temp_cost < cost[q]:
                        cost.pop(q)
                else:
                    cost[q] = temp_cost
                    processed.add(q)
                    paths[q] = p
            
            # Pop traversed points
            cost.pop(p)
            
            self.n_processed += 1
        
        return paths

livewire.py

livewire.py

GUI-Teil

 from __future__ import division
import time
import cv2
from PyQt4 import QtGui, QtCore
from threading import Thread
from livewire import Livewire

class ImageWin(QtGui.QWidget):
    def __init__(self):
        super(ImageWin, self).__init__()
        self.setupUi()
        self.active = False
        self.seed_enabled = True
        self.seed = None
        self.path_map = {}
        self.path = []
        
    def setupUi(self):
        self.hbox = QtGui.QVBoxLayout(self)
        
        # Load and initialize image
        self.image_path = &#39;&#39;
        while self.image_path == &#39;&#39;:
            self.image_path = QtGui.QFileDialog.getOpenFileName(self, &#39;&#39;, &#39;&#39;, &#39;(*.bmp *.jpg *.png)&#39;)
        self.image = QtGui.QPixmap(self.image_path)
        self.cv2_image = cv2.imread(str(self.image_path))
        self.lw = Livewire(self.cv2_image)
        self.w, self.h = self.image.width(), self.image.height()
        
        self.canvas = QtGui.QLabel(self)
        self.canvas.setMouseTracking(True)
        self.canvas.setPixmap(self.image)
        
        self.status_bar = QtGui.QStatusBar(self)
        self.status_bar.showMessage(&#39;Left click to set a seed&#39;)
        
        self.hbox.addWidget(self.canvas)
        self.hbox.addWidget(self.status_bar)
        self.setLayout(self.hbox)
    
    def mousePressEvent(self, event):            
        if self.seed_enabled:
            pos = event.pos()
            x, y = pos.x()-self.canvas.x(), pos.y()-self.canvas.y()
            
            if x < 0:
                x = 0
            if x >= self.w:
                x = self.w - 1
            if y < 0:
                y = 0
            if y >= self.h:
                y = self.h - 1

            # Get the mouse cursor position
            p = y, x
            seed = self.seed
            
            # Export bitmap
            if event.buttons() == QtCore.Qt.MidButton:
                filepath = QtGui.QFileDialog.getSaveFileName(self, &#39;Save image audio to&#39;, &#39;&#39;, &#39;*.bmp\n*.jpg\n*.png&#39;)
                image = self.image.copy()
                
                draw = QtGui.QPainter()
                draw.begin(image)
                draw.setPen(QtCore.Qt.blue)
                if self.path_map:
                    while p != seed:
                        draw.drawPoint(p[1], p[0])
                        for q in self.lw._get_neighbors(p):
                            draw.drawPoint(q[1], q[0])
                        p = self.path_map[p]
                if self.path:
                    draw.setPen(QtCore.Qt.green)
                    for p in self.path:
                        draw.drawPoint(p[1], p[0])
                        for q in self.lw._get_neighbors(p):
                            draw.drawPoint(q[1], q[0])
                draw.end()
                
                image.save(filepath, quality=100)
            
            else:
                self.seed = p
                
                if self.path_map:
                    while p != seed:
                        p = self.path_map[p]
                        self.path.append(p)
                
                # Calculate path map
                if event.buttons() == QtCore.Qt.LeftButton:
                    Thread(target=self._cal_path_matrix).start()
                    Thread(target=self._update_path_map_progress).start()
                
                # Finish current task and reset
                elif event.buttons() == QtCore.Qt.RightButton:
                    self.path_map = {}
                    self.status_bar.showMessage(&#39;Left click to set a seed&#39;)
                    self.active = False
    
    def mouseMoveEvent(self, event):
        if self.active and event.buttons() == QtCore.Qt.NoButton:
            pos = event.pos()
            x, y = pos.x()-self.canvas.x(), pos.y()-self.canvas.y()

            if x < 0 or x >= self.w or y < 0 or y >= self.h:
                pass
            else:
                # Draw livewire
                p = y, x
                path = []
                while p != self.seed:
                    p = self.path_map[p]
                    path.append(p)
                
                image = self.image.copy()
                draw = QtGui.QPainter()
                draw.begin(image)
                draw.setPen(QtCore.Qt.blue)
                for p in path:
                    draw.drawPoint(p[1], p[0])
                if self.path:
                    draw.setPen(QtCore.Qt.green)
                    for p in self.path:
                        draw.drawPoint(p[1], p[0])
                draw.end()
                self.canvas.setPixmap(image)
    
    def _cal_path_matrix(self):
        self.seed_enabled = False
        self.active = False
        self.status_bar.showMessage(&#39;Calculating path map...&#39;)
        path_matrix = self.lw.get_path_matrix(self.seed)
        self.status_bar.showMessage(r&#39;Left: new seed / Right: finish&#39;)
        self.seed_enabled = True
        self.active = True
        
        self.path_map = path_matrix
    
    def _update_path_map_progress(self):
        while not self.seed_enabled:
            time.sleep(0.1)
            message = &#39;Calculating path map... {:.1f}%&#39;.format(self.lw.n_processed/self.lw.n_pixs*100.0)
            self.status_bar.showMessage(message)
        self.status_bar.showMessage(r&#39;Left: new seed / Right: finish&#39;)

gui.py

gui.py

Hauptfunktion

import sys
from PyQt4 import QtGui
from gui import ImageWin

def main():
    app = QtGui.QApplication(sys.argv)
    window = ImageWin()
    window.setMouseTracking(True)
    window.setWindowTitle('Livewire Demo')
    window.show()
    window.setFixedSize(window.size())
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()    

main.py

Hauptfunktion. py

wurde mühsam auf Github (Portal) hochgeladen, willkommen beim Fork.

Verbesserungen der Effizienz

Da der Prototyp dieses Codes nur für die Python-Bewertung und -Verifizierung vor der Entwicklung in C++ dient, wird die Effizienz und die Ausführungsgeschwindigkeit überhaupt nicht berücksichtigt ist völlig inakzeptabel, im Grunde kann ich das 400x400-Bild nicht ausstehen ... Was die Effizienzverbesserung basierend auf der Python-Version betrifft, habe ich nicht sorgfältig darüber nachgedacht, aber es scheint, dass es ein paar offensichtliche Stellen gibt:

1) Nehmen Sie die aktuelle Pixeloperation mit minimalen Kosten heraus

p = min(cost, key=cost.get)

Obwohl das Schreiben Spaß macht , es ist offensichtlich nicht möglich, mindestens einen Mindesthaufen zu verwenden. Da ich dict verwendet habe, um sowohl die zu verarbeitenden Pixel als auch die Kosten darzustellen, und zu faul war, darüber nachzudenken, wie ich es mit Pythons Heapq kombinieren könnte, habe ich einfach das einfache und problemlose min() verwendet.

2) Berechnung der Gradientenrichtung

Die Berechnung trigonometrischer Funktionen sollte so weit wie möglich vermieden werden. Im Originalartikel wird möglicherweise auch der Wertebereich auf >π erweitert Das Element hat ein geringes Gewicht, daher ist es meiner Meinung nach in Ordnung, einfach ein Skalarprodukt der Gradientenrichtungsvektoren der beiden Pixel zu erstellen und das Ergebnis dann zu normalisieren. Selbst wenn Sie Arccos verwenden möchten, können Sie erwägen, eine Näherung durch eine Nachschlagetabelle zu schreiben. Das Letzte, was ich sagen möchte, ist natürlich, dass ich persönlich der Meinung bin, dass ein direkter adaptiver Spilne- oder sogar Dreipunkt-Mittelwertglättungs- und Rauschunterdrückungseffekt wirklich nicht notwendig ist.

3) Berechnen Sie die Position benachbarter Pixel

Wenn zwei Pixel benachbart sind, fallen auch die 8 benachbarten Pixel um sie herum zusammen. Meine Methode ist relativ primitiv und kann die Rate direkt ohne Modularisierung berechnen.

4) Ersetzen Sie einige Datenstrukturen

Zum Beispiel gibt die Pfadkarte im Wesentlichen das vorherige Pixel auf dem kürzesten Pfad für jedes Pixel an, bei dem es sich um eine Matrix handelt. Tatsächlich können Sie stattdessen die Verwendung einer linearen Datenstruktur in Betracht ziehen, aber wenn Sie dies tun, erfolgt dies im Allgemeinen im C/C++-Code.

5) numpy

Meiner Meinung nach wirkt sich die Reihenfolge des Aufrufs von numpy auch auf die Effizienz aus. Kontinuierliche Aufrufe der integrierten Methoden von numpy scheinen jedoch insgesamt eine Verbesserung der Effizienz zu bewirken Auch hier gilt: Wenn es in der tatsächlichen Anwendung diesen Punkt erreicht, sollte es auch in die Kategorie des C/C++-Codes fallen.

6) Verbesserungen auf Algorithmusebene

Dieser Bereich wurde nicht eingehend untersucht. Der erste Eindruck ist, dass es in praktischen Anwendungen nicht notwendig ist, das gesamte Bild zu Beginn zu berechnen. Einige Blockunterteilungen können basierend auf der Startposition erfolgen, die Maus selbst hinterlässt auch eine Flugbahn, und Sie können auch eine heuristische Suche nur in Richtung der Mausflugbahn in Betracht ziehen. Darüber hinaus können Sie bei der Berechnung des Pfades ähnliche Ideen wie bei der Bildpyramide in Betracht ziehen. Es ist nicht erforderlich, den Pfad gleich zu Beginn in voller Auflösung zu suchen. Da die Projekte, die ich später durchführte, diesen Algorithmus nicht verwendeten, habe ich ihn nicht weiter studiert. Obwohl ich sehr neugierig war, gab es tatsächlich viele vorgefertigte Codes, wie zum Beispiel GIMP, aber ich hatte nicht die Energie dazu Schau es dir an.

Weitere Verbesserungen

Obwohl noch keine davon umgesetzt wurde, werde ich sie kurz vorstellen und auf die praktischen Verbesserungen eingehen.

Pfadkühlung

Jeder, der Photoshop und GIMP Magnetic Lasso verwendet hat, weiß, dass sich das Bild während der Bewegung automatisch bewegt, auch wenn es nicht mit der Maus angeklickt wird. Generieren Sie einige Punkte, die die Flugbahn des Ausschnitts festlegen. Diese Punkte sind tatsächlich neue Startpunkte. Diese Methode zum automatischen Generieren neuer Startpunkte wird als Pfadkühlung bezeichnet. Die Grundidee dieser Methode ist wie folgt: Wenn sich die Maus bewegt und ein Pfad für einen bestimmten Zeitraum fixiert bleibt, wird der Punkt in diesem Pfad, der am weitesten vom Startpunkt entfernt ist, als neuer Startwert festgelegt Dahinter verbirgt sich die Idee der Planung, Bellmans Optimalität. Der Name ist auch ziemlich anschaulich, Pfadkühlung.

Interaktives dynamisches Training

Photoshop中磁力套索的一种简陋实现(基于Python)

Bei der einfachen Suche nach kürzesten Pfaden entsprechen die gefundenen Kanten häufig nicht Ihren Wünschen. Im Bild oben ist die grüne Linie die im vorherigen Abschnitt extrahierte Kante und die blaue Linie die gerade extrahierte Kante. Im Bild links möchten wir den Rand von Lenas Hut außerhalb des Spiegels extrahieren. Da der Rand von Lenas Hut im Spiegel jedoch geringere Kosten verursacht, wird das tatsächlich extrahierte blaue Liniensegment rechts eingefügt im Bild rechts. Die Idee des interaktiven dynamischen Trainings besteht also darin, das grüne Liniensegment als korrekt extrahierte Kante zu betrachten und dann das grüne Liniensegment als Trainingsdaten zu verwenden, um einen Korrekturwert zur Kostenfunktion der aktuellen Kantenextraktion hinzuzufügen.

Die in

[1] verwendete Methode besteht darin, das Histogramm der Gradientenintensität der Punkte am Rand des vorherigen Segments zu zählen und dann die Pixel im aktuellen Bild entsprechend der Häufigkeit des Auftretens des Gradienten zu gewichten. Wenn beispielsweise die Gradientenintensität, die allen Pixeln im grünen Liniensegment entspricht, zwischen 50 und 100 liegt, können 50 bis 100 in 5 Bins in Einheiten von 10 unterteilt werden, und die Häufigkeit des Auftretens in jedem Bin kann gezählt werden ist ein Histogramm und gewichtet dann linear die aktuell erkannte Gradientenintensität. Wenn beispielsweise höchstens 10 entsprechende Pixel im Bereich von 50 bis 60 vorhanden sind, wird 10 als Maximalwert angesehen und die Pixel mit aktuell erkannten Gradientenstärken zwischen 50 und 60 werden mit einem Koeffizienten von 1,0 multipliziert Trainingsdaten liegen zwischen 70 und 80, dann beträgt der Kostengewichtungskoeffizient 5/10 = 0,5, dann werden alle Pixel mit aktuell erkannten Gradientenstärken zwischen 70 und 80 mit dem Koeffizienten 0,5 multipliziert; Trainingsdaten, also Die Kosten betragen zusätzlich 0/10 = 0 und der Gewichtungskoeffizient ist 0, sodass selbst wenn eine stärkere Kante erkannt wird, diese nicht von der vorherigen Kante abweicht. Das ist die Grundidee, die eigentliche Umsetzung ist natürlich nicht so einfach. Zusätzlich zu den Pixeln am Rand müssen auch die beiden Pixel links und rechts am vertikalen Rand berücksichtigt werden, um das Kantenmuster sicherzustellen. Wenn sich die Maus außerdem immer weiter von der Trainingskante entfernt, kann das Muster der erkannten Kante anders aussehen, sodass das Training einen kontraproduktiven Effekt haben kann. Daher muss der Umfang dieses Trainings auch den Abstand zwischen berücksichtigen Die Maus und der Seed-Punkt müssen schließlich etwas geglättet und entrauscht werden. Die Details sind in [1] erwähnt (es scheint, dass es damals noch kein SIFT gab). gehen Sie ins Detail.

Korrektur der Startpunktposition (Cursor-Fang)

Dieser Algorithmus kann zwar automatisch den Pfad finden, der der Kante zwischen dem Startpunkt und der Maus am nächsten liegt, jedoch menschlich Meine Hände zittern oft, sodass die Saatpunkte möglicherweise nicht richtig am Rand sitzen. Nachdem der Benutzer die Startpunktposition festgelegt hat, kann daher automatisch das Pixel mit den niedrigsten Kosten innerhalb eines kleinen Bereichs um seine Koordinaten, z. B. eines 7x7-Bereichs, als tatsächliche Startpunktposition gesucht werden. Dieser Vorgang wird als Cursorfang bezeichnet.

Weitere Informationen zu einer einfachen Implementierung des magnetischen Lassos in Photoshop (basierend auf Python) finden Sie auf der chinesischen PHP-Website für verwandte Artikel!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn