Быстрый метод доступа к случайным пикселям изображения и не более одного раза - PullRequest
2 голосов
/ 29 мая 2020

Я изучаю OpenCV (C ++) и в качестве простой практики разработал простой эффект, который делает некоторые пиксели изображения черными или белыми. Я хочу, чтобы каждый пиксель редактировался не более одного раза; поэтому я добавил в вектор адрес всех пикселей. Но это сделало мой код очень медленным; особенно для больших изображений или больших эффектов. Вот мой код:

void effect1(Mat& img, float amount)    // 100 ≥ amount ≥ 0
{
    vector<uchar*> addresses;
    int channels = img.channels();
    uchar* lastAddress = img.ptr<uchar>(0) + img.total() * channels;
    for (uchar* i = img.ptr<uchar>(0); i < lastAddress; i += channels) addresses.push_back(i);   //Fast Enough
    size_t count = img.total() * amount / 100 / 2;
    for (size_t i = 0; i < count; i++)
    {
        size_t addressIndex = xor128() % addresses.size();   //Fast Enough, xor128() is a fast random number generator
        for (size_t j = 0; j < channels; j++)
        {
            *(addresses[addressIndex] + j) = 255;
        }   //Fast Enough
        addresses.erase(addresses.begin() + addressIndex);    // MAKES CODE EXTREMELY SLOW
    }
    for (size_t i = 0; i < count; i++)
    {
        size_t addressIndex = xor128() % addresses.size();   //Fast Enough, xor128() is a fast random number generator
        for (size_t j = 0; j < channels; j++)
        {
            *(addresses[addressIndex] + j) = 0;
        }   //Fast Enough
        addresses.erase(addresses.begin() + addressIndex);    // MAKES CODE EXTREMELY SLOW
    }
}

Я думаю, что перестановка векторных элементов после стирания элемента - это то, что делает мой код медленным (если я удалю address.erase, код будет работать быстро).

Is есть ли какой-либо метод fast для выбора каждого случайного элемента из коллекции (или диапазона чисел) не более одного раза?

Также: я почти уверен, что такой эффект уже существует. Кто-нибудь знает его название?

Ответы [ 3 ]

4 голосов
/ 29 мая 2020

Этот ответ предполагает, что у вас есть функция генератора случайных битов, поскольку std::random_shuffle требует этого. Я не знаю, как работает xor128, поэтому я воспользуюсь функциями библиотеки <random>.

Если у нас есть совокупность N элементов, и мы хотим выбрать группы по размеру j и k случайным образом из этой совокупности без пересечения, мы можем записать индекс каждого элемента на карте, перетасовать колоду, взять j карт, а затем вытянуть k карт. Все, что осталось, выбрасывается. Этого можно добиться с помощью библиотеки <random>. Ожидается ответ о том, как включить собственный ГПСЧ, как вы реализовали с помощью xor128.

Это предполагает, что random_device не будет работать в вашей системе (многие компиляторы реализуют его таким образом, что он всегда будет возвращать та же последовательность), поэтому мы заполняем случайный генератор текущим временем, как старый добрый srand, который делала наша мать.

Непроверено, так как я не знаю, как использовать OpenCV. Любой, кто хоть немного разбирается в этом, отредактируйте его соответствующим образом.

#include <ctime>     // for std::time
#include <numeric>   // for std::iota
#include <random>
#include <vector>

void effect1(Mat& img, float amount, std::mt19937 g)    // 0.0 ≥ amount ≥ 1.00
{
    std::vector<cv::Size> ind(img.total());
    std::iota(ind.begin(), ind.end(), 0);   // fills with 0, 1, 2, ...
    std::random_shuffle(ind.begin(), ind.end(), g);
    cv::Size count = img.total() * amount;

    auto white = get_white<Mat>();  // template function to return this matrix' concept of white
                                    // could easily replace with cv::Vec3d(255,255,255) 
                                    // if all your matrices are 3 channel?
    auto black = get_black<Mat>();  // same but... opposite

    auto end = ind.begin() + count;
    for (auto it = ind.begin(), it != end; ++it)
    {
        img.at(*it) = white;
    }
    end = (ind.begin() + 2 * count) > ind.end() ?
               ind.end() : 
               ind.begin() + 2 * count;
    for (auto it = ind.begin() + count; it != end; ++it)
    {
        img.at(*it) = black;
    }
}

int main()
{
    std::mt19937 g(std::time(nullptr)); // you normally see this seeded with random_device
                                        // but that's broken on some implementations
                                        // adjust as necessary for your needs
    cv::Mat mat = ... // make your cv objects

    effect1(mat, 0.1, g);

    // display it here

}

Другой подход

Вместо перетасовки индексов и извлечения карт из колоды предположите, что каждый пиксель имеет случайную вероятность переключения на белый, переход на черный или оставшийся прежним. Если ваша сумма равна 0,4, выберите случайное число от 0,0 до 1,0, при любом результате от 0,0 до 0,4 пиксель станет черным, а между 0,4 и 0,8 - белым, в противном случае он останется прежним.

Общий алгоритм :

given probability of flipping -> f
for each pixel in image -> p:
    get next random float([0.0, 1.0)) -> r
    if r < f
        then p <- BLACK
    else if r < 2*f
        then p <- WHITE

Вы не получите каждый раз одинаковое количество белых / черных пикселей, но это случайность! Мы все равно генерируем случайное число для каждого пикселя для алгоритма перетасовки. У него такая же сложность, если я не ошибаюсь.

2 голосов
/ 29 мая 2020

Также: я почти уверен, что такой эффект уже существует. Кто-нибудь знает его название?

Эффект, который вы описываете, называется шум соли и перца . Хотя мне известно о прямой реализации OpenCV, не существует. .

Я не уверен, зачем вы добавляете пиксели к вектору в коде, было бы гораздо больше смысла, а также было бы намного эффективнее напрямую работать с объектом Mat и изменять пиксель значение напрямую. Вы можете использовать функцию OpenCV inbuild Mat.at(), чтобы напрямую изменять значения пикселей на 0 или 255.

Я бы создал один l oop, который генерирует случайные индексы в диапазоне размер вашего изображения и напрямую управлять пикселями изображения. Таким образом, вы находитесь в O (n) для добавления шума. Вы также можете просто искать "OpenCV" и "шум соли и перца", я уверен, что уже существует много действительно эффективных реализаций.

0 голосов
/ 29 мая 2020

Еще выкладываю код попроще:

void saltAndPepper(Mat& img, float amount)
{
    vector<size_t> pixels(img.total());    // size_t = unsigned long long
    uchar channels = img.channels();
    iota(pixels.begin(), pixels.end(), 0);    // Fill vector with 0, 1, 2, ...
    shuffle(pixels.begin(), pixels.end(), mt19937(time(nullptr)));    // Shuffle the vector
    size_t count = img.total() * amount / 100 / 2;
    for (size_t i = 0; i < count; i++)
    {
        for (size_t j = 0; j < channels; j++)    // Set all pixel channels (e.g. Grayscale with 1 channel or BGR with 3 channels) to 255
        {
            *(img.ptr<uchar>(0) + (pixels[i] * channels) + j) = 255;
        }
    }
    for (size_t i = count; i < count*2; i++)
    {
        for (size_t j = 0; j < channels; j++)    // Set all pixel channels (e.g. Grayscale with 1 channel or BGR with 3 channels) to 0
        {
            *(img.ptr<uchar>(0) + (pixels[i] * channels) + j) = 0;
        }
    }
}
...