opencv图像分割,统计图像中的特定灰度的点的数量

问题描述

这是一幅基因芯片的荧光图像,检测图像的ROI区域,对这个区域内的阴性点(弱)和阳性点(强)的数量进行统计,并标出点的位置。
在这里插入图片描述

ROI区域检测:

思路:
(1)观察到图像对比度很低,首先对图像进行对比度增强
(2)图像分割需要获得边缘信息,用canny算子检测边缘
(3)对图像做闭运算,图像中很小的点江北腐蚀掉,从而显现出大的边缘
(4)用findContours方法找出边缘
(5) boundingRect方法检测外轮廓
(6)获得ROI并返回裁剪后的图片
代码:

Mat get_ROI_image(Mat &src_image)
{
	Mat imageConvert;
	src_image.convertTo(imageConvert, src_image.type(), 3, 0);//对比度增强y = alpha*x + beta; alpha = 3, beta = 0;

	Mat src_gray;
	cvtColor(imageConvert, src_gray, COLOR_BGR2GRAY);

	//使用Canny检测边缘  
	Mat canny_image;
	Canny(src_gray, canny_image, 80, 126, (3, 3));
	//imshow("canny_image", canny_image);

	//高级形态学闭运算函数  
	Mat closed_image;
	//自定义形态学元素结构
	Mat element5(9, 9, CV_8U, cv::Scalar(1));//5*5正方形,8位uchar型,全1结构元素
	morphologyEx(canny_image, closed_image, cv::MORPH_CLOSE, element5);
	//imshow("closed_image", closed_image);
	//waitKey(0);


	//外部加框
	//检测连通域,每一个连通域以一系列的点表示,FindContours方法只能得到第一个域
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(closed_image, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//CV_RETR_EXTERNAL只检测外部轮廓,可根据自身需求进行调整

	Rect maxRect, secondRect;
	for (int index = 0; index >= 0; index = hierarchy[index][0])
	{
		Rect rect = boundingRect(contours[index]);//检测外轮廓
		if (index == 0)
		{
			maxRect = rect;
			secondRect = rect;
		}
		if (rect.area() > maxRect.area())
		{
			secondRect = maxRect;
			maxRect = rect;
		}

	}
	Mat ROI_image;
	ROI_image = src_image(secondRect);

	rectangle(src_image, secondRect, Scalar(0, 0, 255), 3);//对外轮廓加矩形框
	imshow("src_image_plus_rect", src_image);
	imwrite("src_image_plus_rect.bmp", src_image);

	cout << "完成检测";
	return ROI_image;
}

检测结果
在这里插入图片描述

两类点的识别与数量统计:

找阳性点思路:
(1)对ROI图像做ACE处理,得到高对比度的图像
(2)然后对图像进行高阈值二值化处理,以获得阳性点
(3)用canny找出边缘
(4)用findContours方法找出轮廓
(5)drawContours方法画出轮廓
(6)返回轮廓数量近似为阳性点数量

找阴性点思路:
(1)对ROI图像做ACE处理,得到高对比度的图像
(2)然后对图像进行低阈值二值化处理,以获得全部点
(3)用canny找出边缘
(4)用findContours方法找出轮廓
(5)drawContours方法画出轮廓
(6)阴性点数量=全部点的轮廓数量-阳性点的轮廓数量

ACE代码:

Mat ACE(Mat &src, int C = 3, int n = 3, float MaxCG = 7.5)
{
	int rows = src.rows;
	int cols = src.cols;

	Mat meanLocal; //图像局部均值  
	Mat varLocal;  //图像局部方差  
	Mat meanGlobal;//全局均值
	Mat varGlobal; //全局标准差  

	blur(src.clone(), meanLocal, Size(n, n));
	Mat highFreq = src - meanLocal;//高频成分 

	varLocal = matrixWiseMulti(highFreq, highFreq);
	blur(varLocal, varLocal, Size(n, n));
	//换算成局部标准差  
	varLocal.convertTo(varLocal, CV_32F);
	for (int i = 0; i < rows; i++){
		for (int j = 0; j < cols; j++){
			varLocal.at<float>(i, j) = (float)sqrt(varLocal.at<float>(i, j));
		}
	}
	meanStdDev(src, meanGlobal, varGlobal);
	Mat gainArr = 0.5 * meanGlobal / varLocal;//增益系数矩阵  

	//对增益矩阵进行截止  
	for (int i = 0; i < rows; i++){
		for (int j = 0; j < cols; j++){
			if (gainArr.at<float>(i, j) > MaxCG){
				gainArr.at<float>(i, j) = MaxCG;
			}
		}
	}
	gainArr.convertTo(gainArr, CV_8U);
	gainArr = matrixWiseMulti(gainArr, highFreq);
	Mat dst2 = meanLocal + C*highFreq;
	return dst2;
}

ACE处理后的效果:
在这里插入图片描述

计算点的数量代码:

size_t cal_point_num(Mat &img, String str)
{
	Mat canny_output;
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	size_t point_num;
	CvFont font;
	
	// canny 边缘检测
	Canny(img, canny_output, thresh, thresh * 2, 3);
	//imshow("canny", canny_output);

	// 寻找轮廓
	findContours(canny_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

	Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
	// 画出轮廓
	for (size_t i = 0; i< contours.size(); i++) {
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawing, contours, (int)i, color, 2, 8, hierarchy, 0, Point());
	}
	point_num = contours.size();
	imshow(str, drawing);
	imwrite(str, drawing);
	return point_num;
}

高阈值二值化处理结果:
在这里插入图片描述
画出的阳性点轮廓结果:
在这里插入图片描述
低阈值二值化处理结果:
在这里插入图片描述
画出的全部点轮廓结果:
在这里插入图片描述

完整代码逻辑:

(1)读入待处理图片,获得ROI区域
(2)一些特定的自定义去噪处理
(3)ACE处理
(4)阈值二值化处理
(5)计算点的数量

完整代码:

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

Mat get_ROI_image(Mat &src_image);//获得矩形有效区域
void draw_hist(Mat &img_gray);//画出一幅图像的直方图
Mat ACE(Mat &src, int C, int n, float MaxCG);//对一幅图像做ACE处理
void drop_noise(Mat &img);//去掉图像的一些噪声
size_t cal_point_num(Mat &img, String str);//canny边缘计算及画出轮廓


int thresh = 100;
int max_thresh = 255;
RNG rng(12345);

int main() {
	Mat src_image = imread("作业4图像2.bmp", CV_LOAD_IMAGE_UNCHANGED);
	if (src_image.empty()){
	cout << "图像加载失败" << endl;
	}
	imshow("src_image", src_image);

	Mat ROI_image = get_ROI_image(src_image);//获得区域
	imshow("ROI_image", ROI_image);
	cvtColor(ROI_image, ROI_image, COLOR_BGR2GRAY);//转换成灰度图

	drop_noise(ROI_image);//一些特定的自定义的去噪处理
	imshow("ROI_drop_noise_image", ROI_image);

	Mat ROI_ACE_image = ACE(ROI_image, 8, 11, 15);//进行ACE处理
	imshow("ROI_ACE_image", ROI_ACE_image);
	imwrite("ROI_ACE_image.bmp", ROI_ACE_image);


	Mat threshold_image;
	threshold(ROI_ACE_image, threshold_image, 200, 255, THRESH_BINARY);//高阈值二值化,以获得阳性点
	imshow("threshold_image", threshold_image);
	imwrite("threshold_image1.bmp", threshold_image);


	size_t light_point_num, dark_point_num, whole_point_num;
	light_point_num = cal_point_num(threshold_image, "light_point.bmp");//计算阳性点数量
	threshold(ROI_ACE_image, threshold_image, 60, 255, THRESH_BINARY);//低阈值二值化,以获得全部点
	imshow("threshold_image2", threshold_image);
	imwrite("threshold_image2.bmp", threshold_image);

	whole_point_num = cal_point_num(threshold_image, "whole_point.bmp");//计算所有点数量
	cout << "light_point_num:" << light_point_num << endl;
	cout << "dark_point_num:" << whole_point_num - light_point_num << endl;//阴性点数量=总的点数量-阳性点数量

	waitKey(0);
	return 0;
}


void drop_noise(Mat &img)//自定义的去噪处理
{
	for (int i = 0; i < img.rows; i++)
	{
		for (int j = 0; j < img.cols; j++)
		{
			if (img.at<unsigned char>(i, j) >= 69)//图像中有些盐噪声,通过设置阈值滤除掉
			{
				img.at<unsigned char>(i, j) = 0;
			}
		}
	}
}



Mat matrixWiseMulti(Mat &m1, Mat &m2){
	Mat dst = m1.mul(m2);
	return dst;
}

//float MaxCG:对高频成分的最大增益值,int n:局部半径,int C:对高频的直接增益系数  
Mat ACE(Mat &src, int C = 3, int n = 3, float MaxCG = 7.5)
{
	int rows = src.rows;
	int cols = src.cols;

	Mat meanLocal; //图像局部均值  
	Mat varLocal;  //图像局部方差  
	Mat meanGlobal;//全局均值
	Mat varGlobal; //全局标准差  

	blur(src.clone(), meanLocal, Size(n, n));
	//imshow("低通滤波", meanLocal);
	Mat highFreq = src - meanLocal;//高频成分 
	//imshow("高频成分", highFreq);

	varLocal = matrixWiseMulti(highFreq, highFreq);
	blur(varLocal, varLocal, Size(n, n));
	//换算成局部标准差  
	varLocal.convertTo(varLocal, CV_32F);
	for (int i = 0; i < rows; i++){
		for (int j = 0; j < cols; j++){
			varLocal.at<float>(i, j) = (float)sqrt(varLocal.at<float>(i, j));
		}
	}
	meanStdDev(src, meanGlobal, varGlobal);
	Mat gainArr = 0.5 * meanGlobal / varLocal;//增益系数矩阵  

	//对增益矩阵进行截止  
	for (int i = 0; i < rows; i++){
		for (int j = 0; j < cols; j++){
			if (gainArr.at<float>(i, j) > MaxCG){
				gainArr.at<float>(i, j) = MaxCG;
			}
		}
	}
	gainArr.convertTo(gainArr, CV_8U);
	gainArr = matrixWiseMulti(gainArr, highFreq);
	Mat dst2 = meanLocal + C*highFreq;
	return dst2;
}


void draw_hist(Mat &img_gray)
{
	//需要计算图像的哪个通道(bgr空间需要确定计算 b或g或r空间)    
	const int channels[1] = { 0 };

	//直方图的每一个维度的 柱条的数目(就是将灰度级分组)  
	int histSize[] = { 256 };   //如果这里写成int histSize = 256;   那么下面调用计算直方图的函数的时候,该变量需要写 &histSize  

	//定义一个变量用来存储 单个维度 的数值的取值范围    
	float midRanges[] = { 0, 256 };

	//确定每个维度的取值范围,就是横坐标的总数    
	const float *ranges[] = { midRanges };

	//输出的结果存储的 空间 ,用MatND类型来存储结果  
	MatND dstHist;

	calcHist(&img_gray, 1, channels, Mat(), dstHist, 1, histSize, ranges, true, false);

	//calcHist  函数调用结束后,dstHist变量中将储存了 直方图的信息  用dstHist的模版函数 at<Type>(i)得到第i个柱条的值  at<Type>(i, j)得到第i个并且第j个柱条的值    

	//首先先创建一个黑底的图像,为了可以显示彩色,所以该绘制图像是一个8位的3通道图像    
	Mat drawImage = Mat::zeros(Size(256, 256), CV_8UC3);

	//一个图像的某个灰度级的像素个数(最多为图像像素总数),可能会超过显示直方图的所定义的图像的尺寸,因此绘制直方图的时候,让直方图最高的地方只有图像高度的90%来显示  

	//先用minMaxLoc函数来得到计算直方图后的像素的最大个数    
	double g_dHistMaxValue;
	minMaxLoc(dstHist, 0, &g_dHistMaxValue, 0, 0);

	//遍历直方图得到的数据    
	//int myvalue[256];
	for (int i = 0; i < 256; i++)
	{
		cout << i << ":" << dstHist.at<float>(i) << endl;
		int value = cvRound(256 * 0.9 *(dstHist.at<float>(i) / g_dHistMaxValue));

		line(drawImage, Point(i, drawImage.rows - 1), Point(i, drawImage.rows - 1 - value), Scalar(255, 0, 0));
	}
	imshow("【直方图】", drawImage);
	//waitKey(0);
}

Mat get_ROI_image(Mat &src_image)
{
	Mat imageConvert;
	src_image.convertTo(imageConvert, src_image.type(), 3, 0);//对比度增强y = alpha*x + beta; alpha = 3, beta = 0;
	//imshow("imageConvert", imageConvert);

	Mat src_gray;
	cvtColor(imageConvert, src_gray, COLOR_BGR2GRAY);
	//imshow("src_gray", src_gray);

	//使用Canny检测边缘  
	Mat canny_image;
	Canny(src_gray, canny_image, 80, 126, (3, 3));
	//imshow("canny_image", canny_image);

	//高级形态学闭运算函数  
	Mat closed_image;
	//自定义形态学元素结构
	Mat element5(9, 9, CV_8U, cv::Scalar(1));//5*5正方形,8位uchar型,全1结构元素
	morphologyEx(canny_image, closed_image, cv::MORPH_CLOSE, element5);
	//imshow("closed_image", closed_image);
	//waitKey(0);


	//外部加框
	//检测连通域,每一个连通域以一系列的点表示,FindContours方法只能得到第一个域
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(closed_image, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//CV_RETR_EXTERNAL只检测外部轮廓,可根据自身需求进行调整

	//Mat contoursImage(closed_image.rows, closed_image.cols, CV_8U, Scalar(255));

	Rect maxRect, secondRect;
	for (int index = 0; index >= 0; index = hierarchy[index][0])
	{
		Scalar color(rand() & 255, rand() & 255, rand() & 255);

		Rect rect = boundingRect(contours[index]);//检测外轮廓
		if (index == 0)
		{
			maxRect = rect;
			secondRect = rect;
		}
		if (rect.area() > maxRect.area())
		{
			secondRect = maxRect;
			maxRect = rect;
		}

	}

	Mat ROI_image;
	ROI_image = src_image(secondRect);

	rectangle(src_image, secondRect, Scalar(0, 0, 255), 3);//对外轮廓加矩形框
	imshow("src_image_plus_rect", src_image);
	imwrite("src_image_plus_rect.bmp", src_image);

	cout << "完成检测";
	return ROI_image;
}

size_t cal_point_num(Mat &img, String str)
{
	Mat canny_output;
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	size_t point_num;
	CvFont font;

	// canny 边缘检测
	Canny(img, canny_output, thresh, thresh * 2, 3);

	// 寻找轮廓
	findContours(canny_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

	Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);

	// 画出轮廓
	for (size_t i = 0; i< contours.size(); i++) {
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawing, contours, (int)i, color, 2, 8, hierarchy, 0, Point());
	}
	point_num = contours.size();

	imshow(str, drawing);
	imwrite(str, drawing);
	return point_num;
}
  • 17
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: OpenCV的connectedComponents函数可以实现对图像进行连通区域分析,进而实现图像分割。 该函数的使用方法为: ```cpp connectedComponents(InputArray image, OutputArray labels, int connectivity = 8, int ltype = CV_32S) ``` 其,image参数表示输入的二值化图像,labels参数表示输出的标记图像,connectivity参数表示连通性,可选值为4或8,ltype参数表示输出标记图像的数据类型,可选值为CV_32S或CV_16U。 该函数返回值为连通域的数量。 使用该函数可以将图像的每个连通域标记为不同的标记,不同的连通域具有不同的标记值。通过对标记图像进行处理,可以实现图像分割的目的。 ### 回答2: OpenCV的connectedComponents是一种图像分割的方法,用于将一个二值图像的像素分成若干个连通区域,并为每个连通区域赋予一个唯一的标签。以下是关于connectedComponents方法的描述: connectedComponents方法将二值图像作为输入,并输出一个标签图像,其每个像素都被赋予一个标签,表示所属的连通区域。标签图像的尺寸与输入图像相同。 该方法通过扫描二值图像的每个像素,对于每个像素,如果其值为非零,则检查其相邻像素的标签,并将其自身标签与相邻像素的标签进行比较。如果相邻像素已经被标记,则将当前像素的标签设置为相同的标签,并将其标签添加到一个集合。如果相邻像素没有被标记,则将当前像素设置为一个新的标签,并将其标签添加到集合。 根据集合的标签数量,可以判断图像连通区域数量。方法会返回连通区域数量,以及一个与输入图像尺寸相同的标签图像。 connectedComponents方法非常适用于图像分割、物体检测等任务。可以根据标签图像提取出每个连通区域的轮廓、面积、质心等信息,进一步进行对象识别和分析。 ### 回答3: OpenCV图像分割函数connectedComponents是用于将图像的像素分成几个不同的连通区域,每个区域都具有相同的像素值或属性。 connectedComponents函数的使用需要先将图像转换为灰度图像,然后阈值化处理或使用其他预处理方法。 connectedComponents函数的调用需要指定两个参数:输入图像和可选的输出图像(标记图像),返回一个整数值,表示图像包含的连通区域数量(包括一个背景区域)。 如果提供了输出图像参数,函数将为每个连通区域分配一个唯一的标记值,并将该值赋给该区域的每个像素。 注意,标记值0用于背景区域,而其他标记值用于非背景区域。 connectedComponents函数可以用于许多图像处理应用,如目标检测、图像分析和计算机视觉等领域。 例如,可以使用connectedComponents函数识别图像的每个对象,并对它们进行独立的标记和分析。 也可以使用connectedComponents函数提取图像的每个连通区域,并对每个区域进行特定的处理。 总之,通过调用OpenCV的connectedComponents函数,可以实现对图像进行有效的分割,以便用于各种图像处理任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值