- 在此记录处理业务当中查询到的一些重要的图像处理函数及其用法,以便查看
Canny
函数功能:采用Canny方法对图像进行边缘检测
函数原型:
void cvCanny(
const CvArr* image,
CvArr* edges,
double threshold1,double threshold2,
int aperture_size=3
);
函数说明:
第一个参数表示输入图像,必须为单通道灰度图。
第二个参数表示输出的边缘图像,为单通道黑白图。
第三个参数和第四个参数表示阈值,这二个阈值中当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割即如果一个像素的梯度大与上限值,则被认为是边缘像素,如果小于下限阈值,则被抛弃。如果该点的梯度在两者之间则当这个点与高于上限值的像素点连接时我们才保留,否则删除。
第五个参数表示Sobel 算子大小,默认为3即表示一个3*3的矩阵。Sobel 算子与高斯拉普拉斯算子都是常用的边缘算子,详细的数学原理可以查阅专业书籍。
为了更好的使用cvCanny()函数,下面再介绍二个实用的函数,这二个函数对后面的程序实现非常有帮助。
图片旋转
- 第一种形式
int _tmain(int argc, _TCHAR* argv[])
{
cv::Mat image = cv::imread("E:\\lena.jpg");
if (image.empty())
{
std::cout<<"read image failure"<<std::endl;
return -1;
}
cv::Point2f center = cv::Point2f(image.cols / 2, image.rows / 2); // 旋转中心
double angle = 30; // 旋转角度
double scale = 1; // 缩放尺度
cv::Mat rotateMat;
rotateMat = cv::getRotationMatrix2D(center, angle, scale);
cv::Mat rotateImg;
cv::warpAffine(image, rotateImg, rotateMat, image.size());
cv::imwrite("E:\\rotate.jpg", rotateImg);
return 0;
}
- 效果展示
原图
旋转后的图-大小没变
旋转后的图的边角不可避免的损失了。
可以进行先平移后旋转,改进代码参看以上原文 - 第二种形式
旋转整数角度(90,180,270这种)
Mat matRotateClockWise90(Mat src)
{
if (src.empty())
{
qDebug()<<"RorateMat src is empty!";
}
// 矩阵转置
transpose(src, src);
//0: 沿X轴翻转; >0: 沿Y轴翻转; <0: 沿X轴和Y轴翻转
flip(src, src, 1);// 翻转模式,flipCode == 0垂直翻转(沿X轴翻转),flipCode>0水平翻转(沿Y轴翻转),flipCode<0水平垂直翻转(先沿X轴翻转,再沿Y轴翻转,等价于旋转180°)
return src;
}
Mat matRotateClockWise180(Mat src)//顺时针180
{
if (src.empty())
{
qDebug() << "RorateMat src is empty!";
}
//0: 沿X轴翻转; >0: 沿Y轴翻转; <0: 沿X轴和Y轴翻转
flip(src, src, 0);// 翻转模式,flipCode == 0垂直翻转(沿X轴翻转),flipCode>0水平翻转(沿Y轴翻转),flipCode<0水平垂直翻转(先沿X轴翻转,再沿Y轴翻转,等价于旋转180°)
flip(src, src, 1);
return src;
//transpose(src, src);// 矩阵转置
}
Mat matRotateClockWise270(Mat src)//顺时针270
{
if (src.empty())
{
qDebug() << "RorateMat src is empty!";
}
// 矩阵转置
//transpose(src, src);
//0: 沿X轴翻转; >0: 沿Y轴翻转; <0: 沿X轴和Y轴翻转
transpose(src, src);// 翻转模式,flipCode == 0垂直翻转(沿X轴翻转),flipCode>0水平翻转(沿Y轴翻转),flipCode<0水平垂直翻转(先沿X轴翻转,再沿Y轴翻转,等价于旋转180°)
flip(src, src, 0);
return src;
}
Mat myRotateAntiClockWise90(Mat src)//逆时针90°
{
if (src.empty())
{
qDebug()<<"mat is empty!";
}
transpose(src, src);
flip(src, src, 0);
return src;
}
- 效果
在下面仿射变换当中可以用到
图像矫正
业务当中需要对图片进行扫描识别,那么第一步必然是纠偏矫正处理等,在此介绍矫正的处理过程
主要过程包括:
- 读取原图像
- 判别怎样第一次旋转
- 转为灰度图
- 二值化
- 找轮廓
- 定义ROI区域
- 旋转一定角度
- 找最外面的额轮廓
- 变换调整至fitsize大小
别的不多说,直接上代码,有些冗余部分
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
//第一个参数:输入图片名称;第二个参数:输出图片名称
void GetContoursPic(const char* pSrcFileName, const char* pDstFileName)
{
Mat srcImg = imread(pSrcFileName);
// 矩阵转置
transpose(srcImg, srcImg);
flip(srcImg, srcImg, 1);
imshow("原始图", srcImg);
Mat gray, binImg;
//灰度化
cvtColor(srcImg, gray, COLOR_RGB2GRAY);
imshow("灰度图", gray);
//二值化
threshold(gray, binImg, 100, 200, CV_THRESH_BINARY);
imshow("二值化", binImg);
vector<vector<Point> > contours;
vector<Rect> boundRect(contours.size());
//注意第5个参数为CV_RETR_EXTERNAL,只检索外框
findContours(binImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓
cout << contours.size() << endl;
for (int i = 0; i < contours.size(); i++)
{
//需要获取的坐标
CvPoint2D32f rectpoint[4];
CvBox2D rect = minAreaRect(Mat(contours[i]));
cvBoxPoints(rect, rectpoint); //获取4个顶点坐标
//与水平线的角度
float angle = rect.angle;
cout << angle << endl;
int line1 = sqrt((rectpoint[1].y - rectpoint[0].y)*(rectpoint[1].y - rectpoint[0].y) + (rectpoint[1].x - rectpoint[0].x)*(rectpoint[1].x - rectpoint[0].x));
int line2 = sqrt((rectpoint[3].y - rectpoint[0].y)*(rectpoint[3].y - rectpoint[0].y) + (rectpoint[3].x - rectpoint[0].x)*(rectpoint[3].x - rectpoint[0].x));
//rectangle(binImg, rectpoint[0], rectpoint[3], Scalar(255), 2);
//面积太小的直接pass
if (line1 * line2 < 600)
{
continue;
}
//为了让正方形横着放,所以旋转角度是不一样的。竖放的,给他加90度,翻过来
if (line1 > line2)
{
angle = 90 + angle;
}
//新建一个感兴趣的区域图,大小跟原图一样大
Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意这里必须选CV_8UC3
RoiSrcImg.setTo(0); //颜色都设置为黑色
//imshow("新建的ROI", RoiSrcImg);
//对得到的轮廓填充一下
drawContours(binImg, contours, -1, Scalar(255), CV_FILLED);
//抠图到RoiSrcImg
srcImg.copyTo(RoiSrcImg, binImg);
//再显示一下看看,除了感兴趣的区域,其他部分都是黑色的了
namedWindow("RoiSrcImg", 1);
imshow("RoiSrcImg", RoiSrcImg);
//创建一个旋转后的图像
Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);
RatationedImg.setTo(0);
//对RoiSrcImg进行旋转
Point2f center = rect.center; //中心点
Mat M2 = getRotationMatrix2D(center, angle, 1);//计算旋转加缩放的变换矩阵
warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(), 1, 0, Scalar(0));//仿射变换
imshow("旋转之后", RatationedImg);
imwrite("r.jpg", RatationedImg); //将矫正后的图片保存下来
}
#if 1
//对ROI区域进行抠图
//对旋转后的图片进行轮廓提取
vector<vector<Point> > contours2;
Mat raw = imread("r.jpg");
Mat SecondFindImg;
//SecondFindImg.setTo(0);
cvtColor(raw, SecondFindImg, COLOR_BGR2GRAY); //灰度化
threshold(SecondFindImg, SecondFindImg, 80, 200, CV_THRESH_BINARY);
findContours(SecondFindImg, contours2, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
//cout << "sec contour:" << contours2.size() << endl;
for (int j = 0; j < contours2.size(); j++)
{
//这时候其实就是一个长方形了,所以获取rect
Rect rect = boundingRect(Mat(contours2[j]));
//面积太小的轮廓直接pass,通过设置过滤面积大小,可以保证只拿到外框
if (rect.area() < 600)
{
continue;
}
Mat dstImg = raw(rect);
imshow("dst", dstImg);
imwrite(pDstFileName, dstImg);
}
#endif
}
void main()
{
GetContoursPic("3copy_meitu_2.jpg", "FinalImage.jpg");
waitKey();
}
业务中的图片不是这,需要隐私保护,放上测试图,大体意思是这

image.png

image.png

