使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果

这篇具有很好参考价值的文章主要介绍了使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

2023.4.16日更新

1.利用一阶矩增加了草莓等水果的质心绘制。

2.绘制出了生长方向。

原为本人机器人视觉作业。参考文章http://t.csdn.cn/eQ0qp(目测是上一届的学长)

要求:在网络上寻找水果重叠在一起的图片、经过一系列图像处理,完成每个水果的分割,并单独标记出来。

  1. 导入图片

在网上找到了一些水果叠在一起的图片,选一个作为本次调试的样图,导入图片如下。

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

为显示方便,将图像缩小两倍,缩小同前几次作业相同,代码如下

2.颜色通道选择

首先尝试了不同通道单独二值化。发现效果不如人意。

以红色通道为例。

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

可以看到四个阈值大范围都不能完成较好的分割(也可能是我哪里出错了)。

为解决不同光照情况对图像的影响,对rgb图像进行归一化,代码如下。

Mat version_lesson::normalize_rgb(Mat &image)
{
	Mat normalize(image.rows, image.cols, CV_32FC3);
	for (int i = 0; i < image.rows; i++){
		for (int j = 0; j < image.cols; j++){
			double epslon = 0.000001;//防止rgb均为0
			int b = image.at<Vec3b>(i, j)[0];
			int g = image.at<Vec3b>(i, j)[1];
			int r = image.at<Vec3b>(i, j)[2];
			double sum = b + g + r + epslon;
			normalize.at<Vec3f>(i, j)[2] = r / sum;
			normalize.at<Vec3f>(i, j)[1] = g / sum;
			normalize.at<Vec3f>(i, j)[0] = b / sum;
		}
	}
	return normalize;
}

归一化后图像与之前的对比如下所示。

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

要想看到归一化后消除亮度影响效果,可以绘制其直方图,绘制代码及绘制后的直方图如下所示。

 直方图部分最终代码中没有,这边做测试用。

Mat version_lesson::print_hist_demo(Mat &img) {
	int bins = 256;
	int hist_size[] = { bins };
	float range[] = { 0,256 };
	const float *ranges[] = { range };
	MatND hist;
	int channels[] = { 0 };
	//计算出灰度直方图
	calcHist(&img, 1, channels, Mat(), hist, 1, hist_size, ranges);
	//画出直方图
	double max_val;
	minMaxLoc(hist, 0, &max_val, 0, 0);//定位矩阵中最小值、最大值的位置
	int scale = 2;
	int hist_height = 256;
	Mat hist_img = Mat::zeros(hist_height, bins*scale, CV_8UC3);//创建一个全0的特殊矩阵
	for (int i = 0; i < bins; i++)
	{
		float bin_val = hist.at<float>(i);
		int inten = cvRound(bin_val*hist_height / max_val);//要绘制高度
		//画矩形
		rectangle(hist_img, Point(scale*i, hist_height - 1), Point((i + 1)*scale - 1, hist_height - inten), CV_RGB(255, 255, 255));
	}
	return hist_img;
}

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

可以发现是消除了亮度的影响


3.二值化处理

由于图像主要分布在红色、绿色通道空间内,因此将归一化后的图像进行红绿分割,即灰度化处理。

思想于平常灰度化亮度值不同,将比较方式改为红色绿色通道内的值大小,详细见http://t.csdn.cn/eQ0qp

代码如下:

Mat version_lesson::normalize_gray(Mat &image) {
	Mat rg_gray(image.rows, image.cols, CV_8UC1);
		for (int i = 0; i < image.rows; i++){
			for (int j = 0; j < image.cols; j++){
				//读取rg值
				double g = image.at<Vec3f>(i, j)[1];
				double r = image.at<Vec3f>(i, j)[2];
				if (r > g)
					rg_gray.at<uchar>(i, j) = (r - g) * 255;
				else
					rg_gray.at<uchar>(i, j) = 0;
			}
		}
	return rg_gray;
}

处理后效果如下

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

得到灰度图就可以进行阈值分割.使用代码如下

Mat version_lesson::threshold_fenge(Mat &image) {
	Mat binary;
	threshold(image, binary, 100, 255, THRESH_OTSU);
	//imshow("OTSU二值化图像", binary);
	return binary;
}

 其中THRESH_OTSU过滤方法为自适应阈值。(这边借助了imlab调阈值后发现OTSU效果不错),处理结果如下


使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

 可以看到右下方有噪点,中部由于梗的存在也有。

4.形态学操作

对处理后的图像进行形态学操作。

填补小空洞:开运算

梗处理:膨胀

为了防止图像严重失真,核不宜过大,故先采用一次开操作将白色噪点填充,后逐渐降低膨胀的核大小,一连四次膨胀,代码如下:

Mat version_lesson::morphology_do(Mat &image) {
	Mat dst2;
	Mat kerne_open = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1));
	morphologyEx(image, dst2,MORPH_OPEN, kerne_open);
	//imshow("开运算操作", dst2);
	Mat kernel_dilate1 = getStructuringElement(MORPH_RECT, Size(10, 10), Point(-1, -1));
	morphologyEx(dst2, dst2, MORPH_DILATE, kernel_dilate1);
	//imshow("膨胀操作1", dst2);
	Mat kernel_dilate2 = getStructuringElement(MORPH_RECT, Size(8, 8), Point(-1, -1));
	morphologyEx(dst2, dst2, MORPH_DILATE, kernel_dilate2);
	//imshow("膨胀操作2", dst2);
	Mat kernel_dilate3 = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1));
	morphologyEx(dst2, dst2, MORPH_DILATE, kernel_dilate3);
	//imshow("膨胀操作3", dst2);
	Mat kernel_dilate4 = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	morphologyEx(dst2, dst2, MORPH_DILATE, kernel_dilate4);
	//imshow("膨胀操作4", dst2);
	return dst2;
}

逐步显示出的结果如下:

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

5.分割操作 

对其使用分水岭分割操作。

由于分水岭操作第一个参数需要使用8bit3通道的图像,而上述归一化后产生的图像时32bit3通道的图像,因此这边重新定义了一个额外的归一化图像,用于生成分水岭操作的第一个参数。(生成的图像与第一个图象基本相同,但rg灰度化后会产生明显差异,故不适用后续操作,这边只用它当分水岭的参数)代码如下。

Mat version_lesson::normalize_rgb2(Mat &image)
{
	Mat normalize(image.rows, image.cols, CV_8UC3);
	for (int i = 0; i < image.rows; i++) {
		for (int j = 0; j < image.cols; j++) {
			double epslon = 0.000001;//防止rgb均为0
			int b = image.at<Vec3b>(i, j)[0];
			int g = image.at<Vec3b>(i, j)[1];
			int r = image.at<Vec3b>(i, j)[2];
			double sum = b + g + r + epslon;
			normalize.at<Vec3b>(i, j)[2] = int((r / sum)*255);
			normalize.at<Vec3b>(i, j)[1] = int((g / sum)*255);
			normalize.at<Vec3b>(i, j)[0] = int((b / sum)*255);
		}
	}
	return normalize;
}

基于距离图的分水岭分割代码操作如下 

Mat version_lesson::water_fenge(Mat &image, Mat &src, Mat &gray) {//
	Mat dist;
	Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
	distanceTransform(image, dist, DIST_L2, 5);
	normalize(dist, dist, 0, 255, NORM_MINMAX);
	double my_minv = 0.0, my_maxv = 0.0;
	minMaxIdx(dist, &my_minv, &my_maxv);
	Mat sure_fg;//注水点
	threshold(dist, sure_fg, 0.8 * my_maxv, 255, THRESH_BINARY);
	sure_fg.convertTo(sure_fg, CV_8U);
	Mat element1 = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
	dilate(sure_fg, sure_fg, element, Point(-1, -1), 3);
	sure_fg.convertTo(sure_fg, CV_8U);
	imshow("sure_fg", sure_fg);
	Mat sure_bg;
	dilate(image, sure_bg, element, Point(-1, -1));
	imshow("sure_bg", sure_bg);
	Mat unkonwn = Mat(image.size(), CV_8U);
	unkonwn = sure_bg - sure_fg;
	imshow("unkonwn", unkonwn);
	Mat label_img = Mat(image.size(), CV_32S);
	int num = connectedComponents(sure_fg, label_img, 8);
	label_img = label_img + 1;
	for (int i = 0; i < unkonwn.rows; i++){
		for (int j = 0; j < unkonwn.cols; j++){
			if (((int)unkonwn.at<uchar>(i, j)) == 255){
				label_img.at<signed int>(i, j) = 0;
			}
		}
	}
	watershed(src, label_img);
	double maxVal = 0;
	double minVal = 0;
	minMaxLoc(label_img, &minVal, &maxVal);
	Mat dst = Mat::zeros(src.size(), CV_8U);
	label_img.convertTo(dst, CV_8U, 255.0 / (maxVal - minVal), -255.0 * minVal / (maxVal - minVal));
	imshow("marks", dst);
	waitKey(0);
	return dst;
}

得到图像如下

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

              与原图像对比可以看到,分割效果明显。

6.其他图片测试

  1. 测试图片1如下(来源ppt)

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

2.测试图片2如下(来源百度)

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

3.测试图片3如下(来源百度)

 使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

2023.4.16增设内容:

质心可以使用一阶矩进行计算,生长方向可以用绘制下极值点来拟合、绘制

大部分是参考了上文中博客的绘制方法,但由于分水岭效果较差,导致最后绘制出的图像十分杂乱,质心、极值点多的一批。

为解决问题,我在前面又加了一个自适应阈值的分割,以便将图片转成适合求解距的黑白图像。

代码如下:

void version_lesson::orientation(Mat& src, Mat ref)
{
	Mat binary;
	threshold(ref, binary, 100, 255, THRESH_OTSU);
	imshow("binary", binary);
	Mat element1 = getStructuringElement(MORPH_RECT, Size(3, 3));
	Mat er;
	erode(binary, er, element1);
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(er, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
	for (int i = 0; i < contours.size(); i++){
		drawContours(src, contours, i, Scalar(0, 255, 255), 2, 8, hierarchy, 0,Point());
		Mat tmp(contours.at(i));
		Moments moment = moments(tmp, false);
		if (moment.m00 != 0){
			int x = cvRound(moment.m10 / moment.m00);//计算重心横坐标
			int y = cvRound(moment.m01 / moment.m00);//计算重心纵坐标
			circle(src, Point(x, y), 5, Scalar(235, 191, 0), -1);//绘制实心圆
			int minyx = contours[i][0].x;//当前轮廓上极值点横坐标赋初值
			int minyy = contours[i][0].y;//当前轮廓上极值点纵坐标赋初值
			int maxyx = contours[i][0].x;//当前轮廓下极值点横坐标赋初值
			int maxyy = contours[i][0].y;//当前轮廓下极值点纵坐标赋初值
			for (int j = 0; j < contours[i].size(); j++){
				if (minyy > contours[i][j].y){
					minyy = contours[i][j].y;
					minyx = contours[i][j].x;
				}
				if (maxyy < contours[i][j].y){
					maxyy = contours[i][j].y;
					maxyx = contours[i][j].x;
				}
			}
			circle(src, Point(maxyx, maxyy), 5, Scalar(0, 255, 0), -1);//绘制当前轮廓下极值点
			if (maxyx != x){
				double k = (maxyy - y) / (maxyx - x);//斜率
				double b = y - k * x;//纵向偏移
				double x1 = (minyy - 30 - b) / k;//上极值点纵坐标对应于直线上的横坐标
				arrowedLine(src, Point(maxyx, maxyy),
					Point(x1, minyy - 30), Scalar(255, 255, 0), 2, LINE_AA);//绘制生长方向线段(带箭头)
			}
			else{
				arrowedLine(src, Point(maxyx, maxyy),
					Point(x, minyy - 30),Scalar(255, 255, 0), 2, LINE_AA);//绘制生长方向线段(带箭头)
			}
		}
	}
}

绘制效果如下

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

这种方法存在不足之处,仍没有趋于完美,取决于自己分水岭的分割效果,可以看以下“失败”范例。

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

可以看到橙子由于分水岭分割出来存在一些空洞,故无法较为完美的拟合出唯一的质心

下两组表现了当水果数目增多,这个差异会越来越大

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果,opencv,c++,计算机视觉

且实在无法规避的一件事情为,这种识别方法终归是不太完美的,尤其需要对重心进行一定的筛选,才能选出没有干扰的重心。时间问题这边也没法开展进一步的修改。

比较有趣的一件事情是,发现了分水岭图像分割还受图像大小的影响,当我缩放过于小时,分水岭往往会把多个水果分割成一个,而重新resize大一些的时候,往往会比较准确。

生长方向往往可以使用其他方法绘制,这个方法仍具有局限性

 下附主函数代码:

#include<opencv2\opencv.hpp>
#include <version_lesson.h>
#include<quickopencv.h>
#include<iostream>
using namespace std;
using namespace cv;

int main(int argc, char **argv) {
	version_lesson vl;
	QuickDemo qd;
	Mat dst, gray, red, morphology, water;
	Mat src = imread("D:/Open CV/picture/柿子.jpg");
	src = qd.resize_demo(src);
	imshow("原图", src);
	red = vl.normalize_rgb(src);
	src = vl.normalize_rgb2(src);
	//imshow("rgb归一化后", red);
	//gray=vl.rgb2hsi(src);
	gray = vl.normalize_gray(red);
	//imshow("灰度图像", gray);
	dst = vl.threshold_fenge(gray);
	//imshow("OTSU二值化图像", dst);
	morphology = vl.morphology_do(dst);
	//imshow("形态学操作图像", morphology);
	//dst = vl.threshold_fenge(red);
//src = vl.normalize_rgb(src);
//imshow("rgb归一化后",src);
//cvtColor(src, gray, COLOR_BGR2GRAY);
//dst = vl.threshold_fenge(gray);
imshow("原图", src);
//green=vl.rgb_divide(src);
//erzhi = vl.threshold_fenge(green);
//hist = vl.print_hist_demo(red);
//imshow("绿色通道直方图", hist);
	water = vl.water_fenge(dst, src, gray);
	vl.orientation(src, water);
	imshow("water", water);
	imshow("生长", src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}//


主要函数及引用关系见上。

7.总结文章来源地址https://www.toymoban.com/news/detail-757754.html

  1. 由于灰度化是使用红绿分割,导致绿色水果+绿色背景或红色水果+红色背景会严重失真甚至分割不出来。
  2. 基本完成了分割,而参考的博客中(目测是上一届学长写的)没有使用rgb归一化完成最后的处理,其分水岭第一个参数的格式问题这边优化解决了。

到了这里,关于使用opencv c++完成图像中水果分割(分水岭、形态学操作、通道处理)单独标记每个水果的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 图像分割 - 分水岭算法

    目录 1. 介绍 2.  分水岭算法的实现 距离变换 连接连通分量 3. 代码 图像是由x,y表示的,如果将灰度值也考虑进去的话,那么一幅图像需要一个三维的空间去表

    2024年02月02日
    浏览(32)
  • (数字图像处理MATLAB+Python)第十章图像分割-第四,五节:分水岭分割和综合案例

    分水岭分割 :图像处理中常用的一种分割方法,它基于图像中灰度或颜色的变化来划分不同的区域。分水岭分割算法的原理是基于地理学上的分水岭概念。将图像看作一个 地貌图 ,在图像中低洼的部分被看作水池,而高处则表示山脉。通过在图像中加入水并让其逐渐充满,

    2024年02月10日
    浏览(48)
  • [图像处理]14.分割算法比较 OTSU算法+自适应阈值算法+分水岭

    参考文献: OTSU阈值分割+孔洞填充+海陆分离_SwordKii的博客-CSDN博客 drawContours函数_普通网友的博客-CSDN博客_drawcontours R329-opencv阈值分割算法——自适应阈值_Third Impact的博客-CSDN博客_opencv自适应阈值分割 分水岭算法的python实现及解析_进不去的博客-CSDN博客_python分水岭算法 分水

    2024年02月09日
    浏览(50)
  • 【实战篇:粘连物体分割——利用分水岭算法实现糖豆分割检测】

    通过pycharm安装时空门 问题: 讲一下分水岭算法的原理、实现步骤、以及应用。 回答: 分水岭算法是一种基于图像变换与分割的图像分析算法,主要用于图像分割。该算法可以解决很多图像处理领域的问题,例如医学图像分析、面部识别、数字水印等。下面将详细介绍分水岭

    2024年02月03日
    浏览(36)
  • OpenCV——分水岭算法

      分水岭算法是一种图像分割常用的算法,可以有效地将图像中的目标从背景中分离出来。本文以OpenCV库中的分水岭算法为基础,介绍图像分割中的常用概念和算法原理,并结合实际案例展示分水岭算法的应用。   图像分割指的是将图像分成多个不同的区域或对象的过程

    2024年02月12日
    浏览(39)
  • OPENCV实战分水岭法一

    2024年02月09日
    浏览(40)
  • [SDR] GNU Radio 系列教程(十四) —— GNU Radio 低阶到高阶用法的分水岭 ZMQ 的使用详解

    目录 1、前言 2、ZMQ 块的类型 3、ZMQ 块的使用 4、DEMO 4.1 同一台电脑上的两个流程图 4.2 不同电脑上的两个流程图 4.3 作为 REQ/REP 服务器的 Python 程序 4.4 作为 PUSH/PULL 服务器的 Python 程序 4.5 处理流程图数据的 Python 程序 参考链接 学会使用 GNU Radio 中的 ZMQ,是从低阶使用者向高阶

    2023年04月26日
    浏览(34)
  • 比特币上市首日:重要分水岭!

    作者:秦晋 如果按美国东部时间计算,今天是比特币现货ETF上市交易的第一天。也是具有历史纪念意义的一天。昨天,美国证券交易委员会正式批准11只比特币现货ETF进入证券交易所展开交易。这是加密金融与传统金融之间的一个重要「分水岭」时刻。 比特币现货ETF在开市交

    2024年01月16日
    浏览(42)
  • OpenCV数字图像处理基于C++:图像分割

    图像阈值化分割是一种常用的、传统的图像分割技术,因其 实现简单、计算量小、性能比较稳定 而成为图像分割中基本和应用广泛的分割技术。特别 适合于目标和背景占据不同灰度级范围的图像 。不仅 可以极大地压缩数据量 ,而且大大 简化了分析和处理的步骤 ,是进行

    2024年02月11日
    浏览(63)
  • OpenCV - C++实战(06) — Grabcut图像分割

    目录 第6章  图像分割 6.1 Grabcut实现 6.1.1 定义前景和背景 6.1.2   cv::grabCut() 6.1.3 cv::compare() 6.1.4 算法实现 ​​​​​​​ Github代码地址:GitHub - Qinong/OpenCV         Opencv提供了一种常用的图像分割算法Grabcut。Grabcut算法比较复杂,计算量也很大,但有很高的精确度。 6

    2024年02月07日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包