目标
对于裂缝,在通过YOLO确定其大致位置的基础上,描出大概位置,计算出一系列的裂缝参数,如长度,面积,周长等。
处理思路
读取框选中的图像;
转为单通道灰度图;
自适应二值化;
此时发现图像(坝壁)会有局部噪声,这不是我们想要的,对图像开运算;
目前初步得到了二值化的裂缝图像,但还是远远不够的。
处理一:对二值图Canny后找轮廓,用最小矩形框选,判断该矩形中心是否在轮廓内。若是,则纳入“种子点”;
处理二:对二值图进行连通域搜索,判断连通域的中心是否为白色(即裂缝)。若是,则纳入“种子点”;
根据得到的种子点,在灰度图上进行漫灌,差值选定为常规值30;
漫灌后的图进行常规的二值化运算,阈值需要根据现场效果来调。此时得到的二值化图效果就比较好了,对白色点统计像素值就能得到面积,找轮廓就能得到周长。
对于长度计算,我采取的方法是尝试找出图像的中心线并标记出来。这里用到的是图像细化的实现,由于opencv的API在contrib包里,我就只能在网上找一种实现方法,下面是用到的两个函数,具体实现见代码。
cv::Mat thinImage(const cv::Mat& src, const int maxIterations)
void filterOver(cv::Mat thinSrc)
如此就能描出中线。
描出中线后,对中线点进行统计并计算各点之间的距离。(这个目前的方法不是很好)
目前我的处理方式是:对横向裂缝,先对列遍历、再对行遍历。对纵向裂缝则相反。如果两个点之间的横坐标或者纵坐标大于一个阈值,则不计算这两点之间的这段距离,避免比如 分叉、割断 带来的误差。【暂时没想到更好的方法】据此给出面积。
处理效果
存在问题
存在的问题在于裂缝长度的计算上。
比如横向的这种分叉的情况,应该怎么计算某一个分支的长度?
怎么将一个分支下的点相连呢?
实现代码
#pragma once
#include<opencv2\opencv.hpp>
#include<opencv2\dnn.hpp>
#include<fstream>
#include<iostream>
#include "functions.h"
//#include <opencv2\ximgproc.hpp> //细化函数thining所在的头文件
using namespace std;
using namespace cv;
using namespace cv::dnn;
#define path "D:/C_C++_Space/opencv4_xwl/cracktest3.jpg"
#define pixdistance 1
int main()
{
//---------------------------------------加载类别---------------------------------------
bool crackflag = 1;
if (!crackflag)
{
return -1;
}
Mat src = imread(path);//后期改成在YOLO框选范围内搜索
imshow("原图", src);
Mat grayImg, binImg,openImg,curve;
if (src.empty())
{
return -1;
}
//imshow("src", src);
if (src.channels() == 3)
{
cvtColor(src, grayImg, COLOR_BGR2GRAY);
}
//imshow("gray", grayImg);
RNG rng(10086);
////设置操作标志flags
int connectivity = 4; //连通邻域方式
int maskVal = 255; //掩码图像的数值
int flags = connectivity | (maskVal << 8) | FLOODFILL_FIXED_RANGE;
//设置与选中像素点的差值
Scalar loDiff = Scalar(30, 30, 30);
Scalar upDiff = Scalar(30, 30, 30);
////声明掩模矩阵变量
Mat mask = Mat::zeros(src.rows + 2, src.cols + 2, CV_8UC1);
int blockSize = 27;
int constValue = 22;
adaptiveThreshold(grayImg, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, blockSize, constValue);
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
morphologyEx(binImg, binImg, MORPH_OPEN, element);
//imshow("binary", binImg);
Canny(binImg, curve, 80, 200);
vector<vector<Point>> contours;
findContours(curve, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
/*drawContours(src, contours, -1, Scalar(0, 0, 255), 2);*/
Point2f rect[4];
vector<Rect> boundRect(contours.size()); //定义外接矩形集合
vector<RotatedRect> box(contours.size()); //定义最小外接矩形集合
vector<Point> floodseed;
for (int i = 0; i < contours.size(); i++)
{
box[i] = minAreaRect(Mat(contours[i])); //计算每个轮廓最小外接矩形
box[i].points(rect); //把最小外接矩形四个端点复制给rect数组
//for (int i = 0; i < 4; i++)
// line(src, rect[i], rect[(i + 1) % 4], Scalar(0, 255, 0));
/*circle(src, box[i].center, 2, Scalar(0, 255, 0), 2);*/
//double a1 = pointPolygonTest(contours[i], box[i].center, true);//true表示点到轮廓的距离
double b1 = pointPolygonTest(contours[i], box[i].center, false);//false表示计算点与轮廓的位置关系-1表示外部,0在轮廓上,1在轮廓内
Scalar newVal = Scalar(0,0,0);
if (b1==1)
{
floodseed.push_back(box[i].center);
}
}
//imshow("src", src);
//imshow("flood", grayImg);
Mat out, stats, centroids;
int number = connectedComponentsWithStats(binImg, out, stats, centroids, 8, CV_16U);
Mat result = Mat::zeros(src.size(), src.type());
int w = result.cols;
int h = result.rows;
for (int i = 1; i < number; i++)
{
int center_x = centroids.at<double>(i, 0);
int center_y = centroids.at<double>(i, 1);
int binvalue = binImg.at<uchar>(center_y, center_x);
if (binvalue==255)
{
floodseed.push_back(Point(center_x, center_y));
}
}
for (int i=0;i<floodseed.size();i++)
{
floodFill(grayImg, mask, floodseed[i], Scalar(0,255,0), 0, loDiff, upDiff, flags);
}
//imshow("漫灌后", grayImg);
//图像信息统计
Mat finalImg;
threshold(grayImg, finalImg, 85, 255, THRESH_BINARY_INV);//阈值这里取85
////开运算
//Mat newelement = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
//morphologyEx(finalImg, finalImg, MORPH_OPEN, newelement);
medianBlur(finalImg, finalImg, 7);
//查找白色像素点个数
int counter = 0;
Mat_<uchar>::iterator it = finalImg.begin<uchar>();
Mat_<uchar>::iterator itend = finalImg.end<uchar>();
for (; it != itend; ++it)
{
if ((*it) > 0) counter += 1;//二值化后,像素点是0或者255
}
cout << "area = " << counter* pixdistance* pixdistance << endl;
imshow("处理后的图", finalImg);
Mat normalImg = finalImg / 255;//归一化,用于图像细化操作
Mat bond = thinImage(normalImg);
filterOver(bond);
bond *= 255;//还原为细化后的灰度图
imshow("细化后", bond);
//查找白色像素点,但没想清楚如何计算裂缝的长度
vector<Point> pwhite;
if (bond.cols >= bond.rows)//横向裂缝
{
for (int j = 0; j < bond.cols; j++)
{
for (int i = 0; i < bond.rows; i++)
{
if (bond.at<uchar>(i, j) > 0)
pwhite.push_back(Point(j, i));
}
}
}
else//纵向裂缝
{
for (int i = 0; i < bond.rows; i++)
{
for (int j = 0; j < bond.cols; j++)
{
if (bond.at<uchar>(i, j) > 0)
pwhite.push_back(Point(j, i));
}
}
}
double length = 0;
int xthreshold = src.cols * 0.1;
int ythreshold = src.rows * 0.1;
for (int i = 0; i < pwhite.size()-1; i++)
{
int xdiff = abs(pwhite[i].x - pwhite[i + 1].x);
int ydiff = abs(pwhite[i].y - pwhite[i + 1].y);
if (xdiff > xthreshold || ydiff > ythreshold) continue;
length += getDistance(pwhite[i], pwhite[i + 1]);
src.at<Vec3b>(pwhite[i])[0] = 0;
src.at<Vec3b>(pwhite[i])[1] = 0;
src.at<Vec3b>(pwhite[i])[2] = 255;
/*circle(src, pwhite[i], 2, Scalar(0, 0, 255), 1);*/
}
src.at<Vec3b>(pwhite[pwhite.size()-1])[0] = 0;
src.at<Vec3b>(pwhite[pwhite.size()-1])[1] = 0;
src.at<Vec3b>(pwhite[pwhite.size()-1])[2] = 255;
cout << "length = "<<length * pixdistance << endl;
imshow("src", src);
waitKey(0);
return 0;
}
//图像细化的鼻祖代码
cv::Mat thinImage(const cv::Mat& src, const int maxIterations)
{
assert(src.type() == CV_8UC1);
cv::Mat dst;
int width = src.cols;
int height = src.rows;
src.copyTo(dst);
int count = 0; //记录迭代次数
while (true)
{
count++;
if (maxIterations != -1 && count > maxIterations) //限制次数并且迭代次数到达
break;
std::vector<uchar*> mFlag; //用于标记需要删除的点
//对点标记
for (int i = 0; i < height; ++i)
{
uchar* p = dst.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{
//如果满足四个条件,进行标记
// p9 p2 p3
// p8 p1 p4
// p7 p6 p5
uchar p1 = p[j];
if (p1 != 1) continue;
uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
uchar p8 = (j == 0) ? 0 : *(p + j - 1);
uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);
uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);
uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);
uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);
uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);
uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);
if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6)
{
int ap = 0;
if (p2 == 0 && p3 == 1) ++ap;
if (p3 == 0 && p4 == 1) ++ap;
if (p4 == 0 && p5 == 1) ++ap;
if (p5 == 0 && p6 == 1) ++ap;
if (p6 == 0 && p7 == 1) ++ap;
if (p7 == 0 && p8 == 1) ++ap;
if (p8 == 0 && p9 == 1) ++ap;
if (p9 == 0 && p2 == 1) ++ap;
if (ap == 1 && p2 * p4 * p6 == 0 && p4 * p6 * p8 == 0)
{
//标记
mFlag.push_back(p + j);
}
}
}
}
//将标记的点删除
for (std::vector<uchar*>::iterator i = mFlag.begin(); i != mFlag.end(); ++i)
{
**i = 0;
}
//直到没有点满足,算法结束
if (mFlag.empty())
{
break;
}
else
{
mFlag.clear();//将mFlag清空
}
//对点标记
for (int i = 0; i < height; ++i)
{
uchar* p = dst.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{
//如果满足四个条件,进行标记
// p9 p2 p3
// p8 p1 p4
// p7 p6 p5
uchar p1 = p[j];
if (p1 != 1) continue;
uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
uchar p8 = (j == 0) ? 0 : *(p + j - 1);
uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);
uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);
uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);
uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);
uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);
uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);
if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6)
{
int ap = 0;
if (p2 == 0 && p3 == 1) ++ap;
if (p3 == 0 && p4 == 1) ++ap;
if (p4 == 0 && p5 == 1) ++ap;
if (p5 == 0 && p6 == 1) ++ap;
if (p6 == 0 && p7 == 1) ++ap;
if (p7 == 0 && p8 == 1) ++ap;
if (p8 == 0 && p9 == 1) ++ap;
if (p9 == 0 && p2 == 1) ++ap;
if (ap == 1 && p2 * p4 * p8 == 0 && p2 * p6 * p8 == 0)
{
//标记
mFlag.push_back(p + j);
}
}
}
}
//将标记的点删除
for (std::vector<uchar*>::iterator i = mFlag.begin(); i != mFlag.end(); ++i)
{
**i = 0;
}
//直到没有点满足,算法结束
if (mFlag.empty())
{
break;
}
else
{
mFlag.clear();//将mFlag清空
}
}
return dst;
}
/**
* @brief 对骨骼化图数据进行过滤,实现两个点之间至少隔一个空白像素
* @param thinSrc为输入的骨骼化图像,8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白
*/
void filterOver(cv::Mat thinSrc)
{
assert(thinSrc.type() == CV_8UC1);
int width = thinSrc.cols;
int height = thinSrc.rows;
for (int i = 0; i < height; ++i)
{
uchar* p = thinSrc.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{
// 实现两个点之间至少隔一个像素
// p9 p2 p3
// p8 p1 p4
// p7 p6 p5
uchar p1 = p[j];
if (p1 != 1) continue;
uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
uchar p8 = (j == 0) ? 0 : *(p + j - 1);
uchar p2 = (i == 0) ? 0 : *(p - thinSrc.step + j);
uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - thinSrc.step + j + 1);
uchar p9 = (i == 0 || j == 0) ? 0 : *(p - thinSrc.step + j - 1);
uchar p6 = (i == height - 1) ? 0 : *(p + thinSrc.step + j);
uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + thinSrc.step + j + 1);
uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + thinSrc.step + j - 1);
if (p2 + p3 + p8 + p9 >= 1)
{
p[j] = 0;
}
}
}
}
/**
* @brief 从过滤后的骨骼化图像中寻找端点和交叉点
* @param thinSrc为输入的过滤后骨骼化图像,8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白
* @param raudis卷积半径,以当前像素点位圆心,在圆范围内判断点是否为端点或交叉点
* @param thresholdMax交叉点阈值,大于这个值为交叉点
* @param thresholdMin端点阈值,小于这个值为端点
* @return 为对src细化后的输出图像,格式与src格式相同,元素中只有0与1,1代表有元素,0代表为空白
*/
std::vector<cv::Point> getPoints(const cv::Mat& thinSrc, unsigned int raudis, unsigned int thresholdMax, unsigned int thresholdMin )
{
assert(thinSrc.type() == CV_8UC1);
int width = thinSrc.cols;
int height = thinSrc.rows;
cv::Mat tmp;
thinSrc.copyTo(tmp);
std::vector<cv::Point> points;
for (int i = 0; i < height; ++i)
{
for (int j = 0; j < width; ++j)
{
if (*(tmp.data + tmp.step * i + j) == 0)
{
continue;
}
int count = 0;
for (int k = i - raudis; k < i + raudis + 1; k++)
{
for (int l = j - raudis; l < j + raudis + 1; l++)
{
if (k < 0 || l < 0 || k>height - 1 || l>width - 1)
{
continue;
}
else if (*(tmp.data + tmp.step * k + l) == 1)
{
count++;
}
}
}
if (count > thresholdMax || count < thresholdMin)
{
Point point(j, i);
points.push_back(point);
}
}
}
return points;
}
/***************************************************************
FunctionName: getDistance
Purpose: 计算点之间的距离
Parameter: 两个point
Return: 两点之间的距离
****************************************************************/
double getDistance(Point pointO, Point pointA)
{
double distance;
distance = powf((pointO.x - pointA.x), 2) + powf((pointO.y - pointA.y), 2);
distance = sqrtf(distance);
return distance;
}
Mat thinImage(const cv::Mat& src, const int maxIterations = -1);
void filterOver(cv::Mat thinSrc);
vector<cv::Point> getPoints(const cv::Mat& thinSrc, unsigned int raudis = 4, unsigned int thresholdMax = 6, unsigned int thresholdMin = 4);
double getDistance(Point pointO, Point pointA);