访问随机图像像素的快速方法,最多一次



我正在学习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
}
}

我认为在擦除项目后重新排列矢量项目会使我的代码变慢(如果我删除 addresses.erase,代码将运行得很快(。

有没有快速的方法可以从集合(或数字范围(中最多选择一次每个随机项目?

另外:我很确定这种效果已经存在。有谁知道它的名字吗?

这个答案假设你有一个随机位生成器函数,因为std::random_shuffle需要它。我不知道xor128是如何工作的,所以我将使用<random>库的功能。

如果我们有N个项目的总体,并且我们想从该种群中随机选择大小jk组,没有重叠,我们可以在牌上写下每个项目的索引,洗牌组,抽j牌,然后抽k牌。剩下的一切都被丢弃了。我们可以通过<random>库来实现这一点。关于如何合并自定义 PRNG 的答案待定,就像您使用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

您不会每次都获得相同数量的白色/黑色像素,但这是随机性!无论如何,我们正在为洗牌算法的每个像素生成一个随机数。除非我弄错了,否则这具有同样的复杂性。

另外:我很确定这种效果已经存在。有谁知道它的名字吗?

你所描述的效果称为盐和胡椒噪音。不过,我知道OpenCV中没有直接的实现。

我认为在擦除项目后重新排列矢量项目是使 我的代码很慢(如果我删除 addresses.erase,代码会运行得很快(。

我不确定为什么要将像素添加到代码中的矢量中,直接处理Mat对象并直接更改像素值会更有意义,性能也更高。您可以使用 OpenCV 内置Mat.at()函数直接将像素值更改为 0 或 255。

我会创建一个循环,该循环在您的图像尺寸范围内生成随机索引并直接操作图像像素。这样你就在O(n(中增加了噪音。你也可以只搜索"OpenCV"和"盐和胡椒噪音",我相信已经有很多真正高性能的实现。

我还发布了一个更简单的代码:

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

最新更新