【问题标题】:Fill circle with gradient用渐变填充圆圈
【发布时间】:2017-12-11 06:49:16
【问题描述】:

我想要用渐变颜色填充圆圈,就像我在底部显示的那样。我想不出简单的方法,如何做到这一点。 我可以制作更多圆圈,但可以看到过渡。

cv::circle(img, center, circle_radius * 1.5, cv::Scalar(1.0, 1.0, 0.3), CV_FILLED);
cv::circle(img, center, circle_radius * 1.2, cv::Scalar(1.0, 1.0, 0.6), CV_FILLED);
cv::circle(img, center, circle_radius, cv::Scalar(1.0, 1.0, 1.0), CV_FILLED);

【问题讨论】:

  • 大概您可以将亮度/灰度值设置为与中心距离的函数?
  • @EdChum 是的。取决于与中心的距离。
  • 是的,@EdChum 说只是创建一个函数来计算距中心的距离,然后根据该距离为其分配一个值。
  • 但是我需要创建更多圈子吗?
  • 不,你不需要。

标签: c++ opencv


【解决方案1】:

您需要做的就是创建一个函数,该函数接收一个中心点和一个新点,计算距离,并返回该点的灰度值。或者,您可以只返回距离,在该点存储 距离,然后稍后使用 cv::normalize() 缩放整个事物。

假设您在(100, 100) 图像中的中心点为(50, 50)。这是您想要做的伪代码:

function euclideanDistance(center, point)  # returns a float
    return sqrt( (center.x - point.x)^2 + (center.y - point.y)^2 )

center = (50, 50)
rows = 100
cols = 100

gradient = new Mat(rows, cols) # should be of type float

for row < rows:
    for col < cols:
        point = (col, row)
        gradient[row, col] = euclideanDistance(center, point)

normalize(gradient, 0, 255, NORM_MINMAX, uint8)
gradient = 255 - gradient

注意这里的步骤:

  1. 创建欧几里得距离函数来计算距离
  2. 创建一个浮点矩阵来保存距离值
  3. 循环遍历所有行和列并分配距离值
  4. 标准化为您想要的范围(您可以在此处坚持使用浮点数,而不是强制转换为 uint8,但您可以这样做)
  5. 翻转二值渐变,因为距离越远会越亮 --- 但您想要相反。

现在对于您的确切示例图像,圆圈中有一个渐变,而此方法只是将整个图像创建为渐变。在您的情况下,如果您想要一个特定的半径,只需修改计算欧几里得距离的函数,如果超出某个距离,请将其设置为 0(圆心的值,最终将翻转为白色) :

function euclideanDistance(center, point, radius)  # returns a float
    distance = sqrt( (center.x - point.x)^2 + (center.y - point.y)^2 )
    if distance > radius:
        return 0
    else
        return distance

这是上面的实际 C++ 代码:

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <cmath>

float euclidean_distance(cv::Point center, cv::Point point, int radius){
    float distance = std::sqrt(
        std::pow(center.x - point.x, 2) + std::pow(center.y - point.y, 2));
    if (distance > radius) return 0;
    return distance;
}

int main(){

    int h = 400;
    int w = 400;
    int radius = 100;
    cv::Mat gradient = cv::Mat::zeros(h, w, CV_32F);

    cv::Point center(150, 200);
    cv::Point point;

    for(int row=0; row<h; ++row){
        for(int col=0; col<w; ++col){
            point.x = col;
            point.y = row;
            gradient.at<float>(row, col) = euclidean_distance(center, point, radius);
        }
    }

    cv::normalize(gradient, gradient, 0, 255, cv::NORM_MINMAX, CV_8U);
    cv::bitwise_not(gradient, gradient);

    cv::imshow("gradient", gradient);
    cv::waitKey();

}


一个完全不同的方法(虽然做同样的事情)是使用distanceTransform()。该函数将从白色斑点中心到最近的黑色值的距离映射到灰度值,就像我们在上面所做的那样。这段代码更简洁并且做同样的事情。但是,它可以处理任意形状,而不仅仅是圆形,这很酷。

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>

int main(){

    int h = 400;
    int w = 400;
    int radius = 100;
    cv::Point center(150, 200);
    cv::Mat gradient = cv::Mat::zeros(h, w, CV_8U);
    cv::rectangle(gradient, cv::Point(115, 100), cv::Point(270, 350), cv::Scalar(255), -1, 8 );

    cv::Mat gradient_padding;
    cv::bitwise_not(gradient, gradient_padding);

    cv::distanceTransform(gradient, gradient, CV_DIST_L2, CV_DIST_MASK_PRECISE);
    cv::normalize(gradient, gradient, 0, 255, cv::NORM_MINMAX, CV_8U);

    cv::bitwise_or(gradient, gradient_padding, gradient);

    cv::imshow("gradient-distxform.png", gradient);
    cv::waitKey();

}

【讨论】:

  • 我认为画很多圈的解决方案会更容易实现
  • @KamilSzelag 我不确定我是否同意,但你的方法也完全正常。不同之处在于您的方法将根据每个像素最接近的半径离散灰度值,我的方法将为每个单独的距离分配唯一值。换句话说,您的渐变图像将具有radius 不同的灰度值,我的将具有不同距离灰度值的数量。可以对大半径产生影响。我想知道哪个更快?
  • 每个像素中的唯一值等于更好的质量,真的。我想知道光栅图像是否有明显的变化。我认为,您的方法更快。
  • @NejcGalof 当然,我添加了 C++ 代码。为了将来参考,如果您确实尝试实施解决方案,请编辑您的原始问题以添加代码。这样我就可以给出一个简单的提示来解决你的问题,或者可以复制/粘贴你的代码,而不必从头开始编写。这里没什么大不了的,但对于未来...... :)
  • @NejcGalof 我添加了另一种完全不同的方法来做到这一点,它使用 OpenCV 的 distanceTransform() 函数非常好和干净,它的作用与上述大致相同。它找到白色斑点(通过连接组件),找到这些斑点的中心,然后计算斑点内每个像素到最近的黑色像素的距离。这很酷,因为它可以在任意形状上工作。如果您在黑色图像上画一个白色圆圈,它与我展示的先前版本相同。但是您可以使用椭圆、框、任何形状的白色斑点来做到这一点!
【解决方案2】:

你必须画很多圈。每个圆圈的颜色取决于与中心的距离。下面是一个简单的例子:

void printGradient(cv::Mat &_input,const cv::Point &_center, const double radius)
{
   cv::circle(_input, _center, radius, cv::Scalar(0, 0, 0), -1);

   for(double i=1; i<radius; i=i++)
   {
       const int color = 255-int(i/radius * 255); //or some another color calculation
       cv::circle(_input,_center,i,cv::Scalar(color, color, color),2);
   }  
}

结果:

【讨论】:

    【解决方案3】:

    另一种尚未提及的方法是预先计算圆形渐变图像(使用上述方法之一,如公认的解决方案),并使用带有线性插值的仿射变形来创建其他此类圆形(不同大小)。如果翘曲和插值经过优化并且可能由硬件加速,这可能会更快。 结果可能比完美更差。

    我曾经用它为每一帧内窥镜成像创建一个单独的渐晕蒙版圈。比“手动”计算距离要快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-07
      • 1970-01-01
      • 2018-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多