功能
对检测的轮廓边缘有锯齿以及凹凹凸凸的情况,最大程度的去对边缘进行平滑
流程
- 从已经通过深度学习模型分割后的视频以一定帧率截图出来,保存在文件夹中;
- 从该文件夹中读取所有图片;
- 先对图片 预处理 ,转为灰度图,阈值化,中值滤波,开运算;
- 平滑操作 检测轮廓凸包 ,对凸包进行填充,这样可以得到一个平滑的标准多边形,但是此处会丢失过多大面积区域;
- 对大面积区域进行筛选(大面积通过原图与凸包填充图相减),当面积大于某个限度值时,则保留这部分区域,小于则丢弃,小于这个限度值尽量保证是那些不平滑的锯齿块;
- 再将上一步得到的图像应该保留的区域与凸包填充图相减,这样就可以得到不丢失大部分区域的平滑图像;
- 对检测的图片进行保存至文件夹中。
对比图
:
代码
部分代码被注释,计算两点距离函数,计算线段的斜率,有尝试用三次贝兹对轮廓进行圆滑操作,方法2是通过对轮廓找线,筛选线,但是线段的顺序不一定,未使用该方法
#include"opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include<iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include "opencv2/core/utility.hpp"
#include "opencv2/imgcodecs.hpp"
#include <opencv2/opencv.hpp>
#include <assert.h>
#include <cmath>
#include <cstdint>
#include <exception>
#include <vector>
#include <math.h>
using namespace cv;
using namespace std;
#define PI acos(-1)
float QuadraticBezier(float p0, float p1, float p2, float p3, float t) {
float u = 1.0f - t;
float tt = t * t;
float uuu = u * u*u;
float p = uuu * p0 + 3.0f * u*u * t * p1 +3*u* tt * p2+tt*t*p3;
return p;
}
vector<float> DrawQuadraticBezier(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float precision) {
for (float t = 0.0f; t <= 1.0f; t += precision) {
float x = QuadraticBezier(x0, x1, x2,x3, t);
float y = QuadraticBezier(y0, y1, y2,y3, t);
// 在此处处理插值点,例如绘制像素或保存到图像中
//std::cout << "(" << x << ", " << y << ")" << std::endl;
return { x,y };
}
}
double getDistance(Point2f point1, Point2f point2)
{
double distance = sqrtf(powf((point1.x - point2.x), 2) + powf((point1.y - point2.y), 2));
return distance;
}
double CalculateAngle(Point Mar1Point, Point Mar2Point)
{
double k = (double)(Mar2Point.y - Mar1Point.y) / (Mar2Point.x - Mar1Point.x); //计算斜率
double arcLength1 = atan(k); //弧度
double current_angle = arcLength1 * 180 / PI; //角度
return current_angle;
}
int main()
{
try
{
system("color 5E"); //设置控制台颜色
//直接读取视频截成图像
//VideoCapture::VideoCapture("C:/Users/J24050360/Desktop/test/video/model2_original_test2_out.avi");
//VideoCapture capture("C:/Users/J24050360/Desktop/test/video/model2_original_test2_out.avi");
//if (!capture.isOpened())
// cout << "fail to open!" << endl;
// //获取整个帧数
// long totalFrameNumber = capture.get(CAP_PROP_FRAME_COUNT);
// cout << "整个视频共" << totalFrameNumber << "帧" << endl;
// //设置开始帧()
// long frameToStart = 1000;
// capture.set(CAP_PROP_POS_FRAMES, frameToStart);
// cout << "从第" << frameToStart << "帧开始读" << endl;
// //设置结束帧
// int frameToStop = 3000;
// if (frameToStop < frameToStart)
// {
// cout << "结束帧小于开始帧,程序错误,即将退出!" << endl;
// return -1;
// }
// else
// {
// cout << "结束帧为:第" << frameToStop << "帧" << endl;
// }
// //获取帧率
// double rate = capture.get(CAP_PROP_FPS);
// cout << "帧率为:" << rate << endl;
// //定义一个用来控制读取视频循环结束的变量
// bool stop = false;
// //承载每一帧的图像
// Mat frame;
// //显示每一帧的窗口
// //namedWindow( "Extractedframe" );
// //两帧间的间隔时间:
// //int delay = 1000/rate;
// double delay = 1000 / rate;
// //利用while循环读取帧
// //currentFrame是在循环体中控制读取到指定的帧后循环结束的变量
// long currentFrame = frameToStart;
// while (!stop)
// {
// //读取下一帧
// if (!capture.read(frame))
// {
// cout << "读取视频失败" << endl;
// return -1;
// }
// //cout << "正在读取第" << currentFrame << "帧" << endl;
// //imshow( "Extractedframe", frame );
// //此处为跳帧操作
// if (currentFrame % 10 == 0) //此处为帧数间隔,修改这里就可以了
// {
// cout << "正在写第" << currentFrame << "帧" << endl;
// stringstream str;
// str << "C:/Users/J24050360/Desktop/test/video/resultimage" << currentFrame << ".png"; /*图片存储位置*/
// cout << str.str() << endl;
// imwrite(str.str(), frame);
// }
// //waitKey(intdelay=0)当delay≤ 0时会永远等待;当delay>0时会等待delay毫秒
// //当时间结束前没有按键按下时,返回值为-1;否则返回按键
// int c = waitKey(delay);
// //按下ESC或者到达指定的结束帧后退出读取视频
// if ((char)c == 27 || currentFrame > frameToStop)
// {
// stop = true;
// }
// //按下按键后会停留在当前帧,等待下一次按键
// if (c >= 0)
// {
// waitKey(0);
// }
// currentFrame++;
// }
// //关闭视频文件
// capture.release();
//遍历文件夹下面的所有图片
//Mat src = imread("C:/Users/J24050360/Desktop/resultimage1820.png");
string path = "C:/Users/J24050360/Desktop/test/videoimage/*.png";
vector<String> fn;
glob(path, fn, false); //false表示不遍历子文件夹
size_t count = fn.size();
for (int i = 0; i < count; i++)
{
Mat src = imread(fn[i]);
Mat image = src.clone();
//对视频中截取出来的图片进行预处理
cv::cvtColor(image, image, COLOR_RGB2GRAY);
cv::threshold(image, image, 200, 255, THRESH_BINARY);
//二值化之后做一次中值滤波
medianBlur(image, image, 7);
Mat element = getStructuringElement(MORPH_RECT, Size(7, 7));
morphologyEx(image, image, MORPH_OPEN, element); //开运算--先腐蚀后膨胀,平滑大物体边界
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(image, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
sort(contours.begin(), contours.end(), [](const auto& d1, const auto& d2) {return arcLength(d1, true) > arcLength(d2, true); });
// 绘制轮廓
Mat result(image.size(), CV_8UC1, Scalar(0, 0, 0));
vector<double>ContoursAreas;
if (contours.size() < 1)
{
return 0;
}
int contoursize = contours.size();
for (size_t i = 0; i < contoursize; i++)
{
Scalar color(255, 255, 255);
drawContours(result, contours, static_cast<int>(i), color, 2, LINE_8, hierarchy, 0);
ContoursAreas.push_back(contourArea(contours[i]));
}
//方法一:凸包检测,再多边形填充,填充后对损失的区域进行筛选留下细节
cv::Mat anti_aliasing_image(image.size(), CV_8U, cv::Scalar(0));
cv::Mat sub_image(image.size(), CV_8U, cv::Scalar(0));
vector<vector<Point>> hull(contours.size());
for (int i = 0; i < contours.size(); i++)
{
convexHull(Mat(contours[i]), hull[i], false); //凸包检测
}
for (int i = 0; i < contours.size(); i++)
{
cv::fillConvexPoly(anti_aliasing_image, hull[i], (255, 255, 255), cv::LINE_AA); //多边形填充
}
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
if (((anti_aliasing_image.at<uchar>(i, j)) != 255))
{
anti_aliasing_image.at<uchar>(i, j) = 0;
}
}
}
subtract(anti_aliasing_image, image, sub_image);
vector<vector<Point>> contours_sub;
vector<Vec4i> hierarchy_sub;
findContours(sub_image, contours_sub, hierarchy_sub, RETR_EXTERNAL, CHAIN_APPROX_NONE);
sort(contours_sub.begin(), contours_sub.end(), [](const auto& d1, const auto& d2) {return contourArea(d1, true) > contourArea(d2, true); });
// 绘制轮廓
Mat result_subTemp(image.size(), CV_8UC1, Scalar(0, 0, 0));
Mat result_sub(image.size(), CV_8UC1, Scalar(0, 0, 0));
vector<double>ContoursAreas_sub;
if (contours_sub.size() < 1)
{
return 0;
}
for (size_t i = 0; i < contours_sub.size(); i++)
{
Scalar color(255, 255, 255);
if (contourArea(contours_sub[i]) > 100)
{
drawContours(result_subTemp, contours_sub, static_cast<int>(i), color, -1, LINE_8, hierarchy, 0);
ContoursAreas_sub.push_back(contourArea(contours_sub[i]));
}
}
subtract(anti_aliasing_image, result_subTemp, result_sub);
//图像处理结束,对这些图片进行保存处理
String ImgName = fn[i];
string::size_type iPos = ImgName.find_last_of('\\') + 1;
string filename = ImgName.substr(iPos, ImgName.length() - iPos);
string name = filename.substr(0, filename.rfind("."));
if (ImgName.find(".png") != String::npos)
{
string::size_type ipos = ImgName.length() - filename.length();
string::size_type ipos1 = ImgName.length();
//string path = ImgName.substr(0, ipos);
string path = "C:/Users/J24050360/Desktop/test/videoimage_result\\";
imwrite(path + name + ".png", result_sub);//文件夹必须提前创建,否则代码执行没有效果
//imwrite("C:/Users/J24050360/Desktop/1/resultimage360_sub.png", result_sub);
}
}
//方法二,形态学变换,开运算后找直线,通过直线来拟合,将多个小段直线作为一条直线----线段排序无规则,无法判定上条直线与下条直线是相邻的
//Mat Hough_image(image.size(), CV_8U, cv::Scalar(0));
//vector<Vec4i> lines_temp; // 存储检测到的直线
//vector<Vec4i> lines1,lines2; //存储筛选后的线段
//HoughLinesP(result, lines_temp, 1, CV_PI / 180, 10, 5, 30); // 应用霍夫直线检测算法
//for (size_t i = 1; i < lines_temp.size(); i++)
//{
// Vec4i L = lines_temp[i];
// Vec4i L_first = lines_temp[i-1];
// if (fabs(CalculateAngle(Point(L_first[2], L_first[3]), Point(L[0], L[1])) < 2)) //计算角度,上一条直线与下一条直线角度之差在范围内时则作为一条直线
// {
// line(Hough_image, Point(L_first[0], L_first[1]), Point(L[2], L[3]), Scalar(255, 255, 255), 1, LINE_AA);
// Vec4i L_result1 = { L_first[0], L_first[1], L[2], L[3] };
// lines1.push_back(L_result1);
// }
// else
// {
// line(Hough_image, Point(L_first[0], L_first[1]), Point(L_first[2], L_first[3]), Scalar(255, 255, 255), 1, LINE_AA);
// Vec4i L_result2 = { L_first[0], L_first[1], L_first[2], L_first[3] };
// lines2.push_back(L_result2);
// }
//
//}
//vector<vector<Point>> contours_line;
//vector<Vec4i> hierarchy_line;
//findContours(Hough_image, contours_line, hierarchy_line, RETR_EXTERNAL, CHAIN_APPROX_NONE);
//sort(contours_line.begin(), contours_line.end(), [](const auto& d1, const auto& d2) {return arcLength(d1, true) > arcLength(d2, true); });
绘制轮廓
//Mat contours_lineimage(image.size(), CV_8UC1, Scalar(0, 0, 0));
//if (contours_line.size() < 1)
//{
// return 0;
//}
//for (size_t i = 0; i < contours_line.size(); i++)
//{
// Scalar color(255, 255, 255);
// drawContours(contours_lineimage, contours_line, static_cast<int>(i), color, 2, LINE_8, hierarchy, 0);
//}
}
catch (cv::Exception& e)
{
std::cout << e.what() << std::endl;
}
waitKey(0);
return 0;
}