想要获取某商品图片(餐盘)的颜色比例或者边框的颜色?
想要获取图片颜色的占比值,或者边框的颜色。
想要获取某商品图片(餐盘)的颜色比例或者边框的颜色?
想要获取图片颜色的占比值,或者边框的颜色。
#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坐标来跟踪当前被分析的像素
}