理想情况下,边缘检测应该仅产生位于边缘上的像素的集合.实际上,由于噪声,不均匀照明引起的边缘间断,以及其他引入灰度值虚假的不连续的影响,这些像素并不能完全描述边缘特性. 因此,一般在边缘检测后面会紧跟连接算法,将边缘像素组合成有意义的边缘或区域边界.
局部处理是连接边缘点最简单的方法之一,是在每个点(x,y)处的一个小邻域内分析像素的特点,根据特定的准则,将所有的相似点连接起来,以形成根据特定准则满足相同特性像素的一条边缘
用于确定边缘像素相似性的两个主要的性质:
(1)梯度向量的强度
文章图片
(2)梯度向量的方向
文章图片
(x,y)表示一个像素点 (s,t)表示以(x,y)为中心的所有的邻域点
如果(s,t)既满足幅度准则又满足角度准则,那么我们将(s,t)连接到(x,y),在图像的每个像素点重复这一操作,当邻域的中心从一个点到另一个点时必须将已连接的点记录下来,简单的记录过程就是将每组被连接的像素分配不同的灰度值
【opencv|C++实现最简单的边缘连接(局部处理)】根据以上原理得出步骤一:
1 计算图像x y方向的梯度 g_x,g_y
2 根据梯度矩阵计算幅度矩阵和角度矩阵 Mag angle
3 根据幅度准则和方向准则将图像赋予不同的灰度值
#include
#include
#include//
void CalMag(cv::Mat &src1,cv::Mat &src2,cv::Mat &dst1,cv::Mat &dst2){
for(int i=0;
i(i,j);
float gy=src2.at(i,j);
dst1.at(i,j)=std::sqrt(std::pow(gx,2)+std::pow(gy,2));
dst2.at(i,j)=std::atan2(gy,gx);
}
}
}void connect(cv::Mat &src1,cv::Mat &src2,cv::Mat &dst){
for(int i=1;
i(i,j);
float anglex=src2.at(i,j);
for(int m=i-1;
m<=i+1;
m++){
for(int n=j-1;
n<=j+1;
n++){
float magy=src1.at(m,n);
float angley=src2.at(m,n);
if(m!=i&&n!=j) {
if (std::abs(magx - magy) <= 30 && std::abs(anglex - angley) <= 5) {
int dd = (int) (magx / (1.4));
//原因是最大的值为sqrt(2)*255
dst.at(m, n) = dd;
}
}
}
}
}
}
}int main() {
cv::Mat src=https://www.it610.com/article/cv::imread("../3.png");
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
cv::imshow("gray",gray);
gray.convertTo(gray,CV_32FC1);
cv::Mat gradient_x;
gradient_x=(cv::Mat_(3,3)<<-1,0,1,-2,0,2,-1,0,1);
cv::Mat gradient_y;
gradient_y=(cv::Mat_(3,3)<<-1,-2,-1,0,0,0,1,2,1);
cv::Mat g_x;
g_x=cv::Mat::zeros(gray.size(),CV_32FC1);
cv::Mat g_y;
g_y=cv::Mat::zeros(gray.size(),CV_32FC1);
//使用卷积计算x方向 y方向的梯度
cv::filter2D(gray,g_x,-1,gradient_x);
cv::filter2D(gray,g_y,-1,gradient_y);
cv::imshow("src",src);
cv::imshow("x",g_x);
cv::imshow("y",g_y);
cv::Mat Mag(gray.size(),CV_32FC1);
cv::Mat angle(gray.size(),CV_32FC1);
//计算幅度与角度
CalMag(g_x,g_y,Mag,angle);
cv::Mat dst;
dst=cv::Mat::zeros(Mag.size(),CV_8U);
//根据范围赋予不同的值
connect(Mag,angle,dst);
cv::imshow("mag",Mag);
cv::imwrite("../mag.png",Mag);
cv::imshow("dst",dst);
cv::imwrite("../dst.png",dst);
cv::waitKey(0);
return 0;
}
文章图片
原图
文章图片
结果图
由于前面的计算代价很高,要计算每个像素的所有的邻域,通过将步骤一简化得到步骤二:
1 计算图像x y方向的梯度 g_x,g_y
2 根据梯度矩阵计算幅度矩阵和角度矩阵 Mag angle
3 根据以下公式计算图像g
文章图片
4 扫描g的行 并在不超过指定长度的K的每一行填充(置1)所有的缝隙(0),缝隙一定要在一个或多个1的两端
5 为在任何其他方向上theta检测缝隙,以角度theta旋转g 并应用步骤4中的水平扫描过程,然后将结果以-theta旋转回来
由于旋转图像比较复杂,要考虑图像的大小和灰度值的变换,所以我只旋转了90度 也就是只考虑水平边缘和垂直边缘的情况
#include
#include
#includevoid CalMag(cv::Mat &src1,cv::Mat &src2,cv::Mat &dst1,cv::Mat &dst2){
for(int i=0;
i(i,j);
float gy=src2.at(i,j);
dst1.at(i,j)=std::sqrt(std::pow(gx,2)+std::pow(gy,2));
dst2.at(i,j)=std::atan2(gy,gx);
}
}
}void connect(cv::Mat &src1,cv::Mat &src2,cv::Mat &dst){
for(int i=1;
i(i,j);
float anglex=src2.at(i,j);
if(magx>180&&anglex>-2&&anglex<2)
dst.at(i,j)=255;
}
}
}cv::Mat rotate_arbitrarily_angle1(cv::Mat matSrc, float angle, bool direction,int height,int width) {
float theta = angle * CV_PI / 180.0;
int nRowsSrc = https://www.it610.com/article/matSrc.rows;
int nColsSrc = matSrc.cols;
// 如果是顺时针旋转
if (!direction) theta = 2 * CV_PI - theta;
// 全部以逆时针旋转来计算
// 逆时针旋转矩阵
float matRotate[3][3]{ {
std::cos(theta), -std::sin(theta), 0},
{std::sin(theta), std::cos(theta), 0 },
{0, 0, 1} };
float pt[3][2]{
{ 0, nRowsSrc },
{nColsSrc, nRowsSrc},
{nColsSrc, 0} };
for (int i = 0;
i < 3;
i++) {
float x = pt[i][0] * matRotate[0][0] + pt[i][1] * matRotate[1][0];
float y = pt[i][0] * matRotate[0][1] + pt[i][1] * matRotate[1][1];
pt[i][0] = x;
pt[i][1] = y;
}
// 计算出旋转后图像的极值点和尺寸
float fMin_x = std::min(std::min(std::min(pt[0][0], pt[1][0]), pt[2][0]), (float)0.0);
float fMin_y = std::min(std::min(std::min(pt[0][1], pt[1][1]), pt[2][1]), (float)0.0);
float fMax_x = std::max(std::max(std::max(pt[0][0], pt[1][0]), pt[2][0]), (float)0.0);
float fMax_y = std::max(std::max(std::max(pt[0][1], pt[1][1]), pt[2][1]), (float)0.0);
int nRows = cvRound(fMax_y - fMin_y + 0.5) + 1;
int nCols = cvRound(fMax_x - fMin_x + 0.5) + 1;
int nMin_x = cvRound(fMin_x + 0.5);
int nMin_y = cvRound(fMin_y + 0.5);
// 拷贝输出图像
cv::Mat matRet(nRows, nCols, matSrc.type(), cv::Scalar(0));
for (int j = 0;
j < nRows;
j++) {
for (int i = 0;
i < nCols;
i++) {
// 计算出输出图像在原图像中的对应点的坐标,然后复制该坐标的灰度值
// 因为是逆时针转换,所以这里映射到原图像的时候可以看成是,输出图像
// 到顺时针旋转到原图像的,而顺时针旋转矩阵刚好是逆时针旋转矩阵的转置
// 同时还要考虑到要把旋转后的图像的左上角移动到坐标原点。
int x = (i + nMin_x) * matRotate[0][0] + (j + nMin_y) * matRotate[0][1];
int y = (i + nMin_x) * matRotate[1][0] + (j + nMin_y) * matRotate[1][1];
if (x>= 0 && x < nColsSrc && y >= 0 && y < nRowsSrc) {
matRet.at(j, i) = matSrc.at(y, x);
}
}
}
if(direction== false){
int x = (matRet.cols -width) / 2;
int y = (matRet.rows -height) / 2;
cv::Rect rect(x, y, width, height);
matRet = cv::Mat(matRet,rect);
}
return matRet;
}void fill(cv::Mat &src,cv::Mat &dst)
{
for(int i=0;
i(i,j-1);
int g2=src.at(i,j);
int g3=src.at(i,j+1);
if(g2==255)
{
dst.at(i,j)=255;
if(g1==0){
dst.at(i,j-1)=255;
}
if(g3==0){
dst.at(i,j+1)=255;
}
}
}
}
}int main() {
cv::Mat src=https://www.it610.com/article/cv::imread("../3.png");
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
//步骤1:计算梯度
gray.convertTo(gray,CV_32FC1);
cv::Mat gradient_x;
gradient_x=(cv::Mat_(3,3)<<-1,0,1,-2,0,2,-1,0,1);
cv::Mat gradient_y;
gradient_y=(cv::Mat_(3,3)<<-1,-2,-1,0,0,0,1,2,1);
cv::Mat g_x;
g_x=cv::Mat::zeros(gray.size(),CV_32FC1);
cv::Mat g_y;
g_y=cv::Mat::zeros(gray.size(),CV_32FC1);
cv::filter2D(gray,g_x,-1,gradient_x);
cv::filter2D(gray,g_y,-1,gradient_y);
//步骤2 计算幅度角度
cv::Mat Mag(gray.size(),CV_32FC1);
cv::Mat angle(gray.size(),CV_32FC1);
CalMag(g_x,g_y,Mag,angle);
//步骤3 赋值图像只有0 255
cv::Mat dst;
dst=cv::Mat::zeros(Mag.size(),CV_8U);
connect(Mag,angle,dst);
cv::imshow("dst.png",dst);
cv::imwrite("../dst.png",dst);
//步骤4 5 扫描填充
int height=src.rows;
int width=src.cols;
cv::Mat dst1;
dst1=cv::Mat::zeros(Mag.size(),CV_8U);
fill(dst,dst1);
dst1=rotate_arbitrarily_angle1(dst1,90,true,height,width);
cv::imshow("dst2",dst1);
cv::imwrite("../dst1.png",dst1);
cv::Mat dst2;
dst2=cv::Mat::zeros(dst1.size(),dst1.type());
fill(dst1,dst2);
dst2=rotate_arbitrarily_angle1(dst2,90, false,height,width);
cv::imshow("dst3",dst2);
cv::imwrite("../dst3.png",dst2);
cv::waitKey(0);
return 0;
}
文章图片
原图
文章图片
步骤3的结果
文章图片
扫描之后旋转90度的结果
文章图片
扫描填充旋转-90度的结果
推荐阅读
- 人脸识别|【人脸识别系列】| 实现自动化妆
- OpenCV|OpenCV-Python实战(18)——深度学习简介与入门示例
- opencv|图像处理之椒盐噪声的添加与去除
- 人脸识别|【人脸识别系列】| 实现人脸截图保存并编写128维特征向量
- opencv|网络爬虫入门练习
- OpenCV|【OpenCV 完整例程】89. 带阻滤波器的传递函数
- OpenCV|【OpenCV 完整例程】90. 频率域陷波滤波器
- OpenCV|【OpenCV 完整例程】22. 图像添加非中文文字
- OpenCV|【OpenCV 完整例程】91. 高斯噪声、瑞利噪声、爱尔兰噪声
- opencv|python+opencv车道线,实线虚线的检测