基于OpenCV的细胞图像识别
开发流程
1. 基于OpenCV库,对含有细胞的图像进行处理。
2. 统计图像中所含细胞数量。
3. 输出最大和最小细胞的面积,周长,方向,和该细胞的中心位置。(以像素为单位)
4. 输出所有细胞的平均面积。
5. 主要的中间步骤处理结果以cvShowImage的形式输出。
算法具体步骤
1. 图像的读入和转化
读入图像后转化为灰度图像
// Load image to src and Show source image Mat source = imread("cell1.bmp", CV_LOAD_IMAGE_COLOR);imshow("Source image", source);// Get gray graph of source image. Mat gray;cvtColor(source, gray, CV_RGB2GRAY);imshow("Gray image", gray);
2. 分离细胞与背景,并将图像二值化
int thresholdn = Otsu(gray);Mat bin;threshold(gray, bin, thresholdn, 255, CV_THRESH_BINARY);
在分离细胞与背景图像的时候,最重要的是要找到一个合适的阈值,来进行图像和背景的二值化分离。这里我阅读了一些参考文献,决定采用otsu算法即最大类间方差法,又称大津算法。它是按图像的灰度特性,将图像分成背景和目标2部分。背景和目标之间的类间方差越大,说明构成图像的2部分的差别越大,当部分目标错分为背景或部分背景错分为目标都会导致2部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
3. 获取细胞外围轮廓,及其面积、周长等。
这里我们使用findContours()函数来实现对细胞轮廓的获取,在此基础上舍弃一些轮廓,留下最终我们需要的,由于图像中有些和细胞颜色接近而面积远小于细胞的杂质,因此我们还需要对面积进行判断并且舍弃部分杂质。
vector<vector<Point>> contours;vector<Vec4i> hierarchy;Mat cont = Mat::zeros(bin.size(), CV_8UC1);Mat ellp = Mat::zeros(bin.size(), CV_8UC1);findContours(bin, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));//计算平均面积:for (int i = 0; i < contours.size(); i++){if (hash[i]){area[i] = contourArea(contours[i]);totalarea += area[i];}}int average = totalarea / counter;//杂质的判断并记录最大最小细胞的编号int minarea = cont.rows*cont.cols, maxarea = 0, mini, maxi;for (int i = 0; i < contours.size(); i++){if (hash[i])if (area[i]<average * 0.3){hash[i] = false;counter--;}else{if (area[i]<minarea){minarea = area[i];mini = i;}if (area[i]>maxarea){maxarea = area[i];maxi = i;}}}
4. 对细胞进行椭圆拟合,输出最大最小细胞的信息
虽然只需输出最大最小细胞的信息,但为了便于观察分析我对所有细胞都进行了椭圆拟合。
if (hash[i])
{CvPoint center;CvSize size;CvBox2D32f* box;CvPoint* PointArray;CvPoint2D32f* PointArray2D32f;PointArray = (CvPoint*)malloc(count*sizeof(CvPoint));PointArray2D32f = (CvPoint2D32f*)malloc(count*sizeof(CvPoint2D32f));// Alloc memory for ellipse data. box = (CvBox2D32f*)malloc(sizeof(CvBox2D32f));// Convert CvPoint set to CvBox2D32f set. for (int j = 0; j<count;j++){PointArray2D32f[j].x = (float)contours[i][j].x;PointArray2D32f[j].y = (float)contours[i][j].y;}// Fits ellipse to current contour. cvFitEllipse(PointArray2D32f, count, box);// Convert ellipse data from float to integer representation. center.x = cvRound(box->center.x);center.y = cvRound(box->center.y);size.width = cvRound(box->size.width*0.5);size.height = cvRound(box->size.height*0.5);drawContours(cont, contours, i, 255);ellipse(ellp, center, size, box->angle, 0, 360, 255, 1, CV_AA, 0);if (i == mini)cout << "最小细胞面积为" << area[i] << ',' << "周长:" << count << ','<< "短轴与X+轴成:" << 180 - box->angle << "度角," << "中心" << '(' << center.x << ',' << center.y << ");"<<endl;if (i == maxi)cout << "最大细胞面积为" << area[i] << ',' << "周长:" << count << ','<< "短轴与X+轴成:" << 180 - box->angle << "度角," << "中心" << '(' << center.x << ',' << center.y << ");" << endl;// Free memory. free(PointArray);free(PointArray2D32f);free(box);}
算法实现要点
1. Otsu算法
对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比
例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记为g。
假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:
ω0=N0/ M×N (1)
ω1=N1/ M×N (2)
N0+N1=M×N (3)
ω0+ω1=1 (4)
μ=ω0*μ0+ω1*μ1 (5)
g=ω0(μ0-μ)^2+ω1(μ1-μ)^2 (6)
将式(5)代入式(6),得到等价公式: g=ω0ω1(μ0-μ1)^2 (7)
采用遍历的方法得到使类间方差最大的阈值T,即为所求。
Otsu算法步骤如下:
设图象包含L个灰度级(0,1…,L-1),灰度值为i的的象素点数为Ni ,图象总的象素点数为N=N0+N1+...+N(L-1)。灰度值为i的点的概为:
P(i) = N(i)/N.
门限t将整幅图象分为暗区c1和亮区c2两类,则类间方差σ是t的函数:
σ=a1*a2(u1-u2)^2 (2)式中,aj为类cj的面积与图象总面积之比,a1 =sum(P(i)) i->t, a2 = 1-a1; uj为类cj的均值,u1 = sum(i*P(i))/a1 0->t,
u2 = sum(i*P(i))/a2, t+1->L-1
该法选择最佳门限t 使类间方差最大,即:令Δu=u1-u2,σb =max{a1(t)*a2(t)Δu^2}
具体实现代码如下:
int Otsu(Mat& src)
{int height = src.rows;int width = src.cols;//histogram float histogram[256] = { 0 };for (int i = 0; i < src.rows; i++)for (int j = 0; j < src.cols; j++){histogram[src.at<uchar>(i, j)]++;}//normalize histogram int size = height * width;for (int i = 0; i < 256; i++){histogram[i] = histogram[i] / size;}//average pixel value float avgValue = 0;for (int i = 0; i < 256; i++){avgValue += i * histogram[i]; //整幅图像的平均灰度 }int threshold;float maxVariance = 0;float w = 0, u = 0;for (int i = 0; i < 256; i++){w += histogram[i]; //假设当前灰度i为阈值, 0~i 灰度的像素(假设像素值在此范围的像素叫做前景像素) 所占整幅图像的比例 u += i * histogram[i]; // 灰度i 之前的像素(0~i)的平均灰度值: 前景像素的平均灰度值 float t = avgValue * w - u;float variance = t * t / (w * (1 - w));if (variance > maxVariance){maxVariance = variance;threshold = i;}}return threshold;
}
Otsu算法二值化结果:
2. 细胞轮廓提取与分离
findContours()函数会提取图像中的所有轮廓,效果如下
此种情况下有两个问题:
1. 由于函数实现中将整幅图的边界像素值当做0来处理,因而会有一个包含了部分图像边界在内的极大轮廓,而且该轮廓并不是我们需要的。
2. 由于细胞内部相比于边界,颜色较浅且较为接近背景颜色,因而利用Otsu算法无法将整个细胞化为一个整体,所以在细胞内会存在“空洞”。
解决方案:
首先,在采用findContours函数的时候,制定MODE为CV_RETR_TREE,这是将所有的轮廓根据包含关系,建成一个树状结构,若大的轮廓包含了小的,则小的轮廓为大的轮廓的儿子节点,且该树可以有多层。
而根据测试,当我们将整幅图像的边界以内宽度为3的边框的像素赋为255的时候,就能够实现一个覆盖整幅图的边框,并将穿越图像边界的细胞(可能为细胞的一部分)完全分离出来。
for (int i = 0; i < bin.rows; i++)
{for (int j = 1; j < 3; j++){bin.at<uchar>(i, j - 1) = 255;bin.at<uchar>(i, bin.cols - j) = 255;}
}for (int i = 0; i < bin.cols; i++){for (int j = 1; j < 3; j++){bin.at<uchar>(j - 1, i) = 255;bin.at<uchar>(bin.rows - j, i) = 255;}}
于是得到如下算法①将外层轮廓删除②所有父节点是最外层轮廓的(即为细胞最外层)保留,其余轮廓删除。
for (int i = 0; i < contours.size(); i++)if ((hierarchy[i][3] == -1) || (hierarchy[i][3] != 0)){hash[i] = false;counter --;}
结果如下:
3. 去除杂质
在观察图像特质之后我们可以得到杂质如下特征:①颜色和细胞颜色较为接近,二值化后为黑色。②面积远小于细胞。
方法一:统计学处理
根据分析很容易得出如下结论,细胞的面积基本相差不大,服从N(μ, σ)的正态分布,其中μ为面积均值,σ为标准差。根据三倍标准差检验,即分布在[μ-3σ,μ+3σ]外的数据为不符合要求数据。
存在问题:
1. 没有总体数据
在没有总体的情况下,如果强行算出所有数据的均值和标准差,会发现混入了杂质后,细胞的均值下降,方差大幅度增加,导致了杂质基本都存在于[μ-3σ,μ+3σ]区间内。
2. 无法迭代操作
如果采用将符合要求的数据逐个插入总体中,进行迭代,则涉及到最刚开始的数据必须是符合要求的细胞的数据,否则如果一开始都是杂质,那么以杂质建立总体样本,就会出现细胞变成了不符合要求的数据,并且被踢出。
方法二:
法二较为简单,利用杂质面积远小于细胞的特性,设置一个参数,为杂质和细胞均值的面积比例,可以通过实验得出,当比例小于0.3的时候就可以很好的区分出细胞和杂质了。但是这一种方法的误差依然存在。
基于OpenCV的细胞图像识别相关推荐
- 基于OpenCV的棋盘图像识别
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本期我们将一起学习如何使用计算机视觉技术识别棋子及其在棋盘上的位置 ...
- 深度学习核心技术精讲100篇(五十五)- 基于OpenCV实现棋盘图像识别
本文我们将一起学习如何使用计算机视觉技术识别棋子及其在棋盘上的位置 我们利用计算机视觉技术和卷积神经网络(CNN)为这个项目创建分类算法,并确定棋子在棋盘上的位置.最终的应用程序会保存整个图像并可视化 ...
- Java基于opencv实现图像数字识别(一),java开发面试笔试题
我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...
- Java基于opencv实现图像数字识别(一)
Java基于opencv实现图像数字识别(一) 最近分到了一个任务,要做数字识别,我分配到的任务是把数字一个个的分开:当时一脸懵逼,直接百度java如何分割图片中的数字,然后就百度到了用Buffere ...
- android opencv 获取小图在大图的坐标_Android开发—基于OpenCV实现相机实时图像识别跟踪...
利用OpenCV实现实时图像识别和图像跟踪 图像识别 什么是图像识别 图像识别,是指利用计算机对图像进行处理.分析和理解,以识别各种不同模式的目标和对像的技术.根据观测到的图像,对其中的物体分辨其类别 ...
- Python图像识别实战(三):基于OpenCV实现批量单图像超分辨重建(附源码和实现效果)
前面我介绍了可视化的一些方法以及机器学习在预测方面的应用,分为分类问题(预测值是离散型)和回归问题(预测值是连续型)(具体见之前的文章). 从本期开始,我将做一个关于图像识别的系列文章,让读者慢慢理解 ...
- [Python图像处理] 十四.基于OpenCV和像素处理的图像灰度化处理
该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...
- 基于OpenCV实现人脸识别--Python
目录 前言 第一章 OpenCV介绍 第二章 功能描述 2.1 对已有的数据进行检测 2.2 陌生人检测并发出警告 2.3 保存陌生人的视频 2.4 输入图片进行检测 2.5 现场录用信息 第三章 功 ...
- c语言编程图片识别条形码,基于Zbar的条形码图像识别算法的制作方法
本发明涉及条形码识别技术领域,特别是涉及一种基于Zbar的条形码图像识别算法. 背景技术: 条形码是将宽度不等的多个黑条和空白,按一定的编码规则排列,用以表达一组信息的图形标识符.随着科技的迅速发展, ...
最新文章
- Maven系列学习(二)Maven使用入门
- lucene快速入门_为Lucene选择快速唯一标识符(UUID)
- spring之:XmlWebApplicationContext作为Spring Web应用的IoC容器,实例化和加载Bean的过程...
- python之Django学习笔记(五)---后台(admin.py)Action使用
- Redis基础(九)——发布与订阅
- ansible 第三次作业
- 计算机网络连接图标 红叉,Win10网络图标显示红叉叉 检测不到网卡驱动解决方案...
- 查看树莓派引脚以及串口连接
- 【历史上的今天】10 月 17 日:微软发布 Windows 8.1;IMDb 成立;海盗湾创始人诞生
- 删除项目的SVN信息 取消项目的SVN关联 简单粗暴
- Caché库诡异慢问题跟踪
- Linux设置sudo无需密码【Ubuntu、多种方法】
- 杭州保俶塔实验机器人_资讯 | 智慧与挑战!2017年西湖区中小学生科技节智能机器人比赛成绩出炉啦...
- 微信jsapi开发教程之如何获取jsapi_ticket(第二课)
- ldpc译码讲解_LDPC码编译码原理及应用
- 微信 libco 协程库原理剖析
- 文档生成模型:多元贝努利vs多项式
- phpcms怎么用mysql_【PHPCMS系统mysql优化教程】-phpcms教程-【零度中国】
- 计算机毕业设计ssm成都驰聘健身俱乐部管理系统so4b3系统+程序+源码+lw+远程部署
- zcy算法入门笔记001
热门文章
- 盘复分支语句和循环语句的那些知识
- 如何把我们的网站变成黑白色或是灰色
- 第一批用基因编辑工具定制DNA的婴儿即将诞生?
- 第54届超算TOP500排名,中美各自以数量和性能领先
- Linux 桌面虚拟化技术 KVM
- matlab命令批量重命名,MATLAB中预定义对话框之文件打开对话框以及如何批量重命名...
- MyEclipse老是弹出problem occurred窗口
- 江城子·己亥年戊辰月丁丑日话凄凉
- License Code of Jigloo序列号、注册码,可能是通用的呵呵
- ECshop 数据库表结构字段说明