Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fill circle with gradient

Tags:

c++

opencv

I want fill circle with gradient color, like I show on bottom. I can't figure out easy way, how to do that. I can make more circles, but transitions are visible.

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

enter image description here

like image 879
Nejc Galof Avatar asked Nov 21 '25 09:11

Nejc Galof


1 Answers

All you need to do is create a function which takes in a central point and a new point, calculates the distance, and returns a grayscale value for that point. Alternatively you could just return the distance, store the distance at that point, and then scale the whole thing later with cv::normalize().

So let's say you have the central point as (50, 50) in a (100, 100) image. Here's pseudocode for what you'd want to do:

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

Note the steps here:

  1. Create the Euclidean distance function to calculate distance
  2. Create a floating point matrix to hold the distance values
  3. Loop through all rows and columns and assign a distance value
  4. Normalize to the range you want (you could stick with a float here instead of casting to uint8, but you do you)
  5. Flip the binary gradient, since distances farther away will be brighter---but you want the opposite.

Now for your exact example image, there's a gradient in a circle, whereas this method just creates the whole image as a gradient. In your case, if you want a specific radius, just modify the function which calculates the Euclidean distance, and if it's beyond some distance, set it to 0 (the value at the center of the circle, which will be flipped eventually to white):

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

Here is the above in actual C++ code:

#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();

}

Gradient image


A completely different method (though doing the same thing) would be to use the distanceTransform(). This function maps the distance from the center of a white blob to the nearest black value to a grayscale value, like we were doing above. This code is more concise and does the same thing. However, it can work on arbitrary shapes, not just circles, so that's cool.

#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();

}

Gradient from distance transform

like image 98
alkasm Avatar answered Nov 23 '25 00:11

alkasm