前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >二寸照片识别/切边/矫正

二寸照片识别/切边/矫正

作者头像
周旋
发布2020-08-11 14:24:44
1.4K0
发布2020-08-11 14:24:44
举报
文章被收录于专栏:行走的机械人行走的机械人

前言

今天我们要做的就是从左图转换到右图,左图中证件照有轻微的倾斜。

大体思路可以描述为Canny边缘检测-形态学闭操作-轮廓检测-Hough直线检测-确定四个角点-透视变换。

源码下载链接放文末了。

本次代码并不只是纯调API,很多算法逻辑是自己实现的,文字讲解这类代码效果并不好,近期会在B站上传一份视频版讲解,大家可以搜索【周旋学opencv】关注一下或者点击文末原文链接直达。

一:图像预处理

图像预处理就是套路了,先读取原图再转灰度图,然后进行Canny边缘检测。为去除一些黑洞并达到强化边缘效果,还需进行形态学闭操作。

代码语言:javascript
复制
//【1】读取原图片以及投影模板
Mat srcImage, dstImage;
srcImage = imread("2.jpg",33);//读取倾斜图像
imshow("【1】原图", srcImage);
//【2】转灰度图
Mat gray_src, binary, dst;
cvtColor(srcImage, gray_src, COLOR_BGR2GRAY);
imshow("【2】灰度图", gray_src);
//【3】边缘检测并查找轮廓
Canny(gray_src, gray_src, 10, 70, 3);
imshow("【3】canny边缘检测", gray_src);
//【4】形态学操作
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(gray_src, gray_src, MORPH_CLOSE, kernel, Point(-1, -1), 3);
imshow("【4】形态学闭操作", gray_src);

二:查找轮廓并进行绘制

查找轮廓并绘制就是很简单的图像处理套路了,主要用到的就是findCounts函数和drawCounts函数。

代码语言:javascript
复制
//【5】查找,筛选,绘制轮廓
vector<vector<Point>> contours;
vector<Vec4i> hireachy;
findContours(gray_src, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
// 轮廓绘制
double width = srcImage.cols;
double height = srcImage.rows;
Mat drawImage = Mat::zeros(srcImage.size(), CV_8UC3);
for (int t = 0; t < contours.size(); t++) {
  Rect rect = boundingRect(contours[t]);
  if (rect.width > width / 2 && rect.width < width - 5) {
    drawContours(drawImage, contours, static_cast<int>(t), Scalar(0, 0, 255), 2, 8, hireachy, 0, Point());
  }
}
imshow("【5】绘制轮廓图", drawImage);

三:直线检测并计算直线斜率等

我们对得到的轮廓图进行Hough直线检测,并在新的图像中将检测到的实现绘制出来。在for循环绘制直线时,我们顺便根据霍夫直线检测返回的lines直线两个端点(x_1,y_1)以及(x_2,y_2)计算直线斜率以及到整幅图像中心点center(右图红圈)的距离。

代码语言:javascript
复制
//【6】计算直线相关信息
double k[20] = { 0 }, c[20] = {0};//直线斜率K,常数项C y=kx+c
Point2d center;//原图像正中心
center.y = srcImage.rows / 2.0;
center.x = srcImage.cols / 2.0;
double length[20] = { 0 };//距离直线的距离
//霍夫线检测
Mat contoursImg;
int accu = min(width*0.5, height*0.5);
cvtColor(drawImage, contoursImg, COLOR_BGR2GRAY);
vector<Vec4i> lines;
HoughLinesP(contoursImg, lines, 1, CV_PI / 180.0, accu, accu, 0);
Mat linesImage = Mat::zeros(srcImage.size(), CV_8UC3);
for (int t = 0; t < lines.size(); t++) { //遍历检测到的直线
  Vec4i ln = lines[t];
  int b_color = theRNG().uniform(0, 255); //随机颜色
  int g_color = theRNG().uniform(0, 255);
  int r_color = theRNG().uniform(0, 255);
  line(linesImage, Point(ln[0], ln[1]), Point(ln[2], ln[3]), Scalar(b_color, g_color, r_color), 2, 8, 0);//绘制直线
  if (ln[0] != ln[2]) {//当直线不垂直时
    k[t] = (ln[3] - ln[1]) / (ln[2] - ln[0]);
    c[t] = ln[1] - k[t] * ln[0];
    length[t] = abs(k[t] * center.x - center.y + c[t]) / sqrt((pow(k[t],2) + 1));
  }
  else {//直线垂直时
    k[t] = 1000;
    c[t] = (ln[3] - k[t] * ln[0]);
    //length[t] = abs(k[t] * center.x - center.y + c[t]) / pow((sqrt(k[t]) + 1), 0.5);
    length[t] = abs(ln[0] - center.x);
  }
}
printf("number of lines : %d\n", lines.size());
imshow("【6】检测直线", linesImage);

为什么要计算直线到中心点的距离呢?

由于二寸照片切边的存在,导致我们直线检测出的为双层框,所以我们计算各直线到图像正中心的距离,同一方向上距离近的即为内层框,也就是我们想要的。

下一步对直线筛选,这一步仅是进行计算和数据准备。

四:寻找与定位上下左右四条直线

左图四条红色线即为我们找到的上下左右边框线,如何实现的呢?

我们首先遍历所有直线,然后根据直线两个端点坐标判断直线属于顶部直线还是底部还是左侧还是右侧。

如何判断呢?如果直线两端点的x坐标均小于图像中心点x坐标,则说明直线为左侧竖直边框;如果直线两端点的y坐标均大于图像中心点y坐标,则说明直线为底部水平方向边框线。其余两侧同理。

而我们据此确定了直线方向后,就可以对同一方向的直线距中心距离进行比较,距离最近的即为我们要找的内侧线。

代码语言:javascript
复制
// 【7】寻找与定位上下左右四条直线
  int t = height;
  int b = height;
  int l = width;
  int r = width;
  int t_, b_, l_, r_; //数组坐标
  Vec4i topLine, bottomLine, leftLine, rightLine;
  Vec4i ln;
  cout << lines.size() << endl;
  cout << height << " 高:宽 " << width << endl;
  for (int i = 0; i < lines.size(); i++) {//遍历所有直线
    ln = lines[i];
    if (ln[3] < height / 2.0 && ln[1] < height / 2.0 ) {//顶部直线
      cout << "t:" << length[i] << endl;
      if (length[i] < t) {
        topLine = lines[i];
        t = length[i];
        t_ = i;
      }
      continue;
    }
    if (ln[0] < width / 2.0 && ln[2] < width / 2.0) {//左部直线
      cout << "l:" << length[i] << endl;
      if (length[i] < l) {
        leftLine = lines[i];
        l = length[i];
        l_ = i;
      }
      continue;
    }
    if (ln[3] > height / 2.0 && ln[1] > height / 2.0 ) {//底部直线
      cout << "b:" << length[i] << endl;
      if (length[i] < b) {
        bottomLine = lines[i];
        b= length[i];
        b_ = i;
      }
      continue;
    }
    if ((ln[0] > width / 2.0) && (ln[2] > width / 2.0)){//右部直线
      cout << "r:" << length[i] << endl;
      if (length[i] < r) {
        rightLine = lines[i];
        r = length[i];
        r_ = i;
      }
      continue;
    }
  }

检测识别完毕后进行打印显示:

代码语言:javascript
复制
//打印直线两个端点并绘制四条边框直线
cout << "top line : p1(x, y) = " << topLine[0] << "," << topLine[1] << " p2(x, y) = " << topLine[2] << "," << topLine[3] << endl;
cout << "bottom line : p1(x, y) = " << bottomLine[0] << "," << bottomLine[1] << " p2(x, y) = " << bottomLine[2] << "," << bottomLine[3] << endl;
cout << "left line : p1(x, y) = " << leftLine[0] << "," << leftLine[1] << " p2(x, y) = " << leftLine[2] << "," << leftLine[3] << endl;
cout << "right line : p1(x, y) = " << rightLine[0] << "," << rightLine[1] << " p2(x, y) = " << rightLine[2] << "," << rightLine[3] << endl;

line(linesImage, Point(topLine[0], topLine[1]), Point(topLine[2], topLine[3]), Scalar(0, 0, 255), 2, 8, 0);
line(linesImage, Point(bottomLine[0], bottomLine[1]), Point(bottomLine[2], bottomLine[3]), Scalar(0, 0, 255), 2, 8, 0);
line(linesImage, Point(leftLine[0], leftLine[1]), Point(leftLine[2], leftLine[3]), Scalar(0, 0, 255), 2, 8, 0);
line(linesImage, Point(rightLine[0], rightLine[1]), Point(rightLine[2], rightLine[3]), Scalar(0, 0, 255), 2, 8, 0);
namedWindow("【7】筛选直线", WINDOW_AUTOSIZE);
imshow("【7】筛选直线", linesImage);

据此我们就画出了左图中四条红色的内侧线了。

四:计算四个顶点并透视变换

我们现在已知四条直线,就可以计算出四条直线的四个交点了(中图蓝色的四个点)。

得到四个交点,我们就已经成功把证件照提取出来了,然后只需进行透视变换,就可以得到矫正后的图像了。

代码语言:javascript
复制
//【8】计算四个定点并透视变换
// 四条直线交点
Point p1; // 左上角
p1.x = static_cast<int>((c[t_] - c[l_]) / (k[l_] - k[t_]));
p1.y = static_cast<int>(k[t_]*p1.x + c[t_]);
Point p2; // 右上角
p2.x = static_cast<int>((c[t_] - c[r_]) / (k[r_] - k[t_]));
p2.y = static_cast<int>(k[t_]*p2.x + c[t_]);
Point p3; // 左下角
p3.x = static_cast<int>((c[b_] - c[l_]) / (k[l_] - k[b_]));
p3.y = static_cast<int>(k[b_]*p3.x + c[b_]);
Point p4; // 右下角
p4.x = static_cast<int>((c[b_] - c[r_]) / (k[r_] - k[b_]));
p4.y = static_cast<int>(k[b_]*p4.x + c[b_]);
cout << "p1(x, y)=" << p1.x << "," << p1.y << endl;
cout << "p2(x, y)=" << p2.x << "," << p2.y << endl;
cout << "p3(x, y)=" << p3.x << "," << p3.y << endl;
cout << "p4(x, y)=" << p4.x << "," << p4.y << endl;

// 显示四个点坐标
circle(linesImage, p1, 2, Scalar(255, 0, 0), 2, 8, 0);
circle(linesImage, p2, 2, Scalar(255, 0, 0), 2, 8, 0);
circle(linesImage, p3, 2, Scalar(255, 0, 0), 2, 8, 0);
circle(linesImage, p4, 2, Scalar(255, 0, 0), 2, 8, 0);
imshow("【7】筛选直线", linesImage);

// 透视变换
vector<Point2f> src_corners(4);
src_corners[0] = p1;
src_corners[1] = p2;
src_corners[2] = p3;
src_corners[3] = p4;

vector<Point2f> dst_corners(4);
dst_corners[0] = Point(0, 0);
dst_corners[1] = Point(width, 0);
dst_corners[2] = Point(0, height);
dst_corners[3] = Point(width, height);

// 获取透视变换矩阵
Mat resultImage;
Mat warpmatrix = getPerspectiveTransform(src_corners, dst_corners);
warpPerspective(srcImage, resultImage, warpmatrix, resultImage.size(), INTER_LINEAR);
imshow("【8】矫正图像", resultImage);

源码下载链接见文末。

THE END

出于尊重,声明一下本文灵感来源与贾志刚老师小案例实战课程,课程链接

https://edu.51cto.com/course/8354.html

但核心处理方法自己想的//

源码链接: https://pan.baidu.com/s/1aE3f4yw_m4nrtuCwydO7-w 提取码:hj0r

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Opencv视觉实践 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档