Vẽ một hình ảnh với một con rắn


28

Hãy tưởng tượng một đường dẫn 2 chiều liên tục chỉ có thể rẽ trái, phải hoặc đi thẳng, không thể tự cắt nhau và phải lấp đầy một lưới hình chữ nhật như lưới các pixel trong một hình ảnh. Chúng tôi sẽ gọi loại con đường này là một con rắn .

Ví dụ con rắn

Ví dụ mở rộng này cho thấy một con đường rắn trong lưới 10 × 4 bắt đầu màu đỏ và tăng màu sắc khoảng 2% ở mỗi bước cho đến khi nó có màu tím. (Các đường màu đen chỉ để nhấn mạnh hướng đi.)

Mục tiêu

Mục tiêu trong cuộc thi phổ biến này là viết một thuật toán cố gắng tạo lại một hình ảnh nhất định bằng cách sử dụng một con rắn duy nhất có màu liên tục thay đổi với số lượng nhỏ.

Chương trình của bạn phải thực hiện trong một hình ảnh đúng màu của bất kỳ kích thước cũng như là một giá trị dấu chấm động giữa 0 và 1 bao gồm, các khoan dung .

Dung sai xác định số lượng tối đa màu sắc của con rắn được phép thay đổi trong mỗi bước có kích thước pixel. Chúng ta sẽ xác định khoảng cách giữa hai màu RGB là khoảng cách Euclide giữa hai điểm RGB khi được sắp xếp trên một khối màu RGB . Khoảng cách sau đó sẽ được chuẩn hóa để khoảng cách tối đa là 1 và khoảng cách tối thiểu là 0.

Mã giả khoảng cách màu: (Giả sử tất cả các giá trị đầu vào là số nguyên trong phạm vi [0, 255]; đầu ra được chuẩn hóa.)

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

Nếu kết quả của việc gọi hàm này trên màu hiện tại của con rắn và màu khác lớn hơn dung sai cho trước thì con rắn có thể không thay đổi thành màu khác.

Nếu bạn thích, bạn có thể sử dụng chức năng khoảng cách màu khác. Nó phải là một cái gì đó chính xác và được ghi chép tốt, chẳng hạn như những thứ được liệt kê tại http://en.wikipedia.org/wiki/Color_difference . Bạn cũng phải bình thường hóa nó ở trong [0, 1], tức là khoảng cách tối đa có thể phải là 1 và tối thiểu phải là 0. Hãy cho chúng tôi biết trong câu trả lời của bạn nếu bạn sử dụng một thước đo khoảng cách khác.

Hình ảnh thử nghiệm

Tất nhiên bạn nên đăng hình ảnh đầu ra của bạn (và thậm chí cả hình ảnh động của con rắn đang phát triển nếu bạn muốn). Tôi đề nghị đăng nhiều loại hình ảnh này bằng các dung sai thấp khác nhau (có thể khoảng 0,005 đến 0,03).

Mandrill nàng mô na Li Sa Làn sóng lớn Lena màu sắc ngẫu nhiên, độ dốc misc (Sóng lớn hơn)

Đạt tiêu chí

Như đã nêu, đây là một cuộc thi phổ biến. Câu trả lời được bình chọn cao nhất sẽ giành chiến thắng. Câu trả lời cung cấp mô tả "con đường rắn" chính xác và thẩm mỹ nhất về hình ảnh đầu vào nên được bình chọn.

Bất kỳ người dùng nào bị phát hiện gửi hình ảnh độc hại không phải là rắn thực sự sẽ bị loại vĩnh viễn.

Ghi chú

  • Chỉ có một đường dẫn rắn có thể được sử dụng và nó phải lấp đầy hoàn toàn hình ảnh mà không chạm vào cùng một pixel hai lần.
  • Con rắn có thể bắt đầu và kết thúc bất cứ nơi nào trong hình ảnh.
  • Con rắn có thể bắt đầu như bất kỳ màu nào.
  • Con rắn phải ở trong giới hạn của hình ảnh. Các giới hạn không theo chu kỳ.
  • Con rắn không thể di chuyển theo đường chéo hoặc nhiều hơn một pixel mỗi lần.

14
Nghiêm túc mà nói, làm thế nào bạn quản lý để đăng 14 thử thách thực sự tốt (một trong số đó là tốt nhất thứ ba từ trước đến nay) trong 16 ngày mà không bao giờ bỏ qua một trong số chúng? Kudos lớn, PPCG cần nhiều người như bạn! ;)
Martin Ender

@ MartinBüttner Không chắc chắn. Họ cứ tự nhiên đến với tôi :) Công bằng mà nói, một câu hỏi tôi đã làm sandbox không được đón nhận quá nhiều: meta.codegolf.stackexchange.com/a/1820/26997
Sở thích của Calvin

Tôi không chắc liệu giải pháp của mình có bị mắc kẹt trong một vòng lặp vô hạn hay không, hoặc nó chỉ mất một thời gian thực sự rất dài. Và đó chỉ là một hình ảnh 80x80!
Doorknob

1
Ôi ... cái này trông thật sự rất vui.
cjfaure

1
@ Belisarius Tôi không nghĩ rằng nó bắt buộc phải chính xác là hình ảnh gốc, càng gần một bản sao càng tốt.
Οurous

Câu trả lời:


24

Con trăn

Tôi tạo ra một đường dẫn động để giảm thiểu sự thay đổi màu sắc khi con rắn di chuyển. Dưới đây là một số hình ảnh:

dung sai = 0,01

Mona Lisa 0,01 dung sai Mandrill 0,01 dung sai

Đường dẫn màu tuần hoàn cho các hình ảnh trên (màu xanh sang màu đỏ, trở nên xanh hơn khi lặp lại):

Con đường rắn Mona Lisa trong màu sắc tuần hoàn Con đường Mandrill Snake trong màu tuần hoàn

Đường dẫn được tạo bằng cách bắt đầu với một số đường dẫn ban đầu, sau đó thêm các vòng lặp 2x2 vào đó cho đến khi hình ảnh được lấp đầy. Ưu điểm của phương pháp này là các vòng lặp có thể được thêm vào bất cứ nơi nào trên đường dẫn, vì vậy bạn không thể vẽ mình vào một góc và có nhiều tự do hơn để xây dựng con đường bạn muốn. Tôi theo dõi các vòng lặp có thể bên cạnh đường dẫn hiện tại và lưu trữ chúng thành một đống, có trọng số bởi sự thay đổi màu sắc dọc theo vòng lặp. Sau đó tôi bật ra khỏi vòng lặp với sự thay đổi màu ít nhất và thêm nó vào đường dẫn, và lặp lại cho đến khi hình ảnh được lấp đầy.

Tôi thực sự theo dõi các vòng lặp một mình ('DetourBlock' trong mã), sau đó xây dựng lại đường dẫn; Đây là một sai lầm vì có một số trường hợp đặc biệt cho chiều rộng / chiều cao lẻ và tôi đã dành vài giờ để gỡ lỗi phương pháp xây dựng lại. Ồ tốt

Số liệu tạo đường dẫn cần điều chỉnh và tôi cũng có một ý tưởng để tô màu tốt hơn, nhưng tôi nghĩ rằng tôi sẽ hiểu điều này trước vì nó hoạt động khá tốt. Ngoại trừ cái này, có vẻ tốt hơn trong một số đường dẫn cố định:

Linh tinh 0,01 Dung sai

Đây là mã Python, với lời xin lỗi cho thói quen mã hóa tàn bạo của tôi:

# 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()

Và một số hình ảnh khác với dung sai rất thấp 0,001 :

Làn sóng lớn 0,001 dung sai Mona Lisa dung sai 0,001 Lena 0,001 dung sai

Và cũng là con đường sóng tuyệt vời vì nó gọn gàng:

nhập mô tả hình ảnh ở đây

CHỈNH SỬA

Việc tạo đường dẫn có vẻ tốt hơn khi giảm thiểu khoảng cách màu giữa các màu trung bình của các khối liền kề, thay vì giảm thiểu tổng khoảng cách màu giữa các pixel liền kề của chúng. Ngoài ra, hóa ra bạn có thể trung bình màu sắc của bất kỳ hai con đường rắn tuân thủ dung sai và kết thúc với một con đường rắn tuân thủ dung sai khác. Vì vậy, tôi đi qua con đường theo cả hai cách và lấy trung bình chúng, giúp làm mịn rất nhiều cổ vật. Zombie Lena và Scary Hands Mona trông đẹp hơn nhiều. Phiên bản cuối cùng:

Dung sai 0,01 :

Mona cuối cùng 0,01 Lena 0,01 cuối cùng

Sóng lớn cuối cùng 0,01

Dung sai 0,001 :

Mona cuối cùng Lena cuối cùng

Làn sóng lớn cuối cùng


4
Tốt nhất chưa! Tôi yêu cách Great Wave trông!
Sở thích của Calvin

Tôi thích câu trả lời cho thử thách này được thực hiện bằng python heh
Albert Renshaw

17

Java

Chương trình của tôi tạo ra một đường dẫn rắn cho chiều rộng và chiều cao nhất định, sử dụng thuật toán tương tự như thuật toán tạo đường cong Hilbert.

nhập mô tả hình ảnh ở đây

(trò chơi nhỏ: trong hình trên, con rắn bắt đầu ở góc trên cùng bên trái. Bạn có thể tìm thấy nơi anh ta kết thúc không? Chúc may mắn :)

Dưới đây là kết quả cho các giá trị dung sai khác nhau:

Dung sai = 0,01

dung sai = 0,01

Dung sai = 0,05

dung sai = 0,05

Dung sai = 0,1

dung sai = 0,01

Dung sai = 0,01

Làn sóng

Với các khối pixel 4 x 4 và đường dẫn hiển thị

nhập mô tả hình ảnh ở đây

Tính toán con đường rắn

Một đường dẫn rắn được lưu trữ trong một mảng số nguyên hai chiều. Con rắn luôn đi vào lưới bằng góc trên bên trái. Có 4 thao tác cơ bản mà chương trình của tôi có thể thực hiện trên một con đường rắn nhất định:

  • tạo đường dẫn rắn mới cho lưới có chiều rộng 1 hoặc chiều cao 1. Đường dẫn chỉ là một đường đơn giản đi từ trái sang phải hoặc lên xuống, tùy theo trường hợp.

  • tăng chiều cao lưới, bằng cách thêm ở trên cùng một đường dẫn rắn từ trái sang phải, sau đó bằng cách phản chiếu lưới (con rắn phải luôn đi vào lưới bằng góc trên bên trái)

  • tạo ra chiều rộng lưới, bằng cách thêm vào bên trái một đường dẫn rắn từ trên xuống dưới, sau đó bằng cách lật lưới (con rắn phải luôn đi vào lưới bằng góc trên bên trái)

  • nhân đôi kích thước của lưới bằng thuật toán "kiểu Hilbert" (xem mô tả bên dưới)

Sử dụng một loạt các hoạt động nguyên tử này, chương trình có thể tạo ra một đường dẫn rắn có kích thước bất kỳ.

Mã dưới đây tính toán (theo thứ tự ngược lại) những thao tác sẽ cần thiết để có được chiều rộng và chiều cao nhất định. Sau khi được tính toán, các hành động được thực hiện từng cái một cho đến khi chúng ta có một đường dẫn rắn có kích thước như mong đợi.

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
    }

Nhân đôi kích thước đường dẫn con rắn:

Thuật toán nhân đôi kích thước hoạt động như sau:

Hãy xem xét nút này được liên kết với QUYỀN và BOTTOM. Tôi muốn tăng gấp đôi kích thước của nó.

 +-
 |

Có 2 cách để tăng gấp đôi kích thước của nó và giữ cùng một lối thoát (bên phải và bên dưới):

 +-+- 
 |
 +-+
   |

hoặc là

+-+
| |
+ +-
|

Để xác định nên chọn cái nào, tôi cần xử lý cho mỗi hướng nút một giá trị "shift", cho biết cửa thoát được dịch sang trái / phải hay lên / xuống. Tôi đi theo con đường như con rắn sẽ làm và cập nhật giá trị thay đổi dọc theo con đường. Giá trị thay đổi xác định duy nhất khối mở rộng mà tôi cần sử dụng cho bước tiếp theo.


3
+1 cho đường cong Hilbert. Nó trông khá tự nhiên với cái này nhưng nếu bạn có thể đăng mã của mình thì nó sẽ rất tuyệt.
izlin

@izlin Có rất nhiều mã - Tôi sẽ cố gắng đăng một số phần
Arnaud

1
@SuperChafouin Nếu dưới 30k ký tự, vui lòng đăng tất cả. SE sẽ tự động thêm một thanh cuộn.
Martin Ender

Sẽ làm lại một chút mã của tôi nhanh và bẩn và đăng nó :-)
Arnaud

3
Tôi bỏ cuộc, nó kết thúc ở đâu?!
TMH

10

Con trăn

Đây là một thuật toán rất đơn giản để bắt đầu mọi thứ. Nó bắt đầu ở phía trên bên trái của hình ảnh và xoắn ốc theo chiều kim đồng hồ vào trong, làm cho màu sắc càng gần với màu của pixel tiếp theo trong khi vẫn ở trong phạm vi cho phép.

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'

Phải mất một hoặc hai phút để chạy các hình ảnh lớn hơn, mặc dù tôi chắc rằng logic xoắn ốc có thể được tối ưu hóa rất nhiều.

Các kết quả

Chúng thú vị nhưng không tuyệt đẹp. Thật đáng ngạc nhiên, dung sai trên 0,1 tạo ra kết quả tìm kiếm khá chính xác.

Làn sóng lớn ở mức dung sai 0,03:

Làn sóng lớn ở mức dung sai 0,03

Mona Lisa ở mức dung sai 0,02:

Mona Lisa ở mức dung sai 0,02

Lena ở mức dung sai 0,03, sau đó 0,01, rồi 0,005, rồi 0,003:

Lena ở mức dung sai 0,03 Lena ở mức dung sai 0,01 Lena ở mức 0,005 [Lena ở mức 0,003

Các thứ linh tinh ở mức dung sai 0,1, sau đó 0,07, rồi 0,04, rồi 0,01:

Linh tinh ở mức dung sai 0,1 Thứ linh tinh ở mức dung sai 0,07 Thứ linh tinh ở mức dung sai 0,04 Thứ linh tinh ở mức dung sai 0,01


13
Có vẻ hợp pháp để viết một chương trình con rắn với Python.
Arnaud

10

Rắn hổ mang

@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

Lấp đầy hình ảnh với một con rắn như:

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

Điều này cho phép điều chỉnh màu nhanh hơn nhiều so với chỉ các đường theo các hướng xen kẽ, nhưng không trở nên khối như phiên bản 3 chiều.

Ngay cả ở dung sai rất thấp, các cạnh của hình ảnh vẫn có thể nhìn thấy (mặc dù mất chi tiết ở độ phân giải nhỏ hơn).

0,01

nhập mô tả hình ảnh ở đây

0,1

nhập mô tả hình ảnh ở đây

0,01

nhập mô tả hình ảnh ở đây

0,01

nhập mô tả hình ảnh ở đây

0,1

nhập mô tả hình ảnh ở đây

0,03

nhập mô tả hình ảnh ở đây

0,005

nhập mô tả hình ảnh ở đây


1

C #

Con rắn bắt đầu ở pixel trên cùng bên trái với màu trắng và xen kẽ từ trái sang phải và sau đó từ phải sang trái xuống hình ảnh.

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);
            }

        }
    }
}

Kết quả dung sai hình ảnh = 0,1

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.