#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2\/opencv.hpp>
#include<math.h>
#include<Windows.h>
using namespace std;
using namespace cv;
vector<Point2f> check(vector<Point2f> p1, vector<Point2f> p2);
double distance_1(Point2f p1,Point2f p2);
bool isTimingPattern(list<uchar>& l1);
double var(vector<int> s);
void extending(vector<Point2f>& p, double& dis);
void putTextZH(Mat &dst, const char* str, Point org, Scalar color, int fontSize, //下面两个函数可以封装为一个h文件
const char *fn = "Arial", bool italic = false, bool underline = false);
void GetStringSize(HDC hDC, const char* str, int* w, int* h);
int main()
{
Mat src = imread("4.png");
Mat gray, binary, edge;
Mat threshold_gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gray, Size(5, 5), 0);
threshold(gray, threshold_gray, 100, 255, THRESH_BINARY);
Canny(gray, edge, 100, 300);
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(edge, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_SIMPLE); //只记录顶点
//嵌套contours,寻找嵌套数大于等于5的
vector<int> indices;
for (size_t i = 0; i < contours.size(); i++)
{
int num = 0;
int k = i; //很关键
while (hierarchy[k][2] != -1){
k = hierarchy[k][2];
num++;
}
if (num >= 5){
indices.push_back(i);
}
}
//画出粗定位区域
vector<RotatedRect> rects;
Mat img_dc = src.clone();
for (size_t i = 0; i < indices.size(); i++)
{
rects.push_back(minAreaRect(contours[indices[i]]));
drawContours(img_dc, contours,indices[i] , Scalar(0, 255,0), 2);
}
vector<vector<Point2f> > points;
for (size_t i = 0; i < rects.size(); i++)
{
Point2f p1[4];
rects[i].points(p1);
vector<Point2f> p2(begin(p1), end(p1));
points.push_back(p2);
}
//排除错误的定位
vector<vector<Point2f> > timing_pattern_src;
for (int i = 0; i < points.size(); i++)
{
vector<Point2f> pair_points;
for (int j = i + 1; j < points.size(); j++)
{
pair_points=check(points[i], points[j]);
timing_pattern_src.push_back(pair_points);
}
}
//矫正
vector<vector<Point2f> > timing_pattern(timing_pattern_src.begin(), timing_pattern_src.end());
for (int i = 0; i < timing_pattern.size(); i++)
{
Point2f a0 = Point2f(timing_pattern[i][0].x + (timing_pattern[i][2].x - timing_pattern[i][0].x) * 1 / 14, timing_pattern[i][0].y + (timing_pattern[i][2].y - timing_pattern[i][0].y) * 1 / 14);
Point2f a1 = Point2f(timing_pattern[i][1].x + (timing_pattern[i][3].x - timing_pattern[i][1].x) * 1 / 14, timing_pattern[i][1].y + (timing_pattern[i][3].y - timing_pattern[i][1].y) * 1 / 14);
Point2f a2 = Point2f(timing_pattern[i][2].x + (timing_pattern[i][0].x - timing_pattern[i][2].x) * 1 / 14, timing_pattern[i][2].y + (timing_pattern[i][0].y - timing_pattern[i][2].y) * 1 / 14);
Point2f a3 = Point2f(timing_pattern[i][3].x + (timing_pattern[i][1].x - timing_pattern[i][3].x) * 1 / 14, timing_pattern[i][3].y + (timing_pattern[i][1].y - timing_pattern[i][3].y) * 1 / 14);
timing_pattern[i][0] = a0;
timing_pattern[i][1] = a1;
timing_pattern[i][2] = a2;
timing_pattern[i][3] = a3;
}
//对timing pattern 计数
vector<vector<int> > correct_pair_index;
for (int i = 0; i < timing_pattern.size(); i++){
for (int j = 0; j < 4; j += 2){
LineIterator it(threshold_gray, timing_pattern[i][j], timing_pattern[i][j + 1]);
list<uchar> val;
for (int k = 0; k < it.count; k++){
val.push_back(*(uchar*)*it);
//cout << **it << endl;
it++;
}
if (isTimingPattern(val)){
vector<int> ddd;
ddd.push_back(i);
ddd.push_back(j);
line(img_dc, timing_pattern[i][j], timing_pattern[i][j + 1], Scalar(255,0,0), 1); //画出pattern
correct_pair_index.push_back(ddd);
}
}
}
//得到二维码的区域
vector<Point2f> rect_verticle;
for (int i = 0; i < correct_pair_index.size(); i++)
{
rect_verticle.push_back(timing_pattern_src[correct_pair_index[i][0]][correct_pair_index[i][1]]);
rect_verticle.push_back(timing_pattern_src[correct_pair_index[i][0]][correct_pair_index[i][1]+1]);
}
//判断两两之间点的距离,寻找二维码的中间标记
Point2f centerPoint;
vector<Point2f> QRCodePoints;
for (int i = 0; i < rect_verticle.size(); i++)
for (int j = i + 1; j < rect_verticle.size(); j++)
{
double s = distance_1(rect_verticle[i], rect_verticle[j]);
if (s < 10)
{
centerPoint = Point2f((rect_verticle[i].x + rect_verticle[j].x)*0.5, (rect_verticle[i].y + rect_verticle[j].y)*0.5);
}
}
for (int i = 0; i < rect_verticle.size(); i++)
{
double s = distance_1(rect_verticle[i],centerPoint );
if (s>10)
QRCodePoints.push_back(rect_verticle[i]);
}
double distance_pattern_1 = distance_1(centerPoint, QRCodePoints[0]);
double distance_pattern_2 = distance_1(centerPoint, QRCodePoints[1]);
double distance_pattern = (distance_pattern_1 + distance_pattern_2)*0.5;
double extend_distance = distance_pattern / 1.5;
extending(QRCodePoints, extend_distance);
Rect rect_end(QRCodePoints[0], QRCodePoints[1]);
rectangle(img_dc, rect_end, Scalar(0, 0, 255), 4);
//很重要的提醒,这里也许可以用RatateREct的构造函数,用两个点做输入,未验证。
/* cv::Point2f* vertices = new cv::Point2f[4];
rect_end.points(vertices);
for (int j = 0; j < 4; j++)
{
cv::line(img_dc, vertices[j], vertices[(j + 1) % 4], cv::Scalar(0, 0, 255),5);
}*/
putTextZH(img_dc, "二维码定位", Point(0, 20), Scalar(100, 100, 255), 30, "Arial");
putTextZH(img_dc, "缺陷:一幅图最多定位一个二维码", Point(0, 50), Scalar(100, 100, 255) ,30, "Arial");
putTextZH(img_dc, " 对于有旋转角度的二维码效果不好,待改进。。",Point(0,80), Scalar(100, 100, 255), 30, "Arial");
//imwrite("QRcodeLocal.jpg", img_dc);
imshow(" location", img_dc);
waitKey(0);
return 0;
}
vector<Point2f> check(vector<Point2f> p1, vector<Point2f> p2)
{
vector<Point2f> pair_points;
double min_distance1=99999, min_distance2=99999;
double s=0;
int index_i[2] = { 0, 0 }, index_j[2] = { 0, 0 };
for (int i = 0; i < p1.size(); i++)
for (int j = 0; j < p2.size(); j++)
{
s = distance_1(p1[i], p2[j]);
if (s < min_distance2){
if (s < min_distance1){
min_distance2 = min_distance1;
min_distance1 = s;
index_i[1] = index_i[0]; //min_distance1被移到了2,所以index也要移动啊!!!!
index_j[1] = index_j[0];
index_i[0] = i;
index_j[0] = j;
}
else{
min_distance2 = s;
index_i[1] = i;
index_j[1] = j;
}
}
}
pair_points.push_back(p1[index_i[0]]);
pair_points.push_back(p2[index_j[0]]);
pair_points.push_back(p1[index_i[1]]);
pair_points.push_back(p2[index_j[1]]);
return pair_points;
}
double distance_1(Point2f p1, Point2f p2)
{
double s = sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
return s;
}
bool isTimingPattern(list<uchar>& l1)
{
while (*l1.begin() != 0) //全是255,则这里会出错
l1.pop_front();
while (*(l1.rbegin()) != 0)
l1.pop_back();
vector<int> counts;
int count = 1;
auto it1 = l1.begin();
uchar l = *it1;
for (++it1; it1 != l1.end(); it1++){
if (*it1 == l){
count++;
}
else{
counts.push_back(count);
count = 1;
}
l = *it1;
}
counts.push_back(count);
if ((int)counts.size() < 7)
return false;
//计算方差,判断
double variance = var(counts);
cout << variance << endl;
if (variance<3)
return true;
return false;
}
double var(vector<int> s)
{
double sum(0.0);
double sqsum(0.0);
for (int i = 0; i < s.size(); i++){
int v = s[i];
sum += v;
sqsum += v*v;
}
double scale = 1.0 / s.size();
double mean = sum*scale;
double variance = sqsum*scale - mean*mean;
return variance;
}
void extending(vector<Point2f>& p, double& dis)
{
if (p[0].x < p[1].x)
{
p[0].x -= dis;
p[1].x += dis;
}
else{
p[0].x += dis;
p[1].x -= dis;
}
if (p[0].y < p[1].y)
{
p[0].y -= dis;
p[1].y += dis;
}
else{
p[0].y += dis;
p[1].y -= dis;
}
}
void putTextZH(Mat &dst, const char* str, Point org, Scalar color, int fontSize, const char* fn, bool italic, bool underline)
{
CV_Assert(dst.data != 0 && (dst.channels() == 1 || dst.channels() == 3));
int x, y, r, b;
if (org.x > dst.cols || org.y > dst.rows) return;
x = org.x < 0 ? -org.x : 0;
y = org.y < 0 ? -org.y : 0;
LOGFONTA lf;
lf.lfHeight = -fontSize;
lf.lfWidth = 0;
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = 5;
lf.lfItalic = italic; //斜体
lf.lfUnderline = underline; //下划线
lf.lfStrikeOut = 0;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfOutPrecision = 0;
lf.lfClipPrecision = 0;
lf.lfQuality = PROOF_QUALITY;
lf.lfPitchAndFamily = 0;
strcpy_s(lf.lfFaceName, fn);
HFONT hf = CreateFontIndirectA(&lf);
HDC hDC = CreateCompatibleDC(0);
HFONT hOldFont = (HFONT)SelectObject(hDC, hf);
int strBaseW = 0, strBaseH = 0;
int singleRow = 0;
char buf[1 << 12];
strcpy_s(buf, str);
char *bufT[1 << 12]; // 这个用于分隔字符串后剩余的字符,可能会超出。
//处理多行
{
int nnh = 0;
int cw, ch;
const char* ln = strtok_s(buf, "\n", bufT);
while (ln != 0)
{
GetStringSize(hDC, ln, &cw, &ch);
strBaseW = max(strBaseW, cw);
strBaseH = max(strBaseH, ch);
ln = strtok_s(0, "\n", bufT);
nnh++;
}
singleRow = strBaseH;
strBaseH *= nnh;
}
if (org.x + strBaseW < 0 || org.y + strBaseH < 0)
{
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hDC);
return;
}
r = org.x + strBaseW > dst.cols ? dst.cols - org.x - 1 : strBaseW - 1;
b = org.y + strBaseH > dst.rows ? dst.rows - org.y - 1 : strBaseH - 1;
org.x = org.x < 0 ? 0 : org.x;
org.y = org.y < 0 ? 0 : org.y;
BITMAPINFO bmp = { 0 };
BITMAPINFOHEADER& bih = bmp.bmiHeader;
int strDrawLineStep = strBaseW * 3 % 4 == 0 ? strBaseW * 3 : (strBaseW * 3 + 4 - ((strBaseW * 3) % 4));
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = strBaseW;
bih.biHeight = strBaseH;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = BI_RGB;
bih.biSizeImage = strBaseH * strDrawLineStep;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
void* pDibData = 0;
HBITMAP hBmp = CreateDIBSection(hDC, &bmp, DIB_RGB_COLORS, &pDibData, 0, 0);
CV_Assert(pDibData != 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp);
//color.val[2], color.val[1], color.val[0]
SetTextColor(hDC, RGB(255, 255, 255));
SetBkColor(hDC, 0);
//SetStretchBltMode(hDC, COLORONCOLOR);
strcpy_s(buf, str);
const char* ln = strtok_s(buf, "\n", bufT);
int outTextY = 0;
while (ln != 0)
{
TextOutA(hDC, 0, outTextY, ln, strlen(ln));
outTextY += singleRow;
ln = strtok_s(0, "\n", bufT);
}
uchar* dstData = (uchar*)dst.data;
int dstStep = dst.step / sizeof(dstData[0]);
unsigned char* pImg = (unsigned char*)dst.data + org.x * dst.channels() + org.y * dstStep;
unsigned char* pStr = (unsigned char*)pDibData + x * 3;
for (int tty = y; tty <= b; ++tty)
{
unsigned char* subImg = pImg + (tty - y) * dstStep;
unsigned char* subStr = pStr + (strBaseH - tty - 1) * strDrawLineStep;
for (int ttx = x; ttx <= r; ++ttx)
{
for (int n = 0; n < dst.channels(); ++n){
double vtxt = subStr[n] / 255.0;
int cvv = vtxt * color.val[n] + (1 - vtxt) * subImg[n];
subImg[n] = cvv > 255 ? 255 : (cvv < 0 ? 0 : cvv);
}
subStr += 3;
subImg += dst.channels();
}
}
SelectObject(hDC, hOldBmp);
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hBmp);
DeleteDC(hDC);
}
void GetStringSize(HDC hDC, const char* str, int* w, int* h)
{
SIZE size;
GetTextExtentPoint32A(hDC, str, strlen(str), &size);
if (w != 0) *w = size.cx;
if (h != 0) *h = size.cy;
}
效果如下: