大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
C++ Opencv imfill 孔洞填充函数的实现
敬告
本文所述内容已经更新,大可不必继续阅读,更新后内容请点击以下链接
点我
函数实现的中心思想
二值图
此程序针对于二值图,寻找二值图中 像素值为0的连通域,将所有连通域的像素点分别保存下来,将符合条件的连通域的像素值 置为255;
寻找连通域的关键
针对填洞功能的实现,也就是0置为255过程,我们需要以四连通为基本点进行寻找。
种子点的确定
寻找种子点,其实就是寻找二值图中像素值为0的点,我们可以直接采取 遍历 二值图 中的像素,将第一个遇见的像素值为0的点确定为 第一个连通域的种子点。这时候,有一些朋友可能会疑惑,因为按照我的说法,在遍历 的过程中,遇见的第n个像素值为0的点 就是第n个连通域的种子点,进一步说,在整个遍历过程中,遇见像素值为0的像素点的个数,就是连通域的个数。
是的!
当然,如果要实现这一点,那我们就需要在各个连通域的寻找的过程中,将找到的点全部立即置为255,(此处不一定非得是255,只要不是0即可)这样在寻找结束后,我们再遍历二值图时,已经找到的连通域中的所有像素点的值均为255,当再次找到像素值为0 的像素点时,此像素点必是下一个待寻找的连通域的种子点
连通域的寻找过程
首先创建四连通的向量,vector<Point> upp;
用来存储上下前后四个点,
创建vector<vector<vector<Point>>> lenm;
用来存储所有的连通域,至于为什么要创建三维Point
数组,大家可以先看看关于这个三维数组的注释,(下面的公式就是,程序中也有相应的注释),了解清楚每一维代表的意义,再结合一下程序,我感觉大家应该可以明白,再简要赘述一下,lenm.size()
为连通域的个数。
如图所示;函数为第i个连通域像素点个数的求和。
条件设定
在经过以上的寻找过程后,得到的结果必然是全白的图像,而我们只想要填充孔洞,所以我们需要去除不符合的连通域。所谓孔洞,其实就是周围被像素值为255的点包围起来的连通域,但是,有一些连通域,直接和图像的边界相连,而这并不是我们想要的, 至少不是我想要的,(如果大家有不同的需求,程序也是很容易改过去的)。所以,我需要一个标志位,当这个连通域中的像素点接触到边界后,给这个连通域一个标记。在下面的程序中,我用vector<vector<int>> Flag;
来存储标记点,其中Flag[i]
表示第i个连通域的标记点。在程序中,找到种子点后,首先将第i个连通域的Flag[i][0] = 1;
,如果在此连通域中出现边界点,再Flag[i][0] = 0;
(在程序中,此处貌似有一个小BUG,我就先不改了[?])
最后赋值
在寻找到的所有连通域中,Flag[i][0] == 1;
{其中 i 属于 [0,Flag.size()) }的连通域为符合要求的连通域,因此将lenm[i];
中的所有像素点赋值255即可。
话不多说 直接上函数代码
输入二值图;
返回二值图;
Mat imfill(Mat cop)
{
Mat fcop;
cop.copyTo(fcop);
vector<Point> upp;//定义四连通点集,有必要可以是八连通
upp.push_back(Point(-1, 0));
upp.push_back(Point(0, -1));
upp.push_back(Point(0, 1));
upp.push_back(Point(1, 0));
//upp.push_back(Point(1, 1));
//upp.push_back(Point(-1,-1));
//upp.push_back(Point(-1, 1));
//upp.push_back(Point(1, -1));
vector<vector<vector<Point>>> lenm;//三维point向量 lenm.size()是连通域的个数
/*
int impixel_sum = 0;
for (int j = 0,j<lenm[i].size();j++)
{
impixel_sum+= lenm[i][j].size();
}
//这段循环 表示第i个连通域中 像素点的个数。
*/
vector<vector<Point>> numim;
vector<Point> ssinum;
vector<vector<int>> Flag;
vector<int> ce;
int nmss = 0;//连通域的个数;
int nums = 0;//中间变量 用来存储 lenm.size();即 在程序运行过程中 nums始终等于 lenm[i][j][k] 中的j 的 值的大小;
int s1 = 0;
//标志位 ,每次区域生长后 符合条件的像素个数,当第i个连通域,在经过第j次生长后,s1=lenm[i][j].size(),
//若s1==0,表示生长结束,不再有符合条件的点,第i连通域中的所有点都已经找到。
for (int row = 0; row < fcop.rows; row++)
{
for (int col = 0; col < fcop.cols; col++)
{
if (fcop.at<uchar>(row, col) == 0)
{
ce.push_back(1);
Flag.push_back(ce);
//vector<vector<Point>> numim;
//vector<Point> ssinum;
ssinum.push_back(Point(col, row));
numim.push_back(ssinum);
fcop.at<uchar>(row, col) = 255;
ssinum.clear();
s1 = 1;
while (s1 > 0)
{
//ce.push_back(1);
//Flag.push_back(ce);
//vector<Point> ssinum;
for (int i = 0; i < numim[nums].size(); i++)
{
for (int j = 0; j < upp.size(); j++)
{
int X = numim[nums][i].x + upp[j].x;
int Y = numim[nums][i].y + upp[j].y;
if (X >= 0 && Y >= 0 && X < fcop.cols && Y < fcop.rows)
{
if (fcop.at<uchar>(Y, X) == 0)
{
ssinum.push_back(Point(X, Y));
fcop.at<uchar>(Y, X) = 255;
}
}
if (X == 0 || Y == 0 || X == fcop.cols - 1 || Y == fcop.rows - 1)
{
Flag[nmss][0] = 0;
}
}
}
//Flag.push_back(ce);
numim.push_back(ssinum);
s1 = ssinum.size();
nums++;
ssinum.clear();
/*ce.clear();*/
}
nums = 0;
lenm.push_back(numim);
numim.clear();
nmss++;
ce.clear();
}
}
}
//imshow("1",fcop);
Mat ffcop;
cop.copyTo(ffcop);
//ffcop = Mat::zeros(cop.size(),cop.type());
for (int i = 0; i < Flag.size(); i++)
{
if (Flag[i][0] == 1)
{
for (int j = 0; j < lenm[i].size(); j++)
{
for (int k = 0; k < lenm[i][j].size(); k++)
{
int X = lenm[i][j][k].x;
int Y = lenm[i][j][k].y;
ffcop.at<char>(Y, X) = 255;
}
}
}
}
return ffcop;
}
主函数代码
#include<opencv2/opencv.hpp>
#include<iostream>
#include"imfill.h"
using namespace std;
using namespace cv;
Mat src;
vector<vector<Point>> lunk;
vector<Vec4i> level;
//RNG rn;
int main()
{
src = imread("5.jpg");
//imshow("万丈高楼第一步",src);
Mat dst, gray, erzhi;
blur(src, dst, Size(3, 3), Point(-1, -1));
//imshow("均值滤波",dst);
cvtColor(dst, gray, COLOR_BGR2GRAY);
//imshow("灰度图",gray);
Canny(gray, erzhi, 100, 200, 3, false);
//imshow("边缘检测",erzhi);
Mat holef;
holef = imfill(erzhi);
imshow("填洞", holef);
waitKey(0);
return 0;
}
代码框截图
实例图片
运行结果
总结:此程序会填充所有的孔洞,如果想只填充指定阈值范围内的孔洞,需要再多计算每个连通域像素的个数即可,因为所有数据都已经存了下来,所以计算会比较简单。
此程序的运行时间感觉也还可以,如果大家有改进的方法可以共献良策。
自我介绍:
本人是opencv小白,用过半年matlab,最近导师让用C++,所以刚接触不久。第一次发文章,如果程序或者文章哪里有问题,请多多指出,我会立即改正。如果想和楼主一起交流opencv的有关知识,请联系Q:1786184325,让我们一起学习,共同进步!
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/194588.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...