Ідіть і зробіть це зоряним


14

У цьому конкурсі ви повинні написати програму, яка приймає чорно-білий піксельний образ і намагається його змінити так, щоб біла форма утворювала домен зірки з якомога меншими змінами.

Дозволені зміни - перетворення білих пікселів у чорні та перетворення чорних пікселів у білі.

Вихід повинен знову складатися з того ж зображення, але на цей раз із усіма змінами та з позначеним центром. Пікселі, які були змінені з білого на чорний, повинні бути відображені синім кольором, ті, які були змінені з чорного на білий, повинні бути відображені жовтим кольором, а хоча б один центральний піксель повинен бути відображений червоним кольором. (Точні кольори залежать від вас.) Програма повинна витіснити вказане зображення, а також загальну кількість внесених змін.

Визначення

Зоряний домен

Набір білих пікселів зображення представляє зірковий домен, якщо (і лише якщо) є (принаймні) один центральний піксель . Центральний піксель є одним з білих пікселів , які можуть бути conneced по прямій лінії на всі інші білих пікселях таким чином, що лінія тільки перетинає білі пікселі. (Тому центральний піксель не обов'язково унікальний.)

Пряма лінія між двома пікселями

З огляду на два пікселі (початок і кінець, обидва червоні на малюнку нижче), пряма лінія між двома пікселями складається з усіх пікселів, які торкаються (математичної, жовтої на ілюстрації нижче) лінії, яка веде від центру першого піксель до центру останнього пікселя. Піксель не торкається лінії, якщо він торкається його лише кутом, тому, щоб піксель належав до піксельної лінії, (математична, жовта) лінія повинна перетинати піксель, про який йде мова, з ненульовою довжиною. (Якщо вона торкається лише кутової точки, це вважається нульовою довжиною). Розглянемо наступні приклади:

пікселів

Приклад

Перше зображення повинно представляти приклад тестового вводу "вхід", а два інших зображення являють собою два можливих можливих виходу для даного прикладу:

приклад тестовий перший приклад рішення другий приклад рішення

Жовті області (раніше чорні) також зараховуються до "білого" домену, тоді як сині області (раніше білі) зараховуються до "чорної" частини поза доменом, а червона крапка кожного разу представляє один можливий центральний піксель.

Випробування

Наступні тестові випадки - це png, розмір кожного з яких становить 256 х 256 пікселів.

тестовий випадок 1 тестовий випадок 2 тестовий випадок 3 тестовий випадок 4 тестовий випадок 5 тестовий випадок 6

Оцінка балів

Будь ласка, запустіть програму з наступними тестовими кейсами та включіть у відповідь результат (зображення / кількість змін). Я буду складати таблицю лідерів для кожного тестового випадку. Ваш результат буде сумою кожного рейтингу в таблиці лідерів - чим нижча оцінка, тим краще. Застосовуються стандартні лазівки. Не дозволяється програмі розпізнавати ці тестові випадки та запускати для них спеціальний випадок. (Не дозволяється попередньо обчислювати та зберігати оптимальні центральні пікселі для кожного з цих тестових випадків.) Програма повинна працювати для будь-яких зображень.

Таблиця лідерів

Name        | Score | 1     - rk | 2     - rk | 3     - rk | 4     - rk | 5     - rk | 5     - rk | Total Changes
------------+-------+------------+------------+------------+------------+------------+------------+--------------
Maltysen    |    11 | 28688 -  2 | 24208 -  2 | 24248 -  1 |  7103 -  2 | 11097 -  2 | 13019 -  2 | 108363
TheBestOne  |     7 | 0     -  1 | 13698 -  1 | 24269 -  2 |   103 -  1 |  5344 -  1 |  4456 -  1 |  47867  

2
Було б корисно, якби ви пояснили рис. 1. Чому, наприклад, ви підключаєте червоні пікселі?
DavidC

4
Я не дуже впевнений, що ти маєш на увазі. Чи можете ви надати до та після одного з ваших тестових випадків?

Наскільки близько має бути лінія до куточка пікселя, щоб вважати її прохідною?
TheNumberOne

Я додав кілька прикладів і спробував уточнити текст, сподіваюся, це зрозуміло зараз!
дефект

Чи є ще хтось, хто має намір спробувати цей виклик? Я дещо розгублений, оскільки досить багато людей підтримали цей виклик, але поки що у нас є лише одна (не дуже серйозна) відповідь. Будь-яка критика?
невдача

Відповіді:


4

Java 8, 47 867 змін.

Використовує середнє зображення як центральну точку. Потім він притягує всі можливі промені до центру і надає йому найкращий радіус кольору. Потім він забарвлює всі недійсні точки в чорний.

import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class MakeItStarry {

    private static final int RGB_RED = Color.RED.getRGB();
    static int[][] originalImage;

    static final int WHITE = 0;
    static final int BLACK = 1;
    static final int RGB_WHITE = Color.WHITE.getRGB();
    static final int RGB_BLACK = Color.BLACK.getRGB();
    static final int RGB_BLUE = Color.BLUE.getRGB();
    static final int RGB_YELLOW = Color.YELLOW.getRGB();

    public static void main(String[] args) throws Exception{
        originalImage = convert(ImageIO.read(new File(args[0])));
        Point center = findCenter(originalImage);
        int[][] nextImage = starry(originalImage, center);
        BufferedImage result = difference(originalImage, nextImage);
        result.setRGB(center.x, center.y, RGB_RED);
        String fileType;
        String fileName;
        if (args[1].split("\\.").length > 1){
            fileType = args[1].split("\\.")[1];
            fileName = args[1];
        } else {
            fileType = "PNG";
            fileName = args[1] + ".PNG";
        }
        ImageIO.write(result, fileType, new File(fileName));
        System.out.println(cost);
    }

    static int cost;

    private static BufferedImage difference(int[][] image1, int[][] image2) {
        cost = 0;
        int height = image1[0].length;
        int width = image1.length;
        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++){
            for (int y = 0; y < width; y++){
                if (image1[x][y] == image2[x][y]){
                    if (image1[x][y] == WHITE){
                        result.setRGB(x, y, RGB_WHITE);
                    } else {
                        result.setRGB(x, y, RGB_BLACK);
                    }
                } else {
                    cost++;
                    if (image1[x][y] == WHITE){
                        result.setRGB(x, y, RGB_BLUE);
                    } else {
                        result.setRGB(x, y, RGB_YELLOW);
                    }
                }
            }
        }
        return result;
    }

    private static int[][] starry(int[][] image, Point center) {
        int width = image.length;
        int height = image[0].length;
        int[][] result = new int[width][height];
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                result[x][y] = BLACK;
            }
        }
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++) {
                Point endPoint = new Point(x, y, image);
                List<Point> line = Point.lineTo(center, endPoint, image);
                List<Point> newLine = starRay(line);
                newLine.stream().filter(point -> result[point.x][point.y] == BLACK).forEach(point -> {
                    result[point.x][point.y] = point.color;
                });
            }
        }
        int distance = 0;
        while (distance < height || distance < width){//This removes pixels that can't see the center.
            for (int x = Math.max(center.x - distance,0); x < center.x + distance && x < width; x++){
                for (int y = Math.max(center.y - distance, 0); y < center.y + distance && y < height; y++){
                    Point point = new Point(x, y, result);
                    if (Point.distance(center, point) != distance){
                        continue;
                    }
                    if (point.color == WHITE){
                        List<Point> line = Point.lineTo(center, point, result);
                        for (Point p : line){
                            if (p.color == BLACK){
                                point.color = BLACK;
                                break;
                            }
                        }
                        result[point.x][point.y] = point.color;
                    }
                }
            }//All white pixels can technically see the center but only if looking from the edge.
            distance++;
        }
        return result;
    }

    private static List<Point> starRay(List<Point> line) {
        int numOfWhites = 0;
        int farthestGoodPoint = 0;
        int blackCost = 0;
        int whiteCost = 0;
        for (int i = 0; i < line.size(); i++){
            if (line.get(i).color == WHITE){
                numOfWhites++;
                whiteCost++;
                if (numOfWhites + whiteCost > blackCost){
                    blackCost = 0;
                    whiteCost = 0;
                    farthestGoodPoint = i;
                }
            } else {
                blackCost++;
                numOfWhites = 0;
            }
        }
        List<Point> result = new ArrayList<>();
        for (int i = 0; i < line.size(); i++){
            Point p = line.get(i);
            if (i <= farthestGoodPoint){
                result.add(new Point(p.x, p.y, WHITE));
            } else {
                result.add(new Point(p.x, p.y, BLACK));
            }
        }
        return result;
    }

    private static Point findCenter(int[][] image) {
        double totalx = 0;
        double totaly = 0;
        int counter = 0;
        int width = image.length;
        int height = image[0].length;
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                if (image[x][y] == WHITE){
                    totalx += x;
                    totaly += y;
                    counter++;
                }
            }
        }
        return new Point((int)(totalx/counter), (int)(totaly/counter), image);
    }

    private static int[][] convert(BufferedImage image) {
        int width = image.getWidth();
        int height  = image.getHeight();
        int[][] result = new int[width][height];
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                if (image.getRGB(x, y) == RGB_WHITE){
                    result[x][y] = WHITE;
                } else {
                    result[x][y] = BLACK;
                }
            }
        }
        return result;
    }


    private static class Point {

        public int color;
        public int y;
        public int x;

        public Point(int x, int y, int[][] image) {
            this.x = x;
            this.y = y;
            this.color = image[x][y];
        }

        public Point(int x, int y, int color) {
            this.x = x;
            this.y = y;
            this.color = color;
        }

        public static List<Point> lineTo(Point point1, Point point2, int[][] image) {
            List<Point> result = new ArrayList<>();
            boolean reversed = false;
            if (point1.x > point2.x){
                Point buffer = point1;
                point1 = point2;
                point2 = buffer;
                reversed = !reversed;
            }
            int rise = point1.y - point2.y;
            int run = point1.x - point2.x;
            if (run == 0){
                if (point1.y > point2.y){
                    Point buffer = point1;
                    point1 = point2;
                    point2 = buffer;
                    reversed = !reversed;
                }
                int x = point1.x;
                for (int y = point1.y; y <= point2.y; y++){
                    result.add(new Point(x, y, image));
                }
                if (reversed){
                    return reversed(result);
                }
                return result;
            }
            if (rise == 0){
                if (point1.x > point2.x){
                    Point buffer = point1;
                    point1 = point2;
                    point2 = buffer;
                    reversed = !reversed;
                }
                int y = point1.y;
                for (int x = point1.x; x <= point2.x; x++){
                    result.add(new Point(x, y, image));
                }
                if (reversed){
                    return reversed(result);
                }
                return result;
            }
            int gcd = gcd(rise, run);
            rise /= gcd;
            run /= gcd;
            double slope = (rise + 0.0) / run;
            if (Math.abs(rise) >= Math.abs(run)){
                if (point1.y > point2.y){
                    Point buffer = point1;
                    point1 = point2;
                    point2 = buffer;
                    reversed = !reversed;
                }
                double x = point1.x;
                for (double y = point1.y + .5; y <= point2.y; y++){
                    int px = (int) Math.round(x);
                    if (Math.abs(Math.abs(px - x) - .5) < Math.abs(1.0 / (rise * 4))){
                        x += 1/slope;
                        continue;
                    }
                    result.add(new Point(px, (int) Math.round(y - .5), image));
                    result.add(new Point(px, (int) Math.round(y + .5), image));
                    x += 1/slope;
                }
                if (reversed){
                    return reversed(result);
                }
                return result;
            } else {
                if (point1.x > point2.x){
                    Point buffer = point1;
                    point1 = point2;
                    point2 = buffer;
                    reversed = !reversed;
                }
                double y = point1.y;
                for (double x = point1.x + .5; x <= point2.x; x++){
                    int py = (int) Math.round(y);
                    if (Math.abs(Math.abs(py - y) - .5) < Math.abs(1.0 / (run * 4))) {
                        y += slope;
                        continue;
                    }
                    result.add(new Point((int) Math.round(x - .5), py, image));
                    result.add(new Point((int) Math.round(x + .5), py, image));
                    y += slope;
                }
                if (reversed){
                    return reversed(result);
                }
                return result;
            }
        }

        private static List<Point> reversed(List<Point> points) {
            List<Point> result = new ArrayList<>();
            for (int i = points.size() - 1; i >= 0; i--){
                result.add(points.get(i));
            }
            return result;
        }

        private static int gcd(int num1, int num2) {
            if (num1 < 0 && num2 < 0){
                return -gcd(-num1, -num2);
            }
            if (num1 < 0){
                return gcd(-num1, num2);
            }
            if (num2 < 0){
                return gcd(num1, -num2);
            }
            if (num2 > num1){
                return gcd(num2, num1);
            }
            if (num2 == 0){
                return num1;
            }
            return gcd(num2, num1 % num2);
        }

        @Override
        public String toString(){
            return x + " " + y;
        }

        public static int distance(Point point1, Point point2) {
            return Math.abs(point1.x - point2.x) + Math.abs(point1.y - point2.y);
        }
    }
}

Результати

Зображення 1 - 0 змін, зображення 2 - 13 698 змін

12

Зображення 3 - 24 269 змін, зображення 4 - 103 зміни

34

Зображення 5 - 5 444 зміни, зображення 6 - 4 456 змін

56

Без видалених недійсних пікселів загалом 42,782 зміни

Зелені пікселі - це перший шар недійсних пікселів.

Зображення 1 - 0 змін, зображення 2-9,889 змін

12

Зображення 3 - 24 268 змін, зображення 4 - 103 зміни

34

Зображення 5 - 4,471 зміни, зображення 6,050 змін

56

Усі білі пікселі на всіх зображеннях можуть мати лінію, намальовану до них від центрального пікселя, якщо лінія не повинна зароджуватися / закінчуватися в центрах, а скоріше десь на пікселі.

args[0] містить ім'я вхідного файлу.

args[1] містить ім'я вихідного файлу.

Друкує до stdoutкількості змін.


Виглядає здорово! Чи можете ви пояснити, що ви маєте на увазі під "недійсними пікселями"? Я цього не зовсім зрозумів. Також на зображенні 2 в нижньому правому куті я не міг прослідкувати, чому ваша програма "копається" в чорну стіну, але все одно знову забарвлює білі точки в чорні, але я думаю, це має відношення до "недійсних пікселів"?
недолік

Кілька недійсних пікселів викликають ефект каскаду, що робить багато інших недійсними. Я зміню останні кілька зображень, щоб перший шар недійсних пікселів відображався як зелений.
TheNumberOne

3

Python - PIL - 216,228 108,363 зміни загалом

Хто! Розріжте його навпіл завдяки @AJMansfield! Цей алгоритм пропускає все занепокоєння при обчисленні ліній та оптимізації, а що ні. Він просто змінює всі білі на чорні, крім одного. Якщо білих немає, це робить одного чорного білим. Він перевіряє, чи є більше білих або чорних, і змінює кожен один із іншого виду, крім одного. Якщо немає чорного кольору, це робить (0, 0) центр.

import Image
from itertools import product

img = Image.open(raw_input())
img = img.convert("RGB")

pixdata = img.load()
changed=0

m=False
count=0
for x, y in product(xrange(img.size[1]), xrange(img.size[0])):
    if pixdata[x, y]==(0, 0, 0):
        count+=1

colors=[(0, 0, 0), (255, 255, 0)] if img.size[0]*img.size[1]-count>count else [(255, 255, 255), (0, 0, 255)]
m=False
for x, y in product(xrange(img.size[1]), xrange(img.size[0])):
    if pixdata[x, y] == colors[0]:
        if m:
            pixdata[x, y] = colors[1]
        else:
            pixdata[x, y] = (255, 0, 0)
            m=True
        changed+=1

if not m:
    pixdata[0, 0]==(255, 0, 0)
    changed+=1
if colors[0]==(255, 255, 255):
    changed-=1

print changed
img.save("out.png", "PNG")

Результати

Зображення 1 - 28688 змін, зображення 2 - 24208 зміни

Зображення 3 - 24248 змін, зображення 4 - 7103 зміни

Зображення 5 - 11097 змін, зображення 6 - 13019 змін

Бере ім’я файлу з raw_input і записує в out.png і друкує кількість змін.


Зверніть увагу, що пікселі, які були змінені з чорного на білий, повинні бути жовтими у вашому висновку. Тос, який був змінений з білого на чорний, повинен бути синім, а центр (у вашому випадку ваш єдиний "білий" піксель має бути червоним у вашому виході. Крім того, дякую вам за участь =) PS: Завжди має бути можливість зробіть зірковий домен, навіть якщо у вас є повне чорне зображення як вхід, ви можете змінити один піксель на білий (червоний).
недолік

Це може бути неможливо, якщо немає білих або чорних пікселів (тобто повнокольорових). У будь-якому випадку я вношу інші зміни.
Малтісен

Ой. Чорно-біле зображення Моє ліжко.
Малтісен

Я думаю, що може бути ефективніше зробити протилежну стратегію та змінити всі чорні пікселі на білі. Ви пробували це?
AJMansfield

@AJMansfield Я думаю, що це було б більш ефективно для даного тестового випадку, тому, можливо, це вже можна розглядати як обумовлення алгоритму для заданих тестів.
flawr
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.