前言
1.关于边缘检测,我这里用了HED这个边缘检测网络,HED创作于2015年,骨干网络是state-of-the-art的VGG-16,并且使用迁移学习初始化了网络权重。关于HED的算法原理与训练模型代码可以转到github。
2.OpenCV也有好几边缘检测算法可用,就拿最常用的Canny算法来说,对背景复杂一点的图像,手动调参往往是这个场景下边缘很完美的提取出来,当换个有差异的场景,干扰源不一样,要得到好一些的效果,参数阈值又得重新调一遍,如果要做一个能商用的项目,Canny算法是不能完美解决边缘提取的问题。
项目流程
1.先用HED提取身份证边缘。
2.用霍夫变换直线检测对边缘图像做直线检测。
3.对检测到的直线做排序与筛选,得到最终的边缘。
4.找到边缘线的相交点。
5.以四条线的四个相交点来矫正得到的最终身份证目标。
代码实现
1.使用用OpenCV的DNN来做模型推理,得到最终于的边缘图像。
int hedEdge(cv::dnn::Net& edge_net, cv::Mat& cv_book, cv::Mat& cv_hed)
{
if (cv_book.empty())
{
return -2;
}
cv::Mat cv_gamma;
cv::Size reso(512, 512);
cv::Mat blob = cv::dnn::blobFromImage(cv_book, 1.0 / 255.0, reso, cv::Scalar(0, 0, 0), true, false);
edge_net.setInput(blob);
cv_hed = edge_net.forward();
cv::resize(cv_hed.reshape(1, reso.height), cv_hed, cv_book.size(), cv::INTER_LINEAR);
cv_hed.convertTo(cv_hed, CV_8UC1, 255);
return 0;
}
得到边缘图像:
2.对得到的边缘图像进行直线检测并分出垂直与水平线。
struct Line
{
cv::Point LP1;
cv::Point LP2;
cv::Point LC;
Line(cv::Point p1, cv::Point p2)
{
LP1 = p1;
LP2 = p2;
LC = cv::Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
};
void lineDetection(cv::Mat cv_edge,std::vector<Line> &h_lines, std::vector<Line>& v_lines)
{
cv::Mat cv_dilate, cv_erode;
cv::Mat element_e = getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5), cv::Point(-1, -1));
cv::Mat element_d = getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1));
cv::erode(cv_edge, cv_erode, element_e);
cv::dilate(cv_erode, cv_dilate, element_d);
std::vector<cv::Vec4i> lines;
HoughLinesP(cv_dilate, lines, 1, CV_PI / 180, 100,80, 8);
for (size_t i = 0; i < lines.size(); i++)
{
cv::Vec4i v = lines[i];
double delta_x = v[0] - v[2], delta_y = v[1] - v[3];
Line l(cv::Point(v[0], v[1]), cv::Point(v[2], v[3]));
if (fabs(delta_x) > fabs(delta_y))
{
h_lines.push_back(l);
}
else
{
v_lines.push_back(l);
}
}
}
运行结果:
3.提取最终边缘线并得到四个矫正的点。
bool cmpLineY(const Line& p1, const Line& p2)
{
return p1.LC.y < p2.LC.y;
}
bool cmpLineX(const Line& p1, const Line& p2)
{
return p1.LC.x < p2.LC.x;
}
cv::Point2f computeIntersect(Line l1, Line l2)
{
int x1 = l1.LP1.x, x2 = l1.LP2.x, y1 = l1.LP1.y, y2 = l1.LP2.y;
int x3 = l2.LP1.x, x4 = l2.LP2.x, y3 = l2.LP1.y, y4 = l2.LP2.y;
if (float d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4))
{
cv::Point2f pt;
pt.x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
pt.y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
return pt;
}
return cv::Point2f(-1, -1);
}
void screeningLine(cv::Mat cv_src, std::vector<Line> &h_lines, std::vector<Line> &v_lines, std::vector<cv::Point> &out_points)
{
if (h_lines.size() >= 2 && v_lines.size() >= 2)
{
std::sort(h_lines.begin(), h_lines.end(), cmpLineY);
std::sort(v_lines.begin(), v_lines.end(), cmpLineX);
out_points.push_back(computeIntersect(h_lines[0], v_lines[0]));
out_points.push_back(computeIntersect(h_lines[0], v_lines[v_lines.size() - 1]));
out_points.push_back(computeIntersect(h_lines[h_lines.size() - 1], v_lines[0]));
out_points.push_back(computeIntersect(h_lines[h_lines.size() - 1], v_lines[v_lines.size() - 1]));
}
else
{
out_points.push_back(cv::Point2f(2, 2));
out_points.push_back(cv::Point2f(2, cv_src.rows - 2));
out_points.push_back(cv::Point2f(cv_src.cols - 2, 2));
out_points.push_back(cv::Point2f(cv_src.cols - 2, cv_src.rows - 2));
}
}
运行结果:
4.按四个点矫正图像。
int reviseImage(cv::Mat& cv_src, cv::Mat& cv_dst, std::vector<cv::Point>& in_points)
{
cv::Point point_f, point_b;
point_f.x = (in_points.at(0).x < in_points.at(2).x) ? in_points.at(0).x : in_points.at(2).x;
point_f.y = (in_points.at(0).y < in_points.at(1).y) ? in_points.at(0).y : in_points.at(1).y;
point_b.x = (in_points.at(3).x > in_points.at(1).x) ? in_points.at(3).x : in_points.at(1).x;
point_b.y = (in_points.at(3).y > in_points.at(2).y) ? in_points.at(3).y : in_points.at(2).y;
//代码取目标的最小外接矩形,但倾斜45度时会出现比例变形的现象
cv::Rect rect(point_f, point_b);
cv_dst = cv::Mat::zeros(rect.height, rect.width, CV_8UC3);
std::vector<cv::Point2f> dst_pts;
dst_pts.push_back(cv::Point2f(0, 0));
dst_pts.push_back(cv::Point2f(rect.width - 1, 0));
dst_pts.push_back(cv::Point2f(0, rect.height - 1));
dst_pts.push_back(cv::Point2f(rect.width - 1, rect.height - 1));
std::vector<cv::Point2f> tr_points;
tr_points.push_back(in_points.at(0));
tr_points.push_back(in_points.at(1));
tr_points.push_back(in_points.at(2));
tr_points.push_back(in_points.at(3));
cv::Mat transmtx = getPerspectiveTransform(tr_points, dst_pts);
warpPerspective(cv_src, cv_dst, transmtx, cv_dst.size());
return 0;
}
运行结果: