基于OpenCV的条形码区域检测(完)

版权声明:本文为博主原创文章,欢迎转载,转载请贴上博客地址 http://blog.csdn.net/xdg_blog https://blog.csdn.net/xdg_blog/article/details/53794394

基于OpenCV的条形码区域检测(完)

 工作较忙,该系列随笔就要草草收尾,这篇将贴出完整代码,经过大略整理并添加关键注释,希望能够帮助理解。

 当时只是验证性质的实现,所以代码比较粗糙,请谅解!

  若有疑问,欢迎邮件至”[email protected]”沟通。

测试图片在该文件夹下:barcodes@Github


#include <iostream>
#include <vector>
#include <fstream>

#include <opencv2\opencv.hpp>

#include <ctime>

//#define FILE_SAVE

using namespace cv;
using namespace std;

struct BlockInfo {
    //区块序号
    int index;
    //区块位置
    Point leftTop;
    //区块宽度
    int width;
    //区块高低
    int height;
    //区块角度信息直方图
    long angleHist[8];
    //得分(有效像素占总像素数的比例)
    float score;
    //有效点数量
    long count;
    //区块方向
    int direction;
    //所有点角度
    vector<float > angleSet;
    //
    bool exist;

    BlockInfo() {
        index = 0;
        leftTop.x = 0;
        leftTop.y = 0;
        width = 0;
        height = 0;
        memset(angleHist, 0, sizeof(long) * 8);
        score = 0.0f;
        count = 0;
        direction = -1;
        exist = false;
    }
};

vector<BlockInfo > g_blockSet;

//Mat gradient;
double gradient_minVal, gradient_maxVal;
long hist[8];

struct CmpByValue {
    bool operator()(const pair<int, long>& lhs, const pair<int, long>& rhs) {
        return lhs.second > rhs.second;
    }
};

//#define WHOLE_IMG

vector<Mat>  division(Mat &image, int width, int height) {
    int m, n;
    float fm, fn;
    fm = image.rows*1.0 / height*1.0;
    fn = image.cols*1.0 / width*1.0;

    m = (int)fm;
    n = (int)fn;

    if (fm - (float)m > 0) {
        m++;
    }
    if (fn - (float)n > 0) {
        n++;
    }

    int index = 0;

    vector<Mat> imgOut;
    for (int j = 0; j < m; j++) {
        if (j < m - 1) {
            int i;
            for (i = 0; i < n; i++) {
                if (i < n - 1) {
                    BlockInfo temBlock;
                    temBlock.index = index++;
                    Mat temImage(height, width, CV_8U, Scalar(0, 0, 0));
                    Mat imageROI = image(Rect(i*width, j*height, temImage.cols, temImage.rows));
                    addWeighted(temImage, 1.0, imageROI, 1.0, 0., temImage);

                    temBlock.width = width;
                    temBlock.height = height;
                    temBlock.leftTop = Point(i*width, j*height);

                    g_blockSet.push_back(temBlock);
                    imgOut.push_back(temImage);
                }
#ifdef WHOLE_IMG
                else {
                    Mat temImage(height, width, CV_8U, Scalar(0, 0, 0));
                    Mat imageROI = image(Rect(i*width, j*height, image.cols - i*width - 1, temImage.rows));
                    for (size_t y = 0; y < imageROI.rows; ++y) {
                        unsigned char* ps = imageROI.ptr<unsigned char>(y);
                        unsigned char* pp = temImage.ptr<unsigned char>(y);
                        for (size_t x = 0; x < imageROI.cols; ++x) {
                            pp[x] = ps[x];
                        }
                    }
                    imgOut.push_back(temImage);
                }
#endif
            }
        }
#ifdef WHOLE_IMG
        else {
            int i;
            for (i = 0; i < n; i++) {
                if (i < n - 1) {
                    Mat temImage(height, width, CV_8U, Scalar(0, 0, 0));
                    Mat imageROI = image(Rect(i*width, j*height, temImage.cols, image.rows - j*height - 1));
                    for (size_t y = 0; y < imageROI.rows; ++y) {
                        unsigned char* ps = imageROI.ptr<unsigned char>(y);
                        unsigned char* pp = temImage.ptr<unsigned char>(y);
                        for (size_t x = 0; x < imageROI.cols; ++x) {
                            pp[x] = ps[x];
                        }
                    }
                    imgOut.push_back(temImage);
                }
                else {
                    Mat temImage(height, width, CV_8U, Scalar(0, 0, 0));
                    Mat imageROI = image(Rect(i*width, j*height, image.cols - i*width - 1, image.rows - j*height - 1));
                    for (size_t y = 0; y < imageROI.rows; ++y) {
                        unsigned char* ps = imageROI.ptr<unsigned char>(y);
                        unsigned char* pp = temImage.ptr<unsigned char>(y);
                        for (size_t x = 0; x < imageROI.cols; ++x) {
                            pp[x] = ps[x];
                        }
                    }
                    imgOut.push_back(temImage);
                }
            }
        }
#endif
    }
    return imgOut;
}


///保存分割后的区块图像
void savedivisionfiles(vector<Mat> imgDiv) {
    int index = 0;
    for each(Mat m in imgDiv) {
        string prefilename = "Division - ";
        char str[10];
        itoa(index, str, 10);
        string indexstr(str);
        string filename = "..//results//cvtest//" + prefilename + indexstr + ".bmp";
        imwrite(filename, m);
        ++index;
    }
}

///绘制直方图
Mat drawHistgram() {
    Mat histgram(300, 400, CV_8U);
    histgram = Mat::zeros(histgram.size(), CV_8U);
    long maxVal = 0;
    for (int i = 0; i < 8; i++) {
        if (hist[i] > maxVal)
            maxVal = hist[i];
    }

    if (maxVal != 0)
        for (int i = 0; i < 8; i++) {
            long val = hist[i];
            long intensity = cvRound((val * 300 / maxVal)*0.9);
            rectangle(histgram, Point(i * 50 + 5, 300 - intensity),
                Point((i * 50) + 50 - 5, 300),
                CV_RGB(255, 255, 255), 3);
        }

    return histgram;
}

///将各个区块计算Sobel,并得到每个像素的方向信息,进行统计存储
void sobelAndCalc(Mat &img) {
    static int idx = 0;

    ///不纳入统计的梯度强度阈值比例
    const float GRADIANT_THRESHOLD = 0.4f;

#ifdef FILE_SAVE
    char index[10];
    itoa(i, index, 10);
    string indexstr(index);
#endif

    ///纳入统计的像素值的数量
    long pixelCnt = 0;

    Mat gx(img.rows, img.cols, CV_32F);
    Mat gy(img.rows, img.cols, CV_32F);
    Sobel(img, gx, CV_32F, 1, 0, -1);
    Sobel(img, gy, CV_32F, 0, 1, -1);

    ///初始化角度直方图
    memset(hist, 0, sizeof(long) * 8);

    Mat magnitude(gx.rows, gx.cols, CV_32F);
    Mat angleMat(gx.rows, gx.cols, CV_32F);
    cartToPolar(gx, gy, magnitude, angleMat);

    ///只统计梯度强度大于最大梯度强度40%的像素点,将低于阈值的值置为0
    threshold(magnitude, magnitude, gradient_maxVal*GRADIANT_THRESHOLD, gradient_maxVal, THRESH_TOZERO);

#ifdef FILE_SAVE
    string filename0 = "..//results//cvtest//Magnitude - " + indexstr + ".bmp";
    imwrite(filename0, magnitude);
#endif

    vector<float > angleSet;

    ///遍历当前区块所有像素点
    for (size_t y = 0; y < gx.rows; ++y) {
        float* pa = angleMat.ptr<float>(y);
        float* pm = magnitude.ptr<float>(y);
        for (size_t x = 0; x < gx.cols*gx.channels(); ++x) {


            float angle = pa[x];
            ///将弧度限制在0~PI的范围内
            angle = angle > CV_PI ? angle - CV_PI : angle;

            ///当前像素点的梯度强度
            float curGradient = pm[x];

            ///如果之前未被置为0
            if (curGradient > 0) {
                ++pixelCnt;
                ///将弧度值转换为角度
                float angle_new = 180.0 * angle / CV_PI;
                angleSet.push_back(angle_new);

                if (angle >= 0 && angle < 0.125*CV_PI) {
                    hist[0]++;
                }
                else if (angle >= 0.125*CV_PI && angle < 0.25*CV_PI) {
                    hist[1]++;
                }
                else if (angle >= 0.25*CV_PI && angle < 0.375*CV_PI) {
                    hist[2]++;
                }
                else if (angle >= 0.375*CV_PI && angle < 0.5*CV_PI) {
                    hist[3]++;
                }
                else if (angle >= 0.5*CV_PI && angle < 0.625*CV_PI) {
                    hist[4]++;
                }
                else if (angle >= 0.625*CV_PI && angle < 0.75*CV_PI) {
                    hist[5]++;
                }
                else if (angle >= 0.75*CV_PI && angle < 0.875*CV_PI) {
                    hist[6]++;
                }
                else if (angle >= 0.875*CV_PI && angle < CV_PI) {
                    hist[7]++;
                }

            }
        }
    }


#ifdef FILE_SAVE
    string filename1 = "..//results//cvtest//Histgram - " + indexstr + ".bmp";
    imwrite(filename1, drawHistgram());
#endif

    ///存储此区块信息
    g_blockSet[idx].count = pixelCnt;
    g_blockSet[idx].angleSet = angleSet;
    for (int j = 0; j < 8; ++j) {
        g_blockSet[idx].angleHist[j] = hist[j];
    }
    idx++;

}

///获得直方图中值最高的项,并返回其与相邻项的和
long getMaxWithAdj(long* hist, int &index) {
    long max = 0;
    int maxindex = 0;
    for (int i = 0; i < 8; ++i) {
        if (hist[i] > max) {
            max = hist[i];
            maxindex = i;
        }
    }
    index = maxindex;
    return hist[(maxindex - 1) % 8] + hist[maxindex] + hist[(maxindex + 1) % 8];

    //long *newHist = new long[8];

    //for (int i = 0; i < 8; ++i) {
    //  newHist[i] = hist[(i - 1) % 8] + hist[i] + hist[(i + 1) % 8];
    //}

    //long max = 0;
    //int maxindex = 0;
    //for (int i = 0; i < 8; ++i) {
    //  if (newHist[i] > max) {
    //      max = newHist[i];
    //      maxindex = i;
    //  }
    //}

    //index = maxindex;

    //long val = newHist[maxindex];

    //if (newHist) {
    //  delete newHist;
    //}
    //newHist = NULL;

    //return val;
}

///计算所有区块的分数
void blockSetTravel() {
    ///遍历所有区块
    for (vector<BlockInfo >::iterator pb = g_blockSet.begin(); pb < g_blockSet.end(); ++pb) {
        int maxIndex = 0;
        ///获得最高的项的和
        long max = getMaxWithAdj(pb->angleHist, maxIndex);
        pb->direction = maxIndex;
        if (pb->count > pb->height*pb->width / 20.0) {
            if (pb->count > 0)
                ///该区块的得分
                pb->score = max*1.0 / pb->count*1.0;
            else
                pb->score = 0;
        }
        else {
            pb->score = 0;
        }
    }
}

///计算所有区块的总体方向,即整个条形码的大概方向范围在PI的哪一个1/8等分中
int getDirection() {
    int direction[8];
    memset(direction, 0, sizeof(int) * 8);
    ///认为非干扰区块的阈值
    const float SCORE_THRESHOLD = 0.65;
    for each(auto var in g_blockSet) {
        if (var.score >= SCORE_THRESHOLD) {
            direction[var.direction]++;
        }
    }

    int max = 0;
    int maxIndex;
    for (int i = 0; i < 8; ++i) {
        if (direction[i] > max) {
            max = direction[i];
            maxIndex = i;
        }
    }

    return maxIndex;
}

/// 得到该图片中最有可能的条码角度(可以认为是getDirection函数的求精版本)
int getAngle() {
    map<int, long> set;
    for each(auto block in g_blockSet) {
        if (block.exist) {
            for each(auto var in block.angleSet) {
                /// 当图片分辨率较大时,1°将造成条码左右区域多个像素的偏差,故此处以0.5°为步进统计
                if (var - (int)var > 0.5)
                    var = (int)var*1.0 + 0.5;
                else
                    var = (int)var;
                set[var]++;
            }
        }
    }

    /// 将角度信息按照出现的次数进行排序
    vector<pair<int, long > > vec(set.begin(), set.end());
    sort(vec.begin(), vec.end(), CmpByValue());

    /// 返回出现次数最多的那个角度
    return vec.begin()->first;
}

///将指定方向的区块根据梯度强度强弱或深或浅绘制
Mat drawBlockSet(Size size, int direction) {
    Mat result(size, CV_8U, Scalar(0, 0, 0));
    for (vector<BlockInfo >::iterator pvar = g_blockSet.begin(); pvar < g_blockSet.end(); ++pvar) {
        if (pvar->score >= 0.65 && pvar->direction == direction) {
            pvar->exist = true;
            rectangle(result, Rect(pvar->leftTop.x, pvar->leftTop.y, pvar->width, pvar->height), Scalar(255 * pvar->score), -1);
        }
        else {
            pvar->exist = false;
        }
    }
    return result;
}

/// TEST:根据FFT获得角度信息并旋转(可以粗略实现矫正,但是其精度较低,并且要求图片中条码所占比例较大,所以效果并不明显)
void FFT2Rotate(Mat &img) {
    Mat after_guussian;
    GaussianBlur(img, after_guussian, Size(5, 5), 0);

    Mat imageSobelX, imageSobelY, imageSobelOut;
    //水平和垂直方向灰度图像的梯度和,使用Sobel算子    
    Mat imageX16S, imageY16S;
    Sobel(after_guussian, imageX16S, CV_16S, 1, 0, 3, 1, 0, 4);
    Sobel(after_guussian, imageY16S, CV_16S, 0, 1, 3, 1, 0, 4);
    convertScaleAbs(imageX16S, imageSobelX, 1, 0);
    convertScaleAbs(imageY16S, imageSobelY, 1, 0);
    imageSobelOut = imageSobelX + imageSobelY;
    //imshow("XY方向梯度和", imageSobelOut);

    Mat srcImg = imageSobelOut;
    //宽高扩充,非必须,特定的宽高可以提高傅里叶运算效率  
    Mat padded;
    int opWidth = getOptimalDFTSize(srcImg.rows);
    int opHeight = getOptimalDFTSize(srcImg.cols);
    copyMakeBorder(srcImg, padded, 0, opWidth - srcImg.rows, 0, opHeight - srcImg.cols, BORDER_CONSTANT, Scalar::all(0));
    Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
    Mat comImg;

    //通道融合,融合成一个2通道的图像  
    merge(planes, 2, comImg);
    dft(comImg, comImg);
    split(comImg, planes);
    //magnitude(planes[0], planes[1], planes[0]);
    Mat no_use;
    cartToPolar(planes[0], planes[1], planes[0], no_use);
    Mat magMat = planes[0];
    magMat += Scalar::all(1);
    log(magMat, magMat);     //对数变换,方便显示 
    magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));

    int cx = magMat.cols / 2;
    int cy = magMat.rows / 2;
    Mat q0(magMat, Rect(0, 0, cx, cy));
    Mat q1(magMat, Rect(0, cy, cx, cy));
    Mat q2(magMat, Rect(cx, cy, cx, cy));
    Mat q3(magMat, Rect(cx, 0, cx, cy));
    Mat tmp;
    q0.copyTo(tmp);
    q2.copyTo(q0);
    tmp.copyTo(q2);
    q1.copyTo(tmp);
    q3.copyTo(q1);
    tmp.copyTo(q3);
    normalize(magMat, magMat, 0, 1, CV_MINMAX);
    Mat magImg(magMat.size(), CV_8UC1);
    magMat.convertTo(magImg, CV_8UC1, 255, 0);
    //namedWindow("傅立叶频谱", 0);
    //cvResizeWindow("傅立叶频谱", 800, 600);
    //imshow("傅立叶频谱", magImg);

    //HoughLines查找傅立叶频谱的直线,该直线跟原图的一维码方向相互垂直
    threshold(magImg, magImg, 180, 255, CV_THRESH_BINARY);
    //namedWindow("二值化", 0);
    //cvResizeWindow("二值化", 800, 600);
    //imshow("二值化", magImg);

    //Mat element0 = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
    //erode(magImg, magImg, element0, Point(-1, -1), 1);
    //imshow("Erode", magImg);

    vector<Vec2f> lines;
    float pi180 = (float)CV_PI / 360;
    Mat linImg(magImg.size(), CV_8UC3);
    float theta_global = 0;
    HoughLines(magImg, lines, 1, pi180, 180, 0, 0);
    int numLines = lines.size();

    //for (int l = 0; l < numLines; l++) {
    //  float rho = lines[l][0];
    //  float theta = lines[l][1];
    //  float aa = (theta / CV_PI) * 180;
    //  Point pt1, pt2;
    //  double a = cos(theta), b = sin(theta);
    //  double x0 = a*rho, y0 = b*rho;
    //  pt1.x = cvRound(x0 + 5000 * (-b));
    //  pt1.y = cvRound(y0 + 5000 * (a));
    //  pt2.x = cvRound(x0 - 5000 * (-b));
    //  pt2.y = cvRound(y0 - 5000 * (a));
    //  line(linImg, pt1, pt2, Scalar(255, 0, 0), 3, 8, 0);
    //}

    for (int l = 0; l < 1; l++) {
        float rho = lines[l][0];
        float theta = lines[l][1];
        float aa = (theta / CV_PI) * 180;
        Point pt1, pt2;
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;
        pt1.x = cvRound(x0 + 5000 * (-b));
        pt1.y = cvRound(y0 + 5000 * (a));
        pt2.x = cvRound(x0 - 5000 * (-b));
        pt2.y = cvRound(y0 - 5000 * (a));
        line(linImg, pt1, pt2, Scalar(255, 0, 0), 3, 8, 0);
    }

    theta_global = lines[0][1];


    //vector<Vec4i> lines2;
    //HoughLinesP(magImg, lines2, 1, CV_PI / 180, 10, 3, 15);
    //for (size_t i = 0; i < lines2.size(); i++) {
    //  line(linImg, Point(lines2[i][0], lines2[i][1]),
    //      Point(lines2[i][2], lines2[i][3]), Scalar(0, 0, 255), 3, 8);
    //  //cout << "X1: " << lines[i][0] << " \tY1: " << lines[i][1] << " ----> " << " X2: " << lines[i][2] << " \tY2:" << lines[i][3] << endl;
    //}

    //if (lines2.size() >= 2) {
    //  theta_avg = atan((lines2[3][3] - lines2[3][1])*1.0 / (lines2[3][2] - lines2[3][0])*1.0);
    //}

    //namedWindow("Hough直线", 0);
    //cvResizeWindow("Hough直线", 800, 600);
    //imshow("Hough直线", linImg);

    //校正角度计算
    float angelD = 180 * theta_global / CV_PI - 90;
    Point center(img.cols / 2, img.rows / 2);
    Mat rotMat = getRotationMatrix2D(center, angelD, 1.0);
    Mat imageSource = Mat::ones(img.size(), CV_8UC3);
    warpAffine(img, imageSource, rotMat, img.size(), 1, 0, Scalar(255, 255, 255));//仿射变换校正图像  
                                                                                  //namedWindow("角度校正", 0);
                                                                                  //cvResizeWindow("角度校正", 800, 600);
                                                                                  //imshow("角度校正", imageSource);
}

/// TEST:根据hough变换得到角度信息并变换(较为失败,一般通用性较差)
void Hough2Rotate(Mat &img) {
    Mat after_canny;
    Canny(img, after_canny, 200, 180, 3);
    imshow("Canny", after_canny);

    Mat img_cpy;
    after_canny.copyTo(img_cpy);

    float theta_avg = 0;
    vector<Vec2f> lines;
    HoughLines(after_canny, lines, 1, CV_PI / 360, 200);
    size_t size = lines.size();
    size = size > 5 ? 5 : size;
    int cnt = 0;
    for (size_t i = 0; i < size; i++) {
        float rho = lines[i][0];
        float theta = lines[i][1];
        if (theta < CV_PI / 2) {
            theta_avg += theta;
            ++cnt;
        }
        Point pt1, pt2;
        double a = cos(theta);
        double b = sin(theta);
        double x0 = rho*a;
        double y0 = rho*b;
        pt1.x = cvRound(x0 + 5000 * (-b));
        pt1.y = cvRound(y0 + 5000 * a);
        pt2.x = cvRound(x0 - 5000 * (-b));
        pt2.y = cvRound(y0 - 5000 * a);
        line(img_cpy, pt1, pt2, Scalar(255, 255, 255), 1, CV_AA);
    }
    imshow("HoughLine", img_cpy);

    theta_avg /= cnt;

    float angelD = 0;
    if (theta_avg - 0 > 1e-3) {
        angelD = 180 * theta_avg / CV_PI - 90;
    }
    else {
        angelD = 0;
    }
    Point center(img.cols / 2, img.rows / 2);
    Mat rotMat = getRotationMatrix2D(center, angelD, 1.0);
    Mat imageSource = Mat::ones(img.size(), CV_8UC3);
    warpAffine(img, imageSource, rotMat, img.size(), 1, 0, Scalar(255, 255, 255));//仿射变换校正图像  
    imshow("角度校正", imageSource);
}

/// INUSE:通过计算得到的角度进行矫正
void Angle2Rotate(Mat &img, float angle) {
    Point center(img.cols / 2, img.rows / 2);
    Mat rotMat = getRotationMatrix2D(center, angle, 1.0);
    Mat imageSource = Mat::ones(img.size(), CV_8UC3);
    warpAffine(img, imageSource, rotMat, img.size(), 1, 0, Scalar(255, 255, 255));//仿射变换校正图像  
}

int main(int argc, char *argv[]) {
    /// 计时
    clock_t begin, end;

    /// 区块尺寸(对结果影响较大,可以进行多次尝试)
    int block_size = 70;

    begin = clock();
    const string IMG_PATH = "..//imgs//barcodes/3_rotate.bmp";

    Mat src = imread(IMG_PATH, CV_LOAD_IMAGE_COLOR);
    if (!src.data) {
        cout << "Read image error" << endl;
        return -1;
    }

    string indexstr = "05";

    ///将图片格式变为灰度图像
    cvtColor(src, src, CV_RGB2GRAY);

    ///make a copy
    Mat src_cpy;
    src.copyTo(src_cpy);

    ///对图像进行一个9×9的均值滤波,以实现将图像细节部分的纹理信息屏蔽
    blur(src_cpy, src_cpy, Size(9, 9));

    ///对整幅图像进行Sobel求两个方向上的梯度
    Mat gx(src_cpy.rows, src_cpy.cols, CV_32F);
    Mat gy(src_cpy.rows, src_cpy.cols, CV_32F);
    Sobel(src_cpy, gx, CV_32F, 1, 0, -1);
    Sobel(src_cpy, gy, CV_32F, 0, 1, -1);

    ///根据两个方向上的梯度信息,获得每个像素点的弧度和每个像素点的梯度强度
    Mat magnitude(gx.rows, gx.cols, CV_32F);
    Mat anglemat(gx.rows, gx.cols, CV_32F);
    cartToPolar(gx, gy, magnitude, anglemat);

    ///得到梯度强度的最大最小值
    minMaxIdx(magnitude, &gradient_minVal, &gradient_maxVal);

    ///将图像分割区块
    vector<Mat> imgDiv;
    imgDiv = division(src, block_size, block_size);

    ///遍历每个区块,求得所有区块的有效像素数量以及方向信息
    for (vector<Mat >::iterator p = imgDiv.begin(); p < imgDiv.end(); ++p) {
        sobelAndCalc(*p);
    }

    blockSetTravel();

    /// 计算大概的整体方向
    int dir = getDirection();
    /// 将得到的该方向的区块根据梯度强度绘制不同灰度级强度的色块
    Mat region = drawBlockSet(src.size(), dir);

    namedWindow("Region", 0);
    imshow("Region", region);

#ifdef FILE_SAVE
    savedivisionfiles(imgDiv);
#endif

    /// 寻找最大连通域
    vector<vector<Point>> con;
    findContours(region, con, RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);

    double max_area = 0;
    int max_index = 0;

    size_t i = 0;
    for (; i < con.size(); ++i) {
        if (contourArea(con.at(i)) > max_area) {
            max_area = contourArea(con.at(i));
            max_index = i;
        }
    }

    RotatedRect rect = minAreaRect(con.at(max_index));
    Point2f vertices[4];
    rect.points(vertices);

    //cvtColor(src, src, CV_GRAY2RGB);

    /// 将最大连通域绘制出来
    for (int i = 0; i < 4; i++)
        line(src, vertices[i], vertices[(i + 1) % 4], Scalar(0, 0, 255), 4);

    namedWindow("Lined", 0);
    imshow("Lined", src);

    /// 获得该区域的最小正的外接矩形
    int x_min = 50000, y_min = 50000;
    int x_max = 0, y_max = 0;
    for (int i = 0; i < 4; i++) {
        if (vertices[i].x < x_min) {
            x_min = vertices[i].x;
        }
        if (vertices[i].x > x_max) {
            x_max = vertices[i].x;
        }
        if (vertices[i].y < y_min) {
            y_min = vertices[i].y;
        }
        if (vertices[i].y > y_max) {
            y_max = vertices[i].y;
        }
    }

    int select_top_index = 0;
    for (int i = 0; i < 4; i++) {
        if ((int)vertices[i].y == y_min) {
            select_top_index = i;
        }
    }

    //int select_left_index = 0;
    //for (int i = 0; i < 4; i++) {
    //  if ((int)vertices[i].x == x_min) {
    //      select_left_index = i;
    //  }
    //}

    /// 计算正外接矩形的position&size
    int new_width = x_max - x_min + 1;
    int new_height = y_max - y_min + 1;
    int x_min_new = x_min < 0 ? 0 : x_min;
    int y_min_new = y_min < 0 ? 0 : y_min;

    if (x_min_new + new_width > src.cols) {
        new_width = src.cols - x_min_new;
    }

    if (y_min_new + new_height > src.rows) {
        new_height = src.rows - y_min_new;
    }

    Rect rect_clip(x_min_new, y_min_new, new_width, new_height);
    Mat region_clip = src(rect_clip);

    /// 将裁减之后的正的最小外接矩形显示
    namedWindow("Clip", 0);
    imshow("Clip", region_clip);

    /// 计算区域的带角度的外接矩形的顶部点在裁剪之后的图片中的坐标
    Point2d top_point(vertices[select_top_index]);
    top_point.x = (int)top_point.x - x_min;
    top_point.y = (int)top_point.y - y_min;

    /// 计算区域的带角度的外接矩形的长边与短边的长度
    double distance1 = sqrt((vertices[1].x - vertices[0].x)*(vertices[1].x - vertices[0].x) + (vertices[1].y - vertices[0].y)*(vertices[1].y - vertices[0].y));
    double distance2 = sqrt((vertices[2].x - vertices[1].x)*(vertices[2].x - vertices[1].x) + (vertices[2].y - vertices[1].y)*(vertices[2].y - vertices[1].y));

    /// 需要旋转的角度
    double angle = 0;
    /// 旋转之后的图像的尺寸
    double width_rotate = 0, height_rotate = 0;

    /// 判断哪一个是长边,然后计算需要旋转的角度和新图像的尺寸(因为最终结果需要以长边长度为宽度[即水平放置的条码才是需要的结果])
    if (distance1 > distance2) {
        angle = atan2(vertices[1].y - vertices[0].y, vertices[1].x - vertices[0].x);
        width_rotate = distance1;
        height_rotate = distance2;
    }
    else {
        angle = atan2(vertices[2].y - vertices[1].y, vertices[2].x - vertices[1].x);
        width_rotate = distance2;
        height_rotate = distance1;
    }

    /// 将图像转换为角度
    angle = angle * 180.0 / CV_PI;

    Mat rotateImg;
    region_clip.copyTo(rotateImg);

    /// 如果该条码原本就接近水平,则不需要旋转
    if ((int)angle != 180 && (int)angle != 0) {
        /// 以裁剪后(未旋转)的图像的中心点旋转
        Point2f center = Point2f(region_clip.cols / 2, region_clip.rows / 2);
        /// 不进行缩放
        double scale = 1.0;

        /// 计算得到旋转矩阵(该矩阵不包括平移的参数[即在使用该矩阵进行旋转的时候,不会进行平移])
        Mat rotateMat;
        rotateMat = getRotationMatrix2D(center, angle, scale);

        /// 将旋转矩阵加入平移的参数(以下步骤即将条码区域角点移动到新的图片的左上角)
        double* pr = rotateMat.ptr<double>(0);
        double M00 = pr[0];
        double M01 = pr[1];
        double M02 = pr[2];
        pr = rotateMat.ptr<double>(1);
        double M10 = pr[0];
        double M11 = pr[1];
        double M12 = pr[2];

        int torig_x = top_point.x;
        int torig_y = top_point.y;

        top_point.x = torig_x *1.0 * M00 + torig_y*1.0*M10 + M02;
        top_point.y = torig_x *1.0 * M01 + torig_y*1.0*M11 + M12;

        pr = rotateMat.ptr<double>(0);
        pr[2] += (width_rotate - top_point.x);
        pr = rotateMat.ptr<double>(1);
        pr[2] += (-region_clip.rows / 2) + (height_rotate / 2);

        warpAffine(region_clip, rotateImg, rotateMat, Size(width_rotate, height_rotate));

        Mat temp4Disp(rotateImg);
        namedWindow("Rotated0", 0);
        imshow("Rotated0", temp4Disp);
    }

    /// 以上旋转只是通过区域外界矩形的角度进行旋转
    /// 以下旋转是根据统计得到的角度进行二次旋转(提高精度)
    int result_angle = getAngle();
    result_angle = abs(result_angle - 180);
    float new_angle = -(result_angle - fabs(angle));

    if (fabs(new_angle) >= 0.5) {
        Angle2Rotate(rotateImg, new_angle);
    }

    namedWindow("Rotated", 0);
    imshow("Rotated", rotateImg);

    waitKey(0);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/xdg_blog/article/details/53794394