1,dmLocation()中的blockThreshold()
原始的图片为480x640的图片,进过opencv的resize函数将图片缩小成240x640,因为光源照射而截取的图片,实际上并不是每一区域的光照强度都是相同的,即光照不均匀。因此,如果直接使用opencv中的threshold()将图片二值化,会含有较大的误差。所以,采取将图片分成一个个的小方格,分别求每个小方格的threshold()对每个小方格进行二值化。
1.1 使用opencv的Rect类将图片切成width*height的大小,通过(x,y)确定开始切的位置。
Mat srcCut, srcImag //srcCut存放切片后的方格图;srcImag输入的图片
Rect rect; //创建一个矩形实例
rect.width = 80; //设置成80*80的的小方格
rect.height = 80;
rect.x = 0; //从(x,y)坐标点处开始切
rect.y = 0;
srcCut = srcImag(rect); //srcCut成为了srcImag的0*0区域大小为80*80的引用数据
float meanV = mean(srcSec)[0] * _thresh;
threshold(srcSec, srcSec,meanV, 255, THRESH_BINARY_INV) //对srcCut这个区域二值化
threshold()
double threshold( InputArray src,
OutputArray dst,
double thresh,
double maxval,
int type );
其中,src为输入的图像矩阵;dst为输出的图像矩阵;thresh为阈值,图像矩阵的光照均值;maxval为输出的图像矩阵dst中的最大值;
Type参数类型如下:
参数类型 | 含义 |
---|---|
THRESH_BINARY | src中像素点如果大于thresh,则对应像素点位置的dst像素点就设为maxvel;否则为0 |
THRESH_BINARY_INV | 这个效果和THRESH_BINARY的效果翻过来 |
THRESH_TRUNC | src中像素点如果大于thresh,则对应像素点位置的dst像素点就设为maxvel;否则为src中的原始值 |
THRESH_TOZERO | src中像素点如果大于thresh,则对应像素点位置的dst像素点就设为src的值;否则为0 |
THRESH_TOZERO_INV | 和THRESH_TOZERO的效果相反 |
THRESH_MASK | 不支持 |
THRESH_OTSU | 不支持32位 |
THRESH_TRIANGLE | 不支持32位 |
具体可以描述成如下公式:
参数:THRESH_BINARY−−−−dst(x,y)={maxval,src(x,y)>thresh0,otherwise 参数:THRESH\_BINARY ---- dst(x,y)=\begin{cases} maxval, & src(x,y)>thresh\\ 0, & otherwise\\ \end{cases} 参数:THRESH_BINARY−−−−dst(x,y)={maxval,0,src(x,y)>threshotherwise
参数:THRESH_BINARY_INV−−−−dst(x,y)={0,src(x,y)>threshmaxval,otherwise 参数:THRESH\_BINARY\_INV ---- dst(x,y)=\begin{cases} 0, & src(x,y)>thresh\\ maxval, & otherwise\\ \end{cases} 参数:THRESH_BINARY_INV−−−−dst(x,y)={0,maxval,src(x,y)>threshotherwise
参数:THRESH_TRUNC−−−−dst(x,y)={threshhold,src(x,y)>threshsrc(x,y),otherwise 参数:THRESH\_TRUNC ---- dst(x,y)=\begin{cases} threshhold, & src(x,y)>thresh\\ src(x,y), & otherwise \\ \end{cases} 参数:THRESH_TRUNC−−−−dst(x,y)={threshhold,src(x,y),src(x,y)>threshotherwise
参数:THRESH_TOZERO−−−−dst(x,y)={src(x,y),src(x,y)>thresh0,otherwise 参数:THRESH\_TOZERO ---- dst(x,y)=\begin{cases} src(x,y), & src(x,y)>thresh\\ 0, & otherwise \end{cases} 参数:THRESH_TOZERO−−−−dst(x,y)={src(x,y),0,src(x,y)>threshotherwise
参数:THRESH_TOZERO−−−−dst(x,y)={0,src(x,y)>threshsrc(x,y),otherwise 参数:THRESH\_TOZERO ---- dst(x,y)=\begin{cases} 0, & src(x,y)>thresh\\ src(x,y), & otherwise \end{cases} 参数:THRESH_TOZERO−−−−dst(x,y)={0,src(x,y),src(x,y)>threshotherwise
1.2 使用opencv的mean()
opencv中计算单通道所有元素均值和标准差分别使用mean()和meanStdDev(),其中mean()的返回值是array矩阵,通过数组的形式访问,第0个元素就是均值大小;meanStdDev()的返回值是Mat类型的数据。
blockThreshold中
float meanV = mean(srcSec)[0] * _thresh;
均值为什么要乘 “_thresh” ????
这是测试的经验值,一般在0.7-0.8之间,而不是简单的1
2 opencv中的findContours()
findContours()函数是寻找图片中物体的轮廓信息。
void findContours( InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset=Point());
其中,image:输入图像,图像必须为8-bit单(CV_8UC1)通道图像,图像中的非零像素将被视为1,0像素保留其像素值,故加载图像后会自动转换为二值图像。如果第四个参数为cv::RETR_CCOMP或cv::RETR_FLOODFILL,输入图像可以是32-bit整型图像(CV_32SC1)
contours: 检测到的轮廓,每个轮廓都是以点向量的形式进行存储即使用point类型的vector表示
hierarchy: 可选的输出向量(std::vector),包含了图像的拓扑信息,作为轮廓数量的表示hierarchy包含了很多元素,每个轮廓contours[i]对应hierarchy中hierarchy[i][0]~hierarchy[i][3],分别表示后一个轮廓,前一个轮廓,父轮廓,内嵌轮廓的索引,如果没有对应项,则相应的hierarchy[i]设置为负数。
mode轮廓检索模式,可以通过cv::RetrievalModes()查看详细信息,如下 :
参数 | 含义 |
---|---|
RETR_EXTERNAL | 表示只检测最外层轮廓,对所有轮廓设置hierarchy [1]][2]=hierarchy[i][3]=-1 |
RETR_LIST | 提取所有轮廓,并放置在list中,检测的轮廓不建立等级关系 |
RETR_CCOMP | 提取所有轮廓,并将轮廓组织成双层结构(two-level hierarchy),顶层为连通域的外围边界,次层位内层边界 |
RETR_TREE | 提取所有轮廓并重新建立网状轮廓结构 |
**method:**轮廓近似方法可以通过cv::ContourApproximationModes()查看详细信息,如下所示:
参数 | 含义 |
---|---|
CHAIN_APPROX_NONE | 获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过1 |
CHAIN_APPROX_SIMPLE | 压缩水平方向,垂直方向,对角线方向的元素,值保留该方向的重点坐标,如果一个矩形轮廓只需4个点来保存轮廓信息 |
CHAIN_APPROX_TC89_L1和CHAIN_APPROX_TC89_KCOS | 使用Teh-Chinl链逼近算法中的一种 |
**offset:**轮廓点可选偏移量,有默认值Point(),对ROI图像中找出的轮廓。
轮廓提取完成后,可以通过drawContours()画出轮廓图
void drawContours(
InputOutputArray image, // 输入图像
InputArrayOfArrays contours, // 轮廓点集
int contourIdx, // 需要绘制的轮廓的指数 (-1 表示 "all")
const cv::Scalar& color, // 轮廓的颜色
int thickness = 1, // 轮廓线的宽度
int lineType = 8, // 轮廓线的邻域模式('4'邻域 或 '8'邻域)
InputArray hierarchy = noArray(), // 可选 (从 findContours得到)
int maxLevel = INT_MAX, // 轮廓中的最大下降
Point offset = Point() // (可选) 所有点的偏移
)
3 opencv中的RotatedRect类
RotatedRect有三个属性:
-
矩形的中心点;
-
矩形的尺寸;
-
角度。
这个和rect就对了一个角度,因为需要rotate,
vector<RotatedRect> rotRects; //存放最小外接矩形
4 opencv-------ROI
ROI------region of interest,感兴趣的区域。对于图像的处理,并不是对所有的部分都感兴趣,因此对于感兴趣的部分的提和处理,就称其为ROI。
5 代码分析
-
dmLocation()中中的轮廓尺寸,面积约束,比例约束都是为了保证提取的矩形轮廓是我们自己的二维码的轮廓,而不是其他东西的轮廓,即保证找到的是二维码。
-
对于rect的排序,以最小外接矩形的的中心点到图片中心点的位置距离排序,是为了获得最逼近摄像头正下方附近区域的轮廓信息。
6 代码举例
举个栗子:
void do_it()
{
string filePath = "D:/qt_work/Project_QR_ribbon_position/dmc/test00/pictures/singalCode.jpg";
float meanV;
Mat image = imread(filePath);
Mat grayImag, rectImag;
std::cout<<image.size()<<std::endl;
//resize picture
resize(image, image, Size(320, 240), 0, 0, INTER_NEAREST);
imshow("origin picture", image);
//convert image from BGR to GRAY
cvtColor(image, grayImag, CV_BGR2GRAY);
meanV = mean(image)[0];
//threshold image
threshold(grayImag, rectImag, meanV, 255, THRESH_BINARY_INV);
imshow("threshold", rectImag);
//find contour rectangle
vector<vector<Point2i>> contours;
vector<Vec4i> hierarchy;
findContours(rectImag, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, Point(0, 0));
drawContours(rectImag, contours, -1, Scalar::all(255));
imshow("contours", rectImag);
waitKey(0);
}
int main()
{
do_it();
return 0;
}
执行结果:
原始图片:
二极化后的图片:
全部轮廓提取:
外部轮廓提取: