如何利用opencv等工具,获取图片中颜色比例或者边框颜色?

想要获取某商品图片(餐盘)的颜色比例或者边框的颜色?

想要获取图片颜色的占比值,或者边框的颜色。
img

#include
#include <opencv2/opencv.hpp>
#include <windows.h>
#include

using namespace std;
using namespace cv;

#define WINDOW_1 "原图"
#define WINDOW_2 "聚类图"

void KMean(Mat, Mat&, int, int);

int main()
{
Mat srcImage;
srcImage = imread("2.jpg", 17);
int point_x = srcImage.cols / 2;//圆心坐标x值
int point_y = srcImage.rows / 2;//圆心坐标y值
//圆形绘画
circle(srcImage, Point(point_x, point_y), srcImage.rows / 2 - 40, Scalar(0, 0, 255), -1);
Mat dstImage(Size(100, 600), CV_8UC3, Scalar(0, 0, 0));
if (srcImage.empty())
{
printf_s("图片读取失败");
return -1;
}
imshow(WINDOW_1, srcImage);

int clusters_num = 4;//kmeans算法的k值
int iterations = 10;//迭代次数

SYSTEMTIME st = { 0 };
GetLocalTime(&st);  //获取当前时间 可精确到ms
printf("%d-%02d-%02d %02d:%02d:%02d.%d\n",
    st.wYear,
    st.wMonth,
    st.wDay,
    st.wHour,
    st.wMinute,
    st.wSecond,
    st.wMilliseconds);

KMean(srcImage, dstImage, clusters_num, iterations);

SYSTEMTIME st2 = { 0 };
GetLocalTime(&st2);  //获取当前时间 可精确到ms
printf("%d-%02d-%02d %02d:%02d:%02d.%d\n",
    st2.wYear,
    st2.wMonth,
    st2.wDay,
    st2.wHour,
    st2.wMinute,
    st2.wSecond,
    st2.wMilliseconds);

imshow(WINDOW_2, dstImage);

waitKey();
return 0;

}

//K - Mean是一种对图像进行聚类的算法,属于分割聚类方法,这种方法不对聚类进行层次划分,只是通过分析聚类的性质和均值,将像素简单地划分为不相交的聚类。
//在开始算法之前,我们必须先看图像,然后想 : 在这张图像中我能看到多少个聚类 ?
//答案是K - 均值中的K,这意味着我们选择图像应该有多少个簇。要注意找到一个既不太大也不太小的集群。将所有的像素分为K - Cluster。
//一种简单的K均值方法是 : 给定K个输入聚类,选择K个随机像素和从这些像素中扩展聚类。
//我们可以通过这两个步骤对算法进行分类 :
//1) 我们通过分析图像中的每个像素到每个簇的距离来面对它们(注意:距离不是指像素到另一个像素的坐标距离,而是颜色的彩色距离,可以通过RGB空间中的欧氏距离轻松计算出来)。
//使用这种方法,我们开始分析这个K个簇,最初形成簇的单个像素是簇本身的中心(簇的中心 = 所有出现在图像中的像素的平均值)。
//并对每个像素和每个聚类进行比较,以找到最佳的聚类来放置所分析的像素。
//因此,我们将像素放入到从颜色距离而言距离较小的簇中。
//2) 在我们将所有像素放入它们的相对簇后,我们重新计算簇的质心。
//我们只需将像素的所有数值相加到集群中,然后将该值除以像素数。
//完成后,我们将新的质心分配给集群,并重复步骤2中的算法,将像素插入到新的计算出的集群中,距离更小,等等。
//3)当满足最大迭代数或质心不改变(达到收敛)时,算法终止。

//跟踪簇的平均值
class Average3b {

public:
int b;
int g;
int r;

Average3b() { ; }
Average3b(int b, int g, int r) {
    this->b = b;
    this->g = g;
    this->r = r;
}

};
//表示像素簇的类。
//存储与簇有关的信息,比如簇的每个点的像素值,像素数和簇的平均值(质心)
//我们也插入一些方法来计算平均值。
class Cluster {

public:
int pixel_num;
Average3b region_sum;//每个通道
Vec3b centroid;//质心
vector pixels;

Cluster() { ; }
Cluster(Vec3b centroid, int x, int y) {
    //初始时,当一个簇被创建时,它的总元素数为0。
    //当向簇中添加像素成员时,它将增加
    this->pixel_num = 0;

    //将簇和质心初始化为0
    this->region_sum = Average3b(0, 0, 0);
    this->centroid = Vec3b(0, 0, 0);

    //将像素添加到区域并计算新的中心(像素本身)
    add_pixel(centroid, x, y);
    calculate_center();

}

void calculate_center() {
    //质心是三个通道分别计算的
    this->centroid[1] = region_sum.g / pixel_num;
    this->centroid[0] = region_sum.b / pixel_num;
    this->centroid[2] = region_sum.r / pixel_num;

}
//添加一个像素,该像素的RGB值将会被加到总和里
void add_pixel(Vec3b pixel, int x, int y) {
    //将像素值添加到总区域总和
    this->region_sum.b += pixel[0];
    this->region_sum.g += pixel[1];
    this->region_sum.r += pixel[2];

    //将像素分配给对应的容器
    this->pixels.push_back(Point(x, y));
    this->pixel_num++;
}

void clear() {
    //重置簇容器,我们将在新一轮的迭代中重新构建它
    this->pixels.clear();
    //因为clear总是在新的质心计算后调用,所以我们将每个3通道的质心值分配给各个通道。这样,我们就可以说簇又是由新的起点组成的。
    this->region_sum.b = centroid[0];
    this->region_sum.g = centroid[1];
    this->region_sum.r = centroid[2];

    //将簇的像素个数重新置为1
    this->pixel_num = 1;

    //我们唯一没有重置的是质心,因为它已经从k - means算法的另一个调用中修改过了。
}

};

void KMean(Mat srcImage_, Mat& dstImage_, int clusters_num, int iterations /, int t/) {

//簇的一个容器,来跟踪图像的变化
vector<Cluster> clusters;


//1】生成K个随机点作为起始质心
 //随机坐标和像素值
int rand_x, rand_y;
Vec3b pixel;
for (int i = 0; i < clusters_num; i++) {
    rand_x = rand() % srcImage_.rows;
    rand_y = rand() % srcImage_.cols;
    pixel = srcImage_.at<Vec3b>(rand_x, rand_y);
    clusters.push_back(Cluster(pixel, rand_x, rand_y));
}
//2】迭代
//K - Means的开始:我们将算法的所有逻辑放入一个for循环中
//这是因为,我们可以进行固定次数的迭代:如果过程中算法收敛,它的质心不再变化,我们则打破for
for (int i = 0; i < iterations; i++) {

    //在每次迭代中,我们重新初始化一些变量,例如距离和索引,这将帮助我们在每次迭代中找到集群最小阈值和索引
    float distance;
    int index;

    //3】遍历图像每个像素,以选择它们属于哪个集群 :
    for (int x = 0; x < srcImage_.rows; x++) {
        for (int y = 0; y < srcImage_.cols; y++) {
            //现在,对于一个普通的(x, y)像素,分析这个像素属于哪个集群
            //我们遍历k个簇,寻找与该像素距离最近的簇
            float min_dist = FLT_MAX;
            for (int k = 0; k < clusters.size(); k++) {
                //得到距离
                distance = norm(srcImage_.at<Vec3b>(x, y), clusters.at(k).centroid);
                //check
                if (distance < min_dist) {
                    //更新索引和找到的最小距离
                    index = k;
                    min_dist = distance;
                }
            }
            //将像素添加到其簇中
            clusters.at(index).add_pixel(srcImage_.at<Vec3b>(x, y), x, y);

        }
    }
    //4】当所有像素都属于这个簇后,对于每个簇,我们需要计算新的质心
    //我们还检查新的质心是否与旧的质心有显著的差异
    //如果差异很大,则意味着我们需要使用新的质心值再次迭代,以将像素重新分组到集群中。
    //如果质心没有太大变化,那么就打破这个循环进入下一步
    //我们用一个布尔值变量changed来作为标识符,如果一些质心发生了重大的改变,我们将changed设置为true,并计算簇新的质心。
    bool changed = false;

    for (int k = 0; k < clusters.size(); k++) {
        //获取当前质心作为旧的质心
        Vec3b old_centroid = clusters.at(k).centroid;
        //计算新的质心
        clusters.at(k).calculate_center();

        //检查新质心是否与旧质心相差显着
        if (norm(old_centroid, clusters.at(k).centroid) > 10) {
            changed = true;
        }
    }

    //现在检查一些质心是否发生了变化:如果是这样,我们需要继续迭代以找到质心的收敛性,或者直到所有迭代都被耗尽。
    //我们还检查迭代是否完成 : 如果i + 1等于最大迭代次数,这意味着在下一次迭代中,我们将停止for循环 :
    //我们通过打破if来避免这种情况,避免清除每个集群的像素向量。

    //如果他们都没有改变
    if (!changed || i + 1 == iterations) {
        //打破循环,用集群去构建目标镜像
        break;
        iterations = i;
    }

    //如果改变了,至少需要进行另一次迭代以获得收敛,重置簇但保持新计算的质心
    for (int k = 0; k < clusters.size(); k++) {
        clusters.at(k).clear();
    }

}

//5】迭代结束:找到了所有可能的集群(无论是收敛还是迭代结束)。
//让我们构建最终的图像。

Mat showImage = Mat(100, 600, CV_8UC3);
float pixelse_sum = srcImage_.rows * srcImage_.cols;
int col_star = 0;
int col_end = 0;
int pixel_sum = 0;
float pixel_per = 0.00;
for (int k = 0; k < clusters.size(); k++) {
    pixel_sum += clusters.at(k).pixel_num;
}
for (int k = 0; k < clusters.size(); k++) {

    col_end += clusters.at(k).pixel_num / pixelse_sum * 600;
    for (int i = col_star; i < col_end; i++)
    {
        //line(showImage, Point2i(i, 0), Point2i(i, 599), clusters.at(k).centroid);
        line(showImage, Point2i(i, 0), Point2i(i, 599), clusters.at(k).centroid);
    }
    pixel_per = clusters.at(k).pixel_num;
    pixel_per = pixel_per / pixel_sum;
    printf("颜色:RGB(%d,%d,%d)   占比:%f %%\n", clusters.at(k).centroid[2],clusters.at(k).centroid[1], clusters.at(k).centroid[0], pixel_per);
    col_star = col_end;
}
showImage.copyTo(dstImage_);
//x和y坐标来跟踪当前被分析的像素

}

可以看看KMEANS算法
1,K-means算法原理
K-means算法需要我们自己定义K值,如前面知乎的问题,需要提取图片的五种基本色,所以我们就定义K为5,即将图片分为5个簇。means是均值的意思,在本问题背景下,均值代表每个簇的颜色均值。

指定K值后,我们随机生成五个像素坐标,并取这五个像素坐标的颜色作为五个簇的初始均值。

需要注意的是,K-means算法关注的是图像的像素值,我们需要将各个簇包含的像素的像素值记录下来,而不是像素坐标。

然后我们开始迭代,迭代次数也是自己定义的,每一次迭代,我们都遍历图像所有像素,并计算该像素与各个簇的颜色均值的颜色距离,选择最接近的簇,将该像素值加入到此簇中(以便计算该簇新的均值)。

在一次遍历像素完毕后,都需要重新计算各个簇的颜色均值,并判断该新的均值与上次均值是否有差别,如果没有则说明收敛了,就无需继续迭代了。如果相差巨大,则需要再一次迭代。

如果需要再一次迭代,便将所有簇的元素清空,仅保留计算的均值,然后再一次遍历所有像素,重复上一步。

2,算法实现
主函数:读取图片,定义Kmeans算法的K值以及迭代次数,并对图片进行K-means算法。


int main()
{
  Mat srcImage;
  srcImage = imread("4.jpg",17);
  Mat dstImage(Size(100,600), CV_8UC3, Scalar(0,0,0));
  if (srcImage.empty())
  {
    printf_s("图片读取失败");
    return -1;
  }
  imshow(WINDOW_1, srcImage);

  int clusters_num = 5;//kmeans算法的k值
  int iterations = 10;//迭代次数
  KMean(srcImage, dstImage, clusters_num, iterations);
  imshow(WINDOW_2, dstImage);

  waitKey();
  return 0;
}



此函数就实现了对输入图像srcImage 进行聚类操作,并将结果输出到dstImage参数中。clusters_num为K值,iterations为迭代次数。

除了K-means算法函数本身,程序还定义了一个簇类,用来存放簇的一些成员变量以及对簇的操作,例如向簇中添加一个像素,清空簇等等操作。
详情查看:https://blog.csdn.net/qq_43667130/article/details/114276474

采纳一下,
这个可以验证

完整代码如下:

#include <opencv2\opencv.hpp>
using namespace cv;
 
vector<char*>  listFiles(const char * dir);
int main()
{
    Mat img = imread("D:\\OpencvTest\\test.jpg");
    Scalar color = img.at<Vec3b>(150, 150);//读取原图像
    for (size_t row=0; row<50; row++)
        for (size_t col=0;col<img.cols;col++)
        {
            //设置原图像中某点的BGR颜色值
            img.at<Vec3b>(row, col) = Vec3b(color(0), color(1), color(2));
 
        }
    cv::circle(img, Point(150, 150), 4, cv::Scalar(0, 0, 255));//在图像中画出特征点,2是圆的半径
    imshow("img", img);
    waitKey(0);
    return 0;
}