如何将数字 0 到 n 均匀分布到 m 个不同的容器中

How to evenly distribute numbers 0 to n into m different containers

本文关键字:数字 分布      更新时间:2023-10-16

我正在尝试为程序编写一种算法,以在图像上绘制均匀的垂直渐变。 即我想沿着图像的 m 行将像素颜色从 0 更改为 255,但找不到一个好的通用算法来做到这一点。

我尝试使用 opencv 实现这样的东西,但它似乎不起作用

#include <opencv2/opencv.hpp>
int main(){
  //Make the image white.
  cv::Mat Img(w_height, w_width, CV_8U);
  for (int y = 0; y < Img.rows; y += 1) {
      for (int x = 0; x < Img.cols; x++) {
        Img.at<uchar>(y, x) = 255;//White
      }
  }
  // try to create an even, vertical gradient
  for(int row = 0; row < Img.rows; row++ ){
    for(int col = 0; col < Img.cols; col++){
      Img.at<uchar>(row, col) = col % 256;
      } 
    }
  cv::imshow("Window", Img);
  cv::waitKey(0);
  return 0;
}

解决这个问题需要了解三个简单的技巧:

1. 插值:

从一个值逐渐变为另一个值的过程称为插值。有多种插值颜色值的方法:最简单的一种是线性插值每个分量,即以以下形式:

interpolated = start * (1-t) + dest * t .

哪里

  • start 是向值dest进行插值的值。
  • t表示插值应与目标值的接近程度,dest 01的刻度上,0是纯start色,1是纯dest色。

您会发现 RGB 颜色空间中的线性插值不会生成自然的颜色路径。作为高级步骤,您可以改用 HSV 颜色空间。有关颜色插值的更多信息,请参阅此问题。

2. 离散化:

不幸的是,插值会产生实数。因此,我们必须将它们离散化,以便能够将它们用作整数颜色值。最好的方法是使用 round() C++。

3. 求插值点:

现在,我们只需要在图像的每一行t一个实值插值点。我们可以通过分析我们希望看到的输出来推断出一个公式:

  • 对于最底部的行(第 1 行),我们希望有t == 0,因为这是我们希望纯起始颜色出现的地方。
  • 对于最上面的行(第 m 行),我们希望有t == 1因为这是我们希望纯目标颜色出现的地方。
  • 对于每隔一行,我们希望t与到最底部行的距离线性缩放。

实现此结果的公式为:

t = rowIndex / m

通过适当地改变这个公式,该方法可以很容易地适应其他梯度方向。

示例代码(使用线性插值,C++):

#include <algorithm>
#include <cmath>
Color interpolateRGB(Color from, Color to, float t)
{
    // Clamp __t__ to range [0,1]
    t = std::max(std::min(0.f, t), 1.f);
    // Interpolate each RGB component
    uint8_t r = std::roundf(from.r * (1-t) + to.r * t);
    uint8_t g = std::roundf(from.g * (1-t) + to.g * t);
    uint8_t b = std::roundf(from.b * (1-t) + to.b * t);
    return Color(r, g, b);
}
void fillWithGradient(Image& img, Color from, Color to)
{
    for(size_t row = 0; row < img.numRows(); ++row)
    {
        Color value = interpolateRGB(from, to, row / (img.numRows()-1));
        // Set all pixels of this row to __value__
        for(size_t col = 0; col < img.numCols(); ++col)
        {
            img.setPixel(row, col, value);
        }
    }
}

基本思想是使用n/(m-1)的除法r的其余部分,并在每次迭代时将其添加到n

#include <iostream>
#include <vector>
using namespace std;
vector<int> gradient( int n, int m ) {
  div_t q { 0, 0 };
  vector<int> grad(m);
  for( int i=1 ; i<m ; ++i ) {
    q = div( n + q.rem, m-1 );
    grad[i] = grad[i-1] + q.quot;
  }
  return grad;
}
int main() {
  for( int i : gradient(255,10) ) cout << i << ' ';
  cout << 'n';
  return 0;
}

输出:

0 28 56 85 113 141 170 198 226 255