a135433
2021-07-27 15:18
采纳率: 33.3%
浏览 281

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

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

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

  • 点赞
  • 收藏

4条回答 默认 最新

  • A135433a 2021-08-03 21:13
    已采纳

    #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坐标来跟踪当前被分析的像素
    

    }

    点赞 打赏 评论
  • powertoolsteam 2021-07-27 15:48

    可以看看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

    点赞 打赏 评论
  • 王大师王文峰 2021-07-27 17:36

    采纳一下,
    这个可以验证

    点赞 打赏 评论
  • 王大师王文峰 2021-07-27 18:26

    完整代码如下:

    #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;
    }
    
    
    点赞 打赏 评论

相关推荐 更多相似问题