Photoshop을 자주 사용하는 사람들이라면 사람이 안내하는 컷아웃 보조 도구인 자석 올가미 기능에 익숙할 것입니다. R&D 분야에서는 일반적으로 이렇게 부르지 않습니다. 이 에지 추출 방법을 보통 지능형 가위(Intelligent Scissors) 또는 라이브와이어(Livewire)라고 부릅니다.
기본 알고리즘관련 알고리즘에 대해 깊이 연구해본 적은 없지만, 이런 종류의 알고리즘 적용에 있어 가장 영향력 있는 알고리즘은 이 글의 주요 참고자료이기도 한 [1]에서 나온 것이다. 기본 아이디어는 그림을 무방향 그래프로 간주하고 인접한 픽셀 사이에서 로컬 비용을 계산할 수 있다는 것이다. 이제 최단 경로 문제가 해결되었으므로 다음 단계는 추출해야 할 Edge인 Dijkstra 알고리즘을 기반으로 경로를 생성하는 것입니다. 관련된 두 가지 주요 알고리즘이 있습니다: 1) 인접 픽셀의 비용 계산 2) 최단 경로 알고리즘.
에지 감지인접 픽셀 비용을 계산하는 궁극적인 목적은 모서리를 찾는 것이므로 본질은 모서리 감지입니다. 기본 아이디어는 다양한 수단을 통해 에지를 검출하고, 검출된 강도를 기준으로 가중치를 비용으로 계산하는 것입니다. 최단 경로의 관점에서 보면 가장자리가 명확할수록 비용 가치는 작아집니다. [1]의 제안은 가중치 부여를 위해 1) 가장자리 감지 연산자, 2) 기울기 강도(Gradient Magnitude), 3) 기울기 방향(Gradient Direction)을 사용하는 것입니다. 이 기사의 방법은 [1]과 약간 다릅니다. Laplacian Zero-Crossing Operator 대신 OpenCV의 Canny 연산자를 사용합니다. 표현식은 다음과 같습니다.
[lleft( p,q right)={{w}_{E}}{{f}_{E}}left( q right)+{{w}_ {G }}{{f}_{G}}왼쪽( q 오른쪽)+{{w}_{D}}{{f}_{D}}왼쪽( p,q 오른쪽)]
캐니 연산자기본 아이디어는 먼저 그라데이션 정보를 기반으로 연결된 여러 픽셀을 감지한 다음 연결된 각 픽셀의 최대값만 취하여 연결하는 것입니다. 초기 모서리(Edges)를 얻으려면 주변 부분을 0으로 설정합니다. 이 프로세스를 Non-Maximum Suppression이라고 합니다. 그런 다음 2-임계값 방법을 사용하여 감지된 초기 가장자리를 Strong, Weak 및 None의 세 가지 수준으로 나눕니다. 이름에서 알 수 있듯이 Strong은 확실히 가장자리이고 None은 삭제된 다음 Strong에 연결된 가장자리가 Weak에서 선택됩니다. 에지가 유지되면서 최종 결과가 얻어집니다. 이 프로세스를 히스테리시스 임계값이라고 합니다. 이 알고리즘은 너무 고전적이어서 Google이 많은 세부 정보를 제공하는 즉시 자세한 내용을 다루지 않겠습니다. 공식은 다음과 같습니다:
[{{f}_{E}}left( q right)=left{ start{matrix}
0;text{ if }qtext{가 가장자리에 있는 경우} \ 1;text{ if }qtext{가 모서리에 있지 않은 경우} \
end{matrix} right.]
실제로 가중치 계산은 최대 기울기에서 다소 반복적입니다. 왜냐하면 최대 그라디언트 방향에서 발견된 경로는 기본적으로 가장자리이기 때문입니다. 이 항목의 역할에 대한 나의 이해는 주로 1) 큰 그라디언트가 있는 영역에서 명백한 가장자리로부터의 이탈을 방지하는 것입니다. 2) 추출된 가장자리의 연속성을 보장합니다. , 어느 정도 부드럽습니다.
그라디언트 강도는 단지 x 및 y 방향의 그래디언트 값의 제곱이 추가된 것입니다. 제곱근의 공식은 다음과 같습니다. :
[{{I}_{G}}left( q right)=sqrt{{{I}_{x}}left( q right)+ {{I}_{y}}left ( q right)}]
비용이 필요하므로 반전 및 정규화:
[{{f}_{G}}left( q right)=1-frac{ {{I}_{G}}left( q right)}{max left( {{I}_{G}} right)}]
그라디언트 방향이 항목은 실제로 스무딩 항목으로, 추출된 가장자리가 노이즈의 영향을 피할 수 있도록 급격하게 변화하는 가장자리에 상대적으로 높은 비용을 할당합니다. 구체적인 수식은 다음과 같습니다.
[{{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)]
그 중
[{{ d}_{p}}왼쪽( p,q 오른쪽)=왼쪽 각도 {{d}_{bot }}왼쪽( p 오른쪽),{{l}_{D}}왼쪽( p,q 오른쪽 ) 오른쪽 각도 ]
[{{d}_{q}}왼쪽( p,q 오른쪽)=왼쪽 각도 {{l}_{D}}왼쪽( p,q 오른쪽),{{d}_ {bot }} left( q right) rightrangle ]
[{{l}_{D}}left( p,q right)=left{ 시작{행렬}
q-p;text{ if } leftlangle {{d }_{bot }}left( p 오른쪽),q-p rightrangle ge 0 \p-q;text{ if }leftlangle {{d}_{bot }}left( p 오른쪽),q-p rightrangle 6e67780bc5f2789da7111cee3b4ea5ec3)이어야 하므로 바로 위가 최소 비용의 올바른 방향입니다.
최단 경로 탐색
자석 올가미에서는 일반적으로 한 점을 먼저 클릭한 후 마우스를 처음 클릭한 점 A와 그 사이로 이동하는 것이 일반적입니다. 여기서는 처음에 클릭한 픽셀 포인트를 시드 포인트(시드)로 정의합니다. 자기 올가미는 이전 부분에서 언급한 가장자리 관련 비용을 고려하여 실제로 시드 포인트를 찾습니다. 현재 마우스의 최단 경로입니다. 아래 그림과 같이 빨간색이 시드 포인트이고, 마우스를 움직이면 가장자리에 가장 가까운 시드 포인트와 마우스 좌표 사이의 연결이 실시간으로 표시되는 이유도 바로 이것입니다. 라이브와이어라고 합니다.
최단 경로를 달성하는 방법은 다양합니다. 일반적으로 여기서 소개하는 것은 Dijkstra의 알고리즘을 기반으로 한 구현입니다. , 시드 포인트가 주어지면 마지막으로 Dijkstra의 알고리즘이 실행되어 이미지의 모든 픽셀을 탐색하여 각 픽셀에서 시드 포인트까지의 최단 경로를 얻습니다. 아래 그림을 예로 들어 비용 매트릭스에서 Dijkstra 알고리즘을 사용하여 각 요소를 순회한 후 각 요소는 인접한 요소를 가리키므로 모든 픽셀이 오른쪽 상단 모서리와 같은 시드에 대한 경로를 찾을 수 있습니다. 42와 39에 해당하는 픽셀은 화살표를 따라 0으로 향합니다.
알고리즘은 다음과 같습니다.
输入: 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; // 加入待处理以试图寻找更短的路径
그림과 같이 순회 프로세스는 비용이 가장 낮은 영역에 우선 순위를 부여합니다. 아래:
모든 픽셀에 해당하는 시드 픽셀까지의 최단 경로를 찾은 후 마우스를 움직일 때 시드까지의 최단 경로를 그리면 됩니다.
Python 구현
알고리즘 부분은 OpenCV의 Canny 함수와 Sobel 함수(그라데이션용)를 직접 호출하고, RGB 처리도 매우 simple. , 그라디언트의 최대값으로 직접 근사화됩니다. 또한 게으름 때문에 비용 맵과 경로 맵 모두 사전(dict)을 직접 사용하는 반면 확장 픽셀을 기록하는 경우 세트(set)를 직접 사용합니다. GUI 부분은 QThread 사용법을 모르기 때문에 Python의 스레딩을 사용합니다. 이미지에만 대화형 영역과 상태 표시줄 프롬프트가 표시됩니다. 시드 포인트를 설정하려면 마우스 오른쪽 버튼을 클릭하면 추출된 가장자리가 표시됩니다. 녹색 선이고 추출되는 가장자리는 파란색 선입니다.
코드
알고리즘 부분
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 부분
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 = '' while self.image_path == '': self.image_path = QtGui.QFileDialog.getOpenFileName(self, '', '', '(*.bmp *.jpg *.png)') 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('Left click to set a seed') 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, 'Save image audio to', '', '*.bmp\n*.jpg\n*.png') 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('Left click to set a seed') 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('Calculating path map...') path_matrix = self.lw.get_path_matrix(self.seed) self.status_bar.showMessage(r'Left: new seed / Right: finish') 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 = 'Calculating path map... {:.1f}%'.format(self.lw.n_processed/self.lw.n_pixs*100.0) self.status_bar.showMessage(message) self.status_bar.showMessage(r'Left: new seed / Right: finish') gui.py
gui.py
주요 기능
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
main.py
힘들게 Github(포털)에 업로드 되었네요, 환영합니다.
효율성 향상
이 코드의 프로토타입은 C++로 개발하기 전 Python 평가 및 검증만을 위한 것이기 때문에 효율성은 전혀 고려하지 않고, 실행 속도도 는 전혀 용납할 수 없습니다. 기본적으로 400x400 이미지를 견딜 수 없습니다... Python 버전을 기반으로 한 효율성 향상에 대해서는 신중하게 생각하지 않았지만 몇 가지 분명한 곳이 있는 것 같습니다.
1) 현재 최소 비용 픽셀 연산을 빼냅니다
p = min(cost, key=cost.get)
글쓰는 것도 재미있지만 , 분명히 불가능합니다. 적어도 최소 힙을 사용해야 합니다. 처리할 픽셀과 비용을 모두 표현하기 위해 dict를 사용했고, 이를 Python의 heapq와 결합하는 방법을 생각하기에는 너무 게으른 탓에 조잡하고 문제가 없는 min()만 사용했습니다.
2) 기울기 방향 계산
삼각함수 계산은 최대한 피해야 합니다. 게다가 원문에서는 값 범위를 >π로 확장하는 것일 수도 있으므로 실제로는 이렇게도 사용됩니다. 항목의 가중치가 작으므로 두 픽셀의 그래디언트 방향 벡터의 내적을 수행한 다음 결과를 정규화해도 괜찮습니다. arccos를 사용하려는 경우에도 조회 테이블 근사값 작성을 고려할 수 있습니다. 물론 마지막으로 말하고 싶은 것은 이것이 실제로 필요하지 않다고 개인적으로 느낀다는 것입니다. Direct Adaptive Spilne이나 심지어 3점 평균 스무딩 및 노이즈 제거 효과도 좋을 것입니다.
3) 인접 픽셀의 위치 계산
두 픽셀이 인접하면 그 주변의 인접한 8개 픽셀도 일치합니다. 내 방법은 상대적으로 원시적이며 모듈화 없이 직접 속도를 계산할 수 있습니다.
4) 일부 데이터 구조 교체
예를 들어 경로 맵은 기본적으로 각 픽셀의 최단 경로에 있는 이전 픽셀, 즉 행렬을 제공합니다. 실제로 선형 데이터 구조를 대신 사용하는 것을 고려할 수 있지만 이렇게 하면 일반적으로 C/C++ 코드가 됩니다.
5) numpy
제 생각에는 numpy를 호출하는 순서도 효율성에 영향을 미칠 것 같습니다. numpy에 내장된 메소드를 계속 호출하면 전반적인 효율성이 향상되는 것 같습니다. 다시 말하지만, 실제 응용에서 이 정도 수준까지 도달한다면, 역시 C/C++ 코드의 범주에 속해야 합니다.
6) 알고리즘 수준의 개선
이 부분은 아직 깊이 연구되지 않았습니다. 첫인상은 실제 응용에서는 처음부터 전체 이미지를 계산할 필요가 없다는 것입니다. 일부 블록 분할은 시드 위치를 기반으로 수행될 수 있으며 마우스 자체도 궤적을 남기고 마우스 궤적 방향으로만 휴리스틱 검색을 수행하는 것을 고려할 수도 있습니다. 또한 경로를 계산할 때 Image Pyramid와 유사한 아이디어를 차용하는 것도 고려할 수 있습니다. 처음부터 전체 해상도로 경로를 검색할 필요는 없습니다. 나중에 했던 프로젝트에서는 이 알고리즘을 사용하지 않았기 때문에 계속 연구하지는 않았습니다. 무척 궁금하긴 했지만 실제로 GIMP 등 기성 코드가 많이 있기는 했지만 살펴볼 여력이 없었습니다. 그것.
더 많은 개선사항
아무것도 이루어지지 않았지만 간략하게 소개하고 실질적인 개선점을 고려하겠습니다.
경로 냉각
Photoshop과 GIMP Magnetic Lasso를 사용해 본 사람이라면 이미지를 마우스로 클릭하지 않아도 이동 중에 자동으로 움직인다는 사실을 알고 계실 것입니다. 컷아웃 궤적을 수정하는 일부 포인트를 생성합니다. 이러한 포인트는 실제로 새로운 시드 포인트를 사용하는 동안 자동으로 생성하는 방법을 경로 냉각이라고 합니다. 이 방법의 기본 아이디어는 다음과 같습니다. 마우스가 움직일 때 경로가 일정 시간 동안 고정되어 있으면 이 경로의 시드에서 가장 먼 지점이 새로운 시드로 설정됩니다. 그 뒤에는 역동적인 기획 아이디어, 벨만 최적성이 숨겨져 있습니다. 이름도 꽤 생생하고, 경로냉각이다.
Interactive Dynamic Training
간단한 최단 경로 검색을 사용할 때 종종 발견되는 가장자리가 원하는 것이 아닙니다. 위 그림에서 녹색 선은 이전 구간에서 추출된 Edge이고, 파란색 선은 현재 추출 중인 Edge입니다. 왼쪽 그림에서는 거울 바깥쪽의 레나 모자 가장자리가 추출하고자 하는 부분이지만, 거울 속 레나 모자 가장자리의 코스트가 더 낮기 때문에 실제 추출된 파란색 선분을 오른쪽에 붙여넣습니다. 오른쪽 그림에서. 그래서 Interactive Dynamic Training의 아이디어는 녹색 선분을 올바르게 추출된 에지로 간주하고, 녹색 선분을 훈련 데이터로 사용하여 현재 에지 추출의 비용 함수에 수정 값을 추가하는 것입니다.
[1]에서 사용된 방법은 이전 세그먼트의 가장자리에 있는 점의 기울기 강도 히스토그램을 계산한 다음 기울기 발생 빈도에 따라 현재 이미지의 픽셀에 가중치를 부여하는 것입니다. 예를 들어 녹색 선분의 모든 픽셀에 해당하는 기울기 강도가 50에서 100 사이이면 50에서 100을 10개 단위로 5개의 빈으로 나누고 각 빈에서 발생 빈도를 계산할 수 있습니다. 는 히스토그램이고 현재 감지된 경사 강도에 선형적으로 가중치를 부여합니다. 예를 들어, 50~60 범위에 해당 픽셀이 최대 10개 있는 경우 10을 최대값으로 간주하고 현재 감지된 기울기 강도가 50~60인 픽셀에 계수 1.0을 곱합니다. 학습 데이터는 70입니다. 80 사이에 5가 있고 비용 가중치 계수는 5/10=0.5입니다. 그러면 현재 감지된 기울기 강도가 70에서 80 사이인 모든 픽셀에 100보다 큰 픽셀이 없으면 계수 0.5가 곱해집니다. 훈련 데이터이므로 비용은 추가로 0/10=0, 가중치 계수는 0이므로 더 강한 에지가 검출되더라도 이전 에지에서 벗어나지 않습니다. 물론 이것이 기본 아이디어이지만 실제 구현은 그리 간단하지 않습니다. 가장자리의 픽셀 외에도 수직 가장자리의 왼쪽과 오른쪽 두 픽셀도 고려해야하므로 가장자리 패턴이 보장됩니다. 또한 마우스가 훈련 가장자리에서 점점 멀어질수록 감지된 가장자리의 패턴이 다르게 나타날 수 있으므로 훈련이 역효과를 낼 수 있으므로 이 훈련의 범위에서도 가장자리 사이의 거리를 고려해야 합니다. 마지막으로 스무딩 및 노이즈 제거 처리가 필요합니다. 자세한 내용은 [1]에 언급되어 있습니다. (당시에는 SIFT가 없었던 것 같습니다.) 자세히 살펴보세요.
종점 위치 수정(커서 스냅)
이 알고리즘은 종자점과 마우스 사이의 가장자리에 가장 가까운 경로를 자동으로 찾을 수 있지만, 인간은 손이 자주 흔들리는 편이라 씨 포인트가 가장자리에 잘 안착되는 경우가 있어요. 따라서 사용자가 종자점 위치를 설정한 후 실제 종자점 위치로서 해당 좌표를 중심으로 작은 범위(예: 7x7 영역) 내에서 비용이 가장 낮은 픽셀을 자동으로 검색하는 과정을 커서 스냅이라고 합니다.
Photoshop(Python 기반)에서 자기 올가미의 간단한 구현에 대한 자세한 내용은 PHP 중국어 웹사이트의 관련 기사를 참고하세요!