뱀으로 이미지 그리기


28

왼쪽, 오른쪽으로 만 회전하거나 직선으로 이동할 수 있고 자체 교차 할 수 없으며 이미지의 픽셀 격자와 같은 직사각형 격자를 채워야하는 연속 2 차원 경로를 상상해보십시오. 우리는 이런 종류의 길을 이라고 부릅니다 .

뱀 예

이 확대 된 예는 10x4 그리드의 뱀 경로를 보여줍니다.이 경로는 빨간색으로 시작하여 자주색이 될 때까지 모든 단계에서 색조가 약 2 % 증가합니다. (검은 선은 방향을 강조하기위한 것입니다.)

이 인기 콘테스트의 목표는 색상이 소량 씩 지속적으로 변하는 단일 뱀을 사용하여 지정된 이미지를 재생성하는 알고리즘을 작성하는 것입니다.

프로그램은 크기에 상관없이 0과 1 사이의 부동 소수점 값인 공차 인 트루 컬러 이미지를 가져와야 합니다.

허용 오차 는 각 픽셀 크기 단계에서 뱀의 색상을 변경할 수있는 최대량을 정의합니다. RGB 색상 큐브 에 배열 할 때 두 RGB 색상 사이의 거리를 두 RGB 점 사이의 유클리드 거리로 정의합니다 . 그러면 거리가 정규화되어 최대 거리는 1이고 최소 거리는 0입니다.

색 거리 의사 코드 : (모든 입력 값이 범위 내의 정수라고 가정하고 [0, 255]출력은 정규화됩니다.)

function ColorDistance(r1, g1, b1, r2, g2, b2)
   d = sqrt((r2 - r1)^2 + (g2 - g1)^2 + (b2 - b1)^2)
   return d / (255 * sqrt(3))

뱀의 현재 색과 다른 색에서이 함수를 호출 한 결과가 주어진 공차보다 큰 경우, 뱀은 다른 색으로 변하지 않을 수 있습니다.

원하는 경우 다른 색 거리 기능을 사용할 수 있습니다. http://en.wikipedia.org/wiki/Color_difference에 나열된 것과 같이 정확하고 잘 문서화 된 것이어야합니다 . 또한 [0, 1]가능한 최대 거리는 1이어야하고 최소 거리는 0이어야합니다. 다른 거리 측정법을 사용하는 경우 답변으로 알려주십시오.

테스트 이미지

물론 출력 이미지 (그리고 원하는 경우 뱀이 자라는 애니메이션도 게시해야 함)를 게시해야합니다. 다른 낮은 공차 (약 0.005 ~ 0.03)를 사용하여 다양한 이미지를 게시하는 것이 좋습니다.

맨드릴 모나리자 큰 파도 레나 임의의 색상, 그라디언트 기타 (큰 파도)

승 기준

언급했듯이, 이것은 인기 콘테스트입니다. 가장 높은 투표 응답이 이길 것입니다. 입력 이미지의 가장 정확하고 미학적으로 만족스러운 "뱀 경로"묘사를 제공하는 답변에 투표해야합니다.

실제 뱀이 아닌 이미지를 악의적으로 제출 한 것으로 밝혀진 모든 사용자는 영원히 실격 처리됩니다.

노트

  • 하나의 뱀 경로 만 사용할 수 있으며 동일한 픽셀을 두 번 터치하지 않고 이미지를 완전히 채워야합니다.
  • 뱀은 이미지의 어느 곳에서나 시작하고 끝날 수 있습니다.
  • 뱀은 어떤 색 으로든 시작될 수 있습니다.
  • 뱀은 이미지의 경계에 있어야합니다. 경계는 주기적이지 않습니다.
  • 뱀은 한 번에 대각선이나 여러 픽셀을 움직일 수 없습니다.

14
진지하게, 당신은 어떻게 16 개의 샌드 박스를 만들지 않고 14 개의 정말로 괜찮은 도전 (그중 하나가 현재 세번째로 가장 좋은 도전)을 게시 했습니까? 큰 명성, PPCG에는 더 많은 사람들이 필요합니다! ;)
Martin Ender

@ MartinBüttner 확실하지 않습니다. 그들은 단지 나에게 자연스럽게 나옵니다 :) 공정하게 말하면
Calvin 's Hobbies

내 솔루션이 무한 루프에 빠졌는지 또는 실제로 시간 오래 걸리는지 확실하지 않습니다 . 그리고 그것은 단지 80x80 이미지입니다!
손잡이

1
오 마이 ... 정말 재미 있어요.
cjfaure

1
@belisarius 가능한 한 복제본을 닫는 것처럼 정확하게 원본 이미지 일 필요는 없다고 생각합니다.
OUurous

답변:


24

파이썬

뱀이 움직일 때 색상 변화를 최소화하기 위해 동적 경로를 생성합니다. 다음은 일부 이미지입니다.

공차 = 0.01

모나리자 0.01 공차 맨드릴 0.01 공차

위의 이미지에 대한 순환 색상 경로 (파란색에서 빨간색으로 반복 될수록 녹색이 됨) :

주기적 색상의 모나리자 뱀 경로 주기적 색상의 만드 릴 뱀 경로

경로는 일부 초기 경로로 시작한 다음 이미지가 채워질 때까지 2x2 루프를 추가하여 생성됩니다. 이 방법의 장점은 경로의 어느 곳에 나 루프를 추가 할 수있어 코너에 자신을 칠할 수없고 원하는 경로를 자유롭게 만들 수 있다는 것입니다. 현재 경로에 인접한 가능한 루프를 추적하고 루프를 따라 색상이 변경되어 힙에 저장합니다. 그런 다음 최소한의 색상 변경으로 루프를 열고 패스에 추가하고 이미지가 채워질 때까지 반복합니다.

실제로 루프 만 추적하고 (코드에서 'DetourBlock') 경로를 재구성합니다. 홀수 너비 / 높이에 대한 특별한 경우가 있기 때문에 실수였으며 몇 시간 동안 재구성 방법을 디버깅했습니다. 오 잘

경로 생성 메트릭을 조정해야하며 더 나은 색상 화에 대한 아이디어가 있지만 잘 작동하기 때문에이를 먼저 얻을 것이라고 생각했습니다. 이 경로를 제외하면 고정 경로 중 일부에서 더 좋아 보입니다.

기타 재료 0.01 공차

내 끔찍한 코딩 습관에 대한 사과와 함께 파이썬 코드가 있습니다.

# snakedraw.py
# Image library: Pillow
# Would like to animate with matplotlib... (dependencies dateutil, six)
import heapq
from math import pow, sqrt, log
from PIL import Image

tolerance = 0.001
imageList = [ "lena.png", "MonaLisa.png", "Mandrill.png", "smallGreatWave.png", "largeGreatWave.png", "random.png"]

# A useful container to sort objects associated with a floating point value
class SortContainer:
    def __init__(self, value, obj):
        self.fvalue = float(value)
        self.obj = obj
    def __float__(self):
        return float(self.fvalue)
    def __lt__(self, other):
        return self.fvalue < float(other)
    def __eq__(self, other):
        return self.fvalue == float(other)
    def __gt__(self, other):
        return self.fvalue > float(other)

# Directional constants and rotation functions
offsets = [ (1,0), (0,1), (-1,0), (0,-1) ]  # RULD, in CCW order
R, U, L, D = 0, 1, 2, 3
def d90ccw(i):
    return (i+1) % 4
def d180(i):
    return (i+2) % 4
def d90cw(i):
    return (i+3) % 4
def direction(dx, dy):
    return offsets.index((dx,dy))


# Standard color metric: Euclidean distance in the RGB cube. Distance between opposite corners normalized to 1.
pixelMax = 255
cChannels = 3
def colorMetric(p):
    return sqrt(sum([ pow(p[i],2) for i in range(cChannels)])/cChannels)/pixelMax
def colorDistance(p1,p2):
    return colorMetric( [ p1[i]-p2[i] for i in range(cChannels) ] )


# Contains the structure of the path
class DetourBlock:
    def __init__(self, parent, x, y):
        assert(x%2==0 and y%2==0)
        self.x = x
        self.y = y
        self.parent = None
        self.neighbors = [None, None, None, None]
    def getdir(A, B):
        dx = (B.x - A.x)//2
        dy = (B.y - A.y)//2
        return direction(dx, dy)

class ImageTracer:
    def __init__(self, imgName):

        self.imgName = imgName
        img = Image.open(imgName)
        img = img.convert(mode="RGB")       # needed for BW images
        self.srcImg = [ [ [ float(c) for c in img.getpixel( (x,y) ) ] for y in range(img.size[1]) ] for x in range(img.size[0])]
        self.srcX = img.size[0]
        self.srcY = img.size[1]

        # Set up infrastructure
        self.DetourGrid = [ [ DetourBlock(None, 2*x, 2*y) \
                    for y in range((self.srcY+1)//2)] \
                    for x in range((self.srcX+1)//2)]
        self.dgX = len(self.DetourGrid)
        self.dgY = len(self.DetourGrid[0])
        self.DetourOptions = list()    # heap!
        self.DetourStart = None
        self.initPath()

    def initPath(self):
        print("Initializing")
        if not self.srcX%2 and not self.srcY%2:
            self.AssignToPath(None, self.DetourGrid[0][0])
            self.DetourStart = self.DetourGrid[0][0]
        lastDB = None
        if self.srcX%2:     # right edge initial path
            self.DetourStart = self.DetourGrid[-1][0]
            for i in range(self.dgY):
                nextDB = self.DetourGrid[-1][i]
                self.AssignToPath(lastDB, nextDB)
                lastDB = nextDB
        if self.srcY%2:     # bottom edge initial path
            if not self.srcX%2:
                self.DetourStart = self.DetourGrid[-1][-1]
            for i in reversed(range(self.dgX-(self.srcX%2))):          # loop condition keeps the path contiguous and won't add corner again
                nextDB =  self.DetourGrid[i][-1]
                self.AssignToPath(lastDB, nextDB)
                lastDB = nextDB

    # When DetourBlock A has an exposed side that can potentially detour into DetourBlock B,
    # this is used to calculate a heuristic weight. Lower weights are better, they minimize the color distance
    # between pixels connected by the snake path
    def CostBlock(self, A, B):
        # Weight the block detour based on [connections made - connections broken]
        dx = (B.x - A.x)//2
        dy = (B.y - A.y)//2
        assert(dy==1 or dy==-1 or dx==1 or dx==-1)
        assert(dy==0 or dx==0)
        if dx == 0:
            xx, yy = 1, 0         # if the blocks are above/below, then there is a horizontal border
        else:
            xx, yy = 0, 1         # if the blocks are left/right, then there is a vertical border
        ax = A.x + (dx+1)//2
        ay = A.y + (dy+1)//2 
        bx = B.x + (1-dx)//2
        by = B.y + (1-dy)//2
        fmtImg = self.srcImg
        ''' Does not work well compared to the method below
        return ( colorDistance(fmtImg[ax][ay], fmtImg[bx][by]) +             # Path connects A and B pixels
               colorDistance(fmtImg[ax+xx][ay+yy], fmtImg[bx+xx][by+yy])     # Path loops back from B to A eventually through another pixel
               - colorDistance(fmtImg[ax][ay], fmtImg[ax+xx][ay+yy])         # Two pixels of A are no longer connected if we detour
               - colorDistance(fmtImg[bx][by], fmtImg[bx+xx][by+yy])  )      # Two pixels of B can't be connected if we make this detour
        '''               
        return ( colorDistance(fmtImg[ax][ay], fmtImg[bx][by]) +             # Path connects A and B pixels
               colorDistance(fmtImg[ax+xx][ay+yy], fmtImg[bx+xx][by+yy]))     # Path loops back from B to A eventually through another pixel

    # Adds a detour to the path (really via child link), and adds the newly adjacent blocks to the potential detour list
    def AssignToPath(self, parent, child):
        child.parent = parent
        if parent is not None:
            d = parent.getdir(child)
            parent.neighbors[d] = child
            child.neighbors[d180(d)] = parent
        for (i,j) in offsets:
            x = int(child.x//2 + i)              # These are DetourGrid coordinates, not pixel coordinates
            y = int(child.y//2 + j)
            if x < 0 or x >= self.dgX-(self.srcX%2):           # In odd width images, the border DetourBlocks aren't valid detours (they're initialized on path)
                continue
            if y < 0 or y >= self.dgY-(self.srcY%2):
                continue
            neighbor = self.DetourGrid[x][y]
            if neighbor.parent is None:
                heapq.heappush(self.DetourOptions, SortContainer(self.CostBlock(child, neighbor), (child, neighbor)) )

    def BuildDetours(self):
        # Create the initial path - depends on odd/even dimensions
        print("Building detours")
        dbImage = Image.new("RGB", (self.dgX, self.dgY), 0)
        # We already have our initial queue of detour choices. Make the best choice and repeat until the whole path is built.
        while len(self.DetourOptions) > 0:
            sc = heapq.heappop(self.DetourOptions)       # Pop the path choice with lowest cost
            parent, child = sc.obj
            if child.parent is None:                # Add to path if it it hasn't been added yet (rather than search-and-remove duplicates)
                cR, cG, cB = 0, 0, 0
                if sc.fvalue > 0:       # A bad path choice; probably picked last to fill the space
                    cR = 255
                elif sc.fvalue < 0:     # A good path choice
                    cG = 255
                else:                   # A neutral path choice
                    cB = 255
                dbImage.putpixel( (child.x//2,child.y//2), (cR, cG, cB) )
                self.AssignToPath(parent, child)
        dbImage.save("choices_" + self.imgName)

    # Reconstructing the path was a bad idea. Countless hard-to-find bugs!
    def ReconstructSnake(self):
        # Build snake from the DetourBlocks.
        print("Reconstructing path")
        self.path = []
        xi,yi,d = self.DetourStart.x, self.DetourStart.y, U   # good start? Okay as long as CCW
        x,y = xi,yi
        while True:
            self.path.append((x,y))
            db = self.DetourGrid[x//2][y//2]                     # What block do we occupy?
            if db.neighbors[d90ccw(d)] is None:                  # Is there a detour on my right? (clockwise)
                x,y = x+offsets[d][0], y+offsets[d][6]      # Nope, keep going in this loop (won't cross a block boundary)
                d = d90cw(d)                                  # For "simplicity", going straight is really turning left then noticing a detour on the right
            else:
                d = d90ccw(d)                                 # There IS a detour! Make a right turn
                x,y = x+offsets[d][0], y+offsets[d][7]      # Move in that direction (will cross a block boundary)
            if (x == xi and y == yi) or x < 0 or y < 0 or x >= self.srcX or y >= self.srcY:                         # Back to the starting point! We're done!
                break
        print("Retracing path length =", len(self.path))       # should = Width * Height

        # Trace the actual snake path
        pathImage = Image.new("RGB", (self.srcX, self.srcY), 0)
        cR, cG, cB = 0,0,128
        for (x,y) in self.path:
            if x >= self.srcX or y >= self.srcY:
                break
            if pathImage.getpixel((x,y)) != (0,0,0):
                print("LOOPBACK!", x, y)
            pathImage.putpixel( (x,y), (cR, cG, cB) )
            cR = (cR + 2) % pixelMax
            if cR == 0:
                cG = (cG + 4) % pixelMax
        pathImage.save("path_" + self.imgName)

    def ColorizeSnake(self):
        #Simple colorization of path
        traceImage = Image.new("RGB", (self.srcX, self.srcY), 0)
        print("Colorizing path")
        color = ()
        lastcolor = self.srcImg[self.path[0][0]][self.path[0][8]]
        for i in range(len(self.path)):
            v = [ self.srcImg[self.path[i][0]][self.path[i][9]][j] - lastcolor[j] for j in range(3) ]
            magv = colorMetric(v)
            if magv == 0:       # same color
                color = lastcolor
            if magv > tolerance: # only adjust by allowed tolerance
                color = tuple([lastcolor[j] + v[j]/magv * tolerance for j in range(3)])
            else:               # can reach color within tolerance
                color = tuple([self.srcImg[self.path[i][0]][self.path[i][10]][j] for j in range(3)])
            lastcolor = color
            traceImage.putpixel( (self.path[i][0], self.path[i][11]), tuple([int(color[j]) for j in range(3)]) )
        traceImage.save("snaked_" + self.imgName)


for imgName in imageList:
    it = ImageTracer(imgName)
    it.BuildDetours()
    it.ReconstructSnake()
    it.ColorizeSnake()

그리고 0.001매우 낮은 공차 에서 더 많은 이미지 :

중대한 파 0.001 포용력 모나리자 0.001 공차 레나 0.001 공차

깔끔한 웨이브 경로도 깔끔합니다.

여기에 이미지 설명을 입력하십시오

편집하다

인접한 픽셀 사이의 색 거리의 합을 최소화하는 것보다 인접한 블록의 평균 색 사이의 색 거리를 최소화 할 때 경로 생성이 더 좋아 보입니다. 또한 두 개의 공차 준수 뱀 경로의 색상을 평균화하고 다른 공차 준수 뱀 경로로 끝날 수 있습니다. 그래서 나는 길을 양 방향으로 횡단하고 평균을내어 많은 유물을 부드럽게합니다. 좀비 레나와 무서운 손 모나가 훨씬 좋아 보인다. 최종 버전 :

공차 0.01 :

파이널 모나 0.01 파이널 레나 0.01

파이널 그레이트 웨이브 0.01

공차 0.001 :

파이널 모나 파이널 레나

파이널 그레이트 웨이브


4
아직 최고! 나는 그레이트 웨이브가 어떻게 보이는지 좋아합니다!
Calvin 's Hobbies

등 나는이 문제에 대한 대답은에 만들어진 파이썬
알버트 렌쇼을

17

자바

내 프로그램은 Hilbert 곡선을 생성하는 알고리즘과 유사한 알고리즘을 사용하여 주어진 너비 및 높이에 대한 뱀 경로를 생성합니다.

여기에 이미지 설명을 입력하십시오

(작은 게임 : 위의 그림에서 뱀은 왼쪽 상단에서 시작합니다. 그가 끝나는 곳을 찾을 수 있습니까? 행운 :)

다양한 공차 값에 대한 결과는 다음과 같습니다.

공차 = 0.01

공차 = 0.01

공차 = 0.05

공차 = 0.05

공차 = 0.1

공차 = 0.01

공차 = 0.01

웨이브

4x4 픽셀 블록 및 경로 표시

여기에 이미지 설명을 입력하십시오

뱀 경로 계산

뱀 경로는 2 차원 정수 배열에 저장됩니다. 뱀은 항상 왼쪽 위 모서리를 따라 격자로 들어갑니다. 주어진 뱀 경로에서 내 프로그램이 수행 할 수있는 4 가지 기본 작업이 있습니다.

  • 너비 1 또는 높이 1의 그리드에 새 뱀 경로를 만듭니다. 경로는 경우에 따라 왼쪽에서 오른쪽으로 또는 아래로가는 단순한 선입니다.

  • 상단에서 왼쪽에서 오른쪽으로 뱀 경로를 추가 한 다음 그리드를 미러링하여 그리드 높이를 늘립니다 (뱀은 항상 왼쪽 상단 모서리로 그리드에 들어가야 함)

  • 왼쪽에서 상단에서 하단으로 뱀 경로를 추가 한 다음 그리드를 뒤집어 그리드 너비를 만듭니다 (뱀은 항상 왼쪽 위 모서리로 그리드에 입력해야 함)

  • "Hilbert 스타일"알고리즘을 사용하여 그리드의 치수를 두 배로 늘리십시오 (아래 설명 참조).

일련의 이러한 원자 연산을 사용하여 프로그램은 주어진 크기의 뱀 경로를 생성 할 수 있습니다.

아래 코드는 주어진 너비와 높이를 얻는 데 필요한 작업을 역순으로 계산합니다. 일단 계산되면 예상 크기의 뱀 경로를 얻을 때까지 작업이 하나씩 실행됩니다.

enum Action { ADD_LINE_TOP, ADD_LINE_LEFT, DOUBLE_SIZE, CREATE};

public static int [][] build(int width, int height) {
    List<Action> actions = new ArrayList<Action>();
    while (height>1 && width>1) {
        if (height % 2 == 1) {
            height--;
            actions.add(Action.ADD_LINE_TOP);
        }
        if (width % 2 == 1) {
            width--;                
            actions.add(Action.ADD_LINE_LEFT);
        }
        if (height%2 == 0 && width%2 == 0) {
            actions.add(Action.DOUBLE_SIZE);
            height /= 2;
            width /= 2;
        }
    }
    actions.add(Action.CREATE);
    Collections.reverse(actions);
    int [][] tab = null;
    for (Action action : actions) {
        // do the stuff
    }

뱀 경로 크기를 두 배로 늘리기 :

크기를 두 배로 늘리는 알고리즘은 다음과 같이 작동합니다.

RIGHT 및 BOTTOM에 연결된이 노드를 고려하십시오. 크기를 두 배로 늘리고 싶습니다.

 +-
 |

크기를 두 배로 늘리고 동일한 출구를 유지하는 두 가지 방법이 있습니다 (오른쪽 및 아래쪽).

 +-+- 
 |
 +-+
   |

또는

+-+
| |
+ +-
|

어느 것을 선택해야하는지 결정하기 위해 출구 문이 왼쪽 / 오른쪽 또는 위 / 아래로 이동하는지 여부를 나타내는 각 노드 방향에 대해 "shift"값을 처리해야합니다. 뱀처럼 경로를 따라 가고 경로를 따라 이동 값을 업데이트합니다. 시프트 값은 다음 단계에 사용해야 할 확장 블록을 고유하게 결정합니다.


3
힐버트 커브의 경우 +1 이것으로 꽤 자연스럽게 보이지만 코드를 게시 할 수 있다면 좋을 것입니다.
izlin

@izlin 많은 코드가 있습니다-일부를 게시하려고합니다
Arnaud

1
@SuperChafouin 30k 자 미만인 경우 모두 게시하십시오. SE가 자동으로 스크롤바를 추가합니다.
마틴 엔더

빠르고 더러운 내 코드를 약간 수정하여 게시합니다 :-)
Arnaud

3
나는 포기한다. 어디에서 끝나는가?!
TMH

10

파이썬

다음은 일을 시작하기위한 매우 간단한 알고리즘입니다. 이미지의 왼쪽 상단에서 시작하여 시계 방향으로 안쪽으로 나선으로, 허용 범위 내에 머무르면서 가능한 한 다음 픽셀의 색상에 가깝게 색상을 만듭니다.

import Image

def colorDist(c1, c2): #not normalized
    return (sum((c2[i] - c1[i])**2 for i in range(3)))**0.5

def closestColor(current, goal, tolerance):
    tolerance *= 255 * 3**0.5
    d = colorDist(current, goal)
    if d > tolerance: #return closest color in range
        #due to float rounding this may be slightly outside of tolerance range
        return tuple(int(current[i] + tolerance * (goal[i] - current[i]) / d) for i in range(3))
    else:
        return goal

imgName = 'lena.png'
tolerance = 0.03

print 'Starting %s at %.03f tolerance.' % (imgName, tolerance)

img = Image.open(imgName).convert('RGB')

imgData = img.load()
out = Image.new('RGB', img.size)
outData = out.load()

x = y = 0
c = imgData[x, y]
traversed = []
state = 'right'

updateStep = 1000

while len(traversed) < img.size[0] * img.size[1]:
    if len(traversed) > updateStep and len(traversed) % updateStep == 0:
        print '%.02f%% complete' % (100 * len(traversed) / float(img.size[0] * img.size[1]))
    outData[x, y] = c
    traversed.append((x, y))
    oldX, oldY = x, y
    oldState = state
    if state == 'right':
        if x + 1 >= img.size[0] or (x + 1, y) in traversed:
            state = 'down'
            y += 1
        else:
            x += 1
    elif state == 'down':
        if y + 1 >= img.size[1] or (x, y + 1) in traversed:
            state = 'left'
            x -= 1
        else:
            y += 1
    elif state == 'left':
        if x - 1 < 0 or (x - 1, y) in traversed:
            state = 'up'
            y -= 1
        else:
            x -= 1
    elif state == 'up':
        if y - 1 < 0 or (x, y - 1) in traversed:
            state = 'right'
            x += 1
        else:
             y -= 1
    c = closestColor(c, imgData[x, y], tolerance)

out.save('%.03f%s' % (tolerance, imgName))
print '100% complete'

더 큰 이미지를 실행하는 데 1 ~ 2 분이 걸리지 만 나선형 로직이 크게 최적화 될 수 있다고 확신합니다.

결과

흥미롭지 만 화려하지는 않습니다. 놀랍게도 0.1 이상의 공차는 매우 정확한 결과를 만들어냅니다.

0.03 공차의 대파 :

0.03 공차의 대파

0.02 허용 오차의 모나리자 :

0.02 허용 오차의 모나리자

0.03 허용 오차, 0.01, 0.005, 0.003의 Lena :

0.03 허용 오차의 레나 0.01 공차에서 레나 0.005 공차에서 레나 [0.003 공차의 레나

0.1 허용 오차, 기타 0.07, 0.04, 0.01의 기타 항목 :

0.1 공차의 기타 사항 0.07 허용 오차의 기타 사항 0.04 허용 오차의 기타 사항 0.01 허용 오차의 기타 사항


13
파이썬으로 뱀 프로그램을 작성하는 것이 합법적 인 것 같습니다.
Arnaud

10

코브라

@number float
use System.Drawing
class Program
    var source as Bitmap?
    var data as List<of uint8[]> = List<of uint8[]>()
    var canvas as List<of uint8[]> = List<of uint8[]>()
    var moves as int[] = @[0,1]
    var direction as bool = true
    var position as int[] = int[](0)
    var tolerance as float = 0f
    var color as uint8[] = uint8[](4)
    var rotated as bool = false
    var progress as int = 0
    def main
        args = CobraCore.commandLineArgs
        if args.count <> 3, throw Exception()
        .tolerance = float.parse(args[1])
        if .tolerance < 0 or .tolerance > 1, throw Exception()
        .source = Bitmap(args[2])
        .data = .toData(.source to !)
        .canvas = List<of uint8[]>()
        average = float[](4)
        for i in .data
            .canvas.add(uint8[](4))
            for n in 4, average[n] += i[n]/.source.height
        for n in 4, .color[n] = (average[n]/.source.width).round to uint8
        if .source.width % 2
            if .source.height % 2
                .position = @[0, .source.height-1]
                .update
                while .position[1] > 0, .up
                .right
            else
                .position = @[.source.width-1, .source.height-1]
                .update
                while .position[1] > 0, .up
                while .position[0] > 0, .left
                .down
        else
            if .source.height % 2
                .position = @[0,0]
                .update
            else
                .position = @[.source.width-1,0]
                .update
                while .position[0] > 0, .left
                .down
        .right
        .down
        while true
            if (1-.source.height%2)<.position[1]<.source.height-1
                if .moves[1]%2==0
                    if .direction, .down
                    else, .up
                else
                    if .moves[0]==2, .right
                    else, .left
            else
                .right
                if .progress == .data.count, break
                .right
                .right
                if .direction
                    .direction = false
                    .up
                else
                    .direction = true
                    .down
        image = .toBitmap(.canvas, .source.width, .source.height)
        if .rotated, image.rotateFlip(RotateFlipType.Rotate270FlipNone)
        image.save(args[2].split('.')[0]+'_snake.png')

    def right
        .position[0] += 1
        .moves = @[.moves[1], 0]
        .update

    def left
        .position[0] -= 1
        .moves = @[.moves[1], 2]
        .update

    def down
        .position[1] += 1
        .moves = @[.moves[1], 1]
        .update

    def up
        .position[1] -= 1
        .moves = @[.moves[1], 3]
        .update

    def update
        .progress += 1
        index = .position[0]+.position[1]*(.source.width)
        .canvas[index] = .closest(.color,.data[index])
        .color = .canvas[index]

    def closest(color1 as uint8[], color2 as uint8[]) as uint8[]
        d = .difference(color1, color2)
        if d > .tolerance
            output = uint8[](4)
            for i in 4, output[i] = (color1[i] + .tolerance * (color2[i] - _
            color1[i]) / d)to uint8
            return output
        else, return color2

    def difference(color1 as uint8[], color2 as uint8[]) as float
        d = ((color2[0]-color1[0])*(color2[0]-color1[0])+(color2[1]- _
        color1[1])*(color2[1]-color1[1])+(color2[2]-color1[2])*(color2[2]- _
        color1[2])+0f).sqrt
        return d / (255 * 3f.sqrt)

    def toData(image as Bitmap) as List<of uint8[]>
        rectangle = Rectangle(0, 0, image.width, image.height)
        data = image.lockBits(rectangle, System.Drawing.Imaging.ImageLockMode.ReadOnly, _
        image.pixelFormat) to !
        ptr = data.scan0
        bytes = uint8[](data.stride*image.height)
        System.Runtime.InteropServices.Marshal.copy(ptr, bytes, 0, _
        data.stride*image.height)
        pfs = Image.getPixelFormatSize(data.pixelFormat)//8
        pixels = List<of uint8[]>()
        for y in image.height, for x in image.width
            position = (y * data.stride) + (x * pfs)
            red, green, blue, alpha = bytes[position+2], bytes[position+1], _
            bytes[position], if(pfs==4, bytes[position+3], 255u8)
            pixels.add(@[red, green, blue, alpha])
        image.unlockBits(data)
        return pixels

    def toBitmap(pixels as List<of uint8[]>, width as int, height as int) as Bitmap
        image = Bitmap(width, height, Imaging.PixelFormat.Format32bppArgb)
        rectangle = Rectangle(0, 0, image.width, image.height)
        data = image.lockBits(rectangle, System.Drawing.Imaging.ImageLockMode.ReadWrite, _
        image.pixelFormat) to !
        ptr = data.scan0
        bytes = uint8[](data.stride*image.height)
        pfs = System.Drawing.Image.getPixelFormatSize(image.pixelFormat)//8
        System.Runtime.InteropServices.Marshal.copy(ptr, bytes, 0, _
        data.stride*image.height)
        count = -1
        for y in image.height, for x in image.width 
            pos = (y*data.stride)+(x*pfs)
            bytes[pos+2], bytes[pos+1], bytes[pos], bytes[pos+3] = pixels[count+=1]
        System.Runtime.InteropServices.Marshal.copy(bytes, 0, ptr, _
        data.stride*image.height)
        image.unlockBits(data)
        return image

다음과 같은 뱀으로 이미지를 채 웁니다.

#--#
   |
#--#
|
#--#
   |

이렇게하면 방향을 바꾸는 선보다 훨씬 빠른 색상 조정이 가능하지만 3 와이드 버전만큼 방해가되지는 않습니다.

허용 오차가 매우 낮더라도 이미지의 가장자리는 여전히 보입니다 (더 작은 해상도에서는 디테일이 손실 되더라도).

0.01

여기에 이미지 설명을 입력하십시오

0.1

여기에 이미지 설명을 입력하십시오

0.01

여기에 이미지 설명을 입력하십시오

0.01

여기에 이미지 설명을 입력하십시오

0.1

여기에 이미지 설명을 입력하십시오

0.03

여기에 이미지 설명을 입력하십시오

0.005

여기에 이미지 설명을 입력하십시오


1

기음#

스네이크는 왼쪽 상단 픽셀에서 흰색으로 시작하여 이미지를 왼쪽에서 오른쪽으로, 오른쪽에서 왼쪽으로 번갈아 표시합니다.

using System;
using System.Drawing;

namespace snake
{
    class Snake
    {
        static void MakeSnake(Image original, double tolerance)
        {
            Color snakeColor = Color.FromArgb(255, 255, 255);//start white
            Bitmap bmp = (Bitmap)original;
            int w = bmp.Width;
            int h = bmp.Height;
            Bitmap snake = new Bitmap(w, h);

            //even rows snake run left to right else run right to left
            for (int y = 0; y < h; y++)
            {
                if (y % 2 == 0)
                {
                    for (int x = 0; x < w; x++)//L to R
                    {
                        Color pix = bmp.GetPixel(x, y);
                        double diff = Snake.RGB_Distance(snakeColor, pix);
                        if (diff < tolerance)
                        {
                            snakeColor = pix;
                        }
                        //else keep current color
                        snake.SetPixel(x, y, snakeColor);
                    }
                }
                else
                {
                    for (int x = w - 1; x >= 0; x--)//R to L
                    {
                        Color pix = bmp.GetPixel(x, y);
                        double diff = Snake.RGB_Distance(snakeColor, pix);
                        if (diff < tolerance)
                        {
                            snakeColor = pix;
                        }
                        //else keep current color
                        snake.SetPixel(x, y, snakeColor);
                    }
                }
            }

            snake.Save("snake.png");
        }

        static double RGB_Distance(Color current, Color next)
        {
            int dr = current.R - next.R;
            int db = current.B - next.B;
            int dg = current.G - next.G;
            double d = Math.Pow(dr, 2) + Math.Pow(db, 2) + Math.Pow(dg, 2);
            d = Math.Sqrt(d) / (255 * Math.Sqrt(3));
            return d;
        }

        static void Main(string[] args)
        {
            try
            {
                string file = "input.png";
                Image img = Image.FromFile(file);
                double tolerance = 0.03F;
                Snake.MakeSnake(img, tolerance);
                Console.WriteLine("Complete");
            }
            catch(Exception e)
            {
                Console.WriteLine(e.Message);
            }

        }
    }
}

결과 이미지 허용 오차 = 0.1

여기에 이미지 설명을 입력하십시오

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.