Подсчет цветов в изображении: NSCountedSet и colorAtX работают очень медленно

Я делаю приложение для OS X, которое создает цветовую схему из основных цветов изображения.

В качестве первого шага я использую NSCountedSet и colorAtX, чтобы получить все цвета из изображения и подсчитать их появление:

func sampleImage(#width: Int, height: Int, imageRep: NSBitmapImageRep) -> (NSCountedSet, NSCountedSet) {
    // Store all colors from image
    var colors = NSCountedSet(capacity: width * height)
    // Store the colors from left edge of the image
    var leftEdgeColors = NSCountedSet(capacity: height)
    // Loop over the image pixels
    var x = 0
    var y = 0
    while x < width {
        while y < height {
            // Instruments shows that `colorAtX` is very slow
            // and using `NSCountedSet` is also very slow
            if let color = imageRep.colorAtX(x, y: y) {
                if x == 0 {
                    leftEdgeColors.addObject(color)
                }
                colors.addObject(color)
            }
            y++
        }
        // Reset y every x loop
        y = 0
        // We sample a vertical line every x pixels
        x += 1
    }
    return (colors, leftEdgeColors)
}

Моя проблема в том, что это очень медленно. В инструментах я вижу два больших узких места: с NSCountedSet и с colorAtX.

Итак, сначала я подумал, может быть, заменить NSCountedSet чистым эквивалентом Swift, но неудивительно, что новая реализация оказалась намного медленнее, чем NSCountedSet.

Для colorAtX есть этот интересный SO-ответ, но я не смог перевести его на Swift (и я не может использовать заголовок моста для Objective-C для этого проекта).

Моя проблема при попытке перевести это в том, что я не понимаю частей unsigned char и char в ответе.

Что мне попробовать, чтобы сканировать цвета быстрее, чем с colorAtX?

  • Продолжить работу над адаптацией ответа Objective-C, потому что это хороший ответ? Несмотря на то, что на данный момент я застрял, возможно, я смогу добиться этого позже.

  • Использовать другой метод Foundation / Cocoa, о котором я не знаю?

  • Что-нибудь еще, что я мог бы попытаться улучшить свой код?

TL; DR

colorAtX работает медленно, и я не понимаю, как адаптировать этот ответ Objective-C для Swift из-за unsigned char.


person Eric Aya    schedule 24.06.2015    source источник


Ответы (1)


Самая быстрая альтернатива colorAtX () - перебирать необработанные байты изображения с использованием let bitmapBytes = imageRep.bitmapData и самостоятельно составлять цвет из этой информации, что должно быть очень просто, если это просто данные RGBA. Вместо цикла for x / y сделайте что-нибудь вроде этого ...

let bitmapBytes = imageRep.bitmapData
var colors = Dictionary<UInt32, Int>()

var index = 0
for _ in 0..<(width * height) {
    let r = UInt32(bitmapBytes[index++])
    let g = UInt32(bitmapBytes[index++])
    let b = UInt32(bitmapBytes[index++])
    let a = UInt32(bitmapBytes[index++])
    let finalColor = (r << 24) + (g << 16) + (b << 8) + a   

    if colors[finalColor] == nil {
        colors[finalColor] = 1
    } else {
        colors[finalColor]!++
    }
}

Я только догадался, что вам нужно будет проверить порядок значений RGBA!

Самым быстрым способом ведения подсчета может быть просто словарь [Int, Int] значений пикселей для подсчета, что-то вроде colors[color]++. Позже, если вам нужно, вы можете преобразовать это в NSColor, используя NSColor(calibratedRed red: CGFloat, green green: CGFloat, blue blue: CGFloat, alpha alpha: CGFloat)

person GoatInTheMachine    schedule 24.06.2015
comment
Спасибо за ваше понимание. Я считаю, что это метод, использованный в ответе Objective-C, о котором я имел в виду? Но пока я застрял, потому что не понимаю, как перевести unsigned char части на Swift. - person Eric Aya; 24.06.2015
comment
Извините, но я не понимаю, как использовать ваш ответ. : / Что такое index ++? И как применить этот анализ к каждому пикселю, если я не могу перебрать bitmapBytes? Но это выглядит многообещающе, я просто запуталась ... - person Eric Aya; 24.06.2015
comment
Я обновился, все это имеет смысл? У меня нет Mac, поэтому я могу только убедиться, что он компилируется на SwiftStub, но он должен дать вам представление! - person GoatInTheMachine; 24.06.2015
comment
Большое спасибо, теперь я понимаю, что здесь происходит. :) К сожалению, выборка изображения с помощью этого решения выполняется намного медленнее, чем раньше при использовании моего стандартного теста 600x600 изображение. - person Eric Aya; 24.06.2015
comment
Можете ли вы профилировать и найти, какая часть из вышеперечисленного является медленной? Бит обхода байтов должен работать быстро, хотя вам, возможно, придется немного поиграть с уровнями оптимизации Swift. - person GoatInTheMachine; 24.06.2015
comment
После вашего комментария я провел тест со схемой, установленной на выпуск, а не на отладку. Теперь ваше решение на самом деле немного быстрее, чем с colorAtX, это победа! Спасибо. : D - person Eric Aya; 24.06.2015
comment
Потрясающий! Но 40 мсек для создания изображения 600x600 все еще кажется очень медленным, может быть, это словарь? Вы пробовали использовать NSCountedSet с ключом UInt32? Профилирование вызовов в самом методе может быть единственным способом. - person GoatInTheMachine; 24.06.2015
comment
Хорошие новости: я обнаружил предвзятость в тестах, которые я провел сегодня ранее (у меня было x += 10 вместо x += 1 ...). После исправления и после того, как я приблизил тесты к реальному использованию приложения и установил компилятор на Release, моему исходному коду требуется 6,5 секунды для обработки пакета из десяти изображений 600x600, в то время как вашей версии требуется 0,4 секунды для обработки того же пакета. :) Еще раз спасибо за этот ответ, это очень помогло. - person Eric Aya; 24.06.2015