Qt-5-and-OpenCV-4-Computer-Vision-Projects 学习笔记 - 光学字符识别

光学字符识别

void MainWindow::extractText()
{
	//在方法主体的开头,我们检查currentImage成员字段是否为空
	//如果为null,则在我们的应用中没有打开任何图像,因此我们在显示消息框后立即返回。
    if (currentImage == nullptr) {
        QMessageBox::information(this, "Information", "No opened image.");
        return;
    }
	//如果它不为null,那么我们将创建 Tesseract API 实例
	//Tesseract 要求我们必须将语言环境设置为C,
	//首先,我们使用LC_ALL类别和空值调用setlocale函数,以获取并保存当前的语言环境设置,
	//然后再次使用相同的类别和值C来设置 Tesseract 所需的语言环境
    char *old_ctype = strdup(setlocale(LC_ALL, NULL));
    setlocale(LC_ALL, "C");
    if (tesseractAPI == nullptr) {
        //在将区域设置设置为C之后,现在,我们可以创建 Tesseract API 实例
        //我们使用表达式new tesseract::TessBaseAPI()创建它。
        //新创建的 API 实例必须在使用前进行初始化
        tesseractAPI = new tesseract::TessBaseAPI();
        // Initialize tesseract-ocr with English, with specifying tessdata path
        if (tesseractAPI->Init(TESSDATA_PREFIX, "chi_sim")) {
            QMessageBox::information(this, "Error", "Could not initialize tesseract.");
            return;
        }
    }
	//准备好 Tesseract API 实例后,我们将获得当前打开的图像,并将其转换为QImage::Format_RGB888格式的图像,就像在先前项目中所做的那样。
    QPixmap pixmap = currentImage->pixmap();
    QImage image = pixmap.toImage();
    image = image.convertToFormat(QImage::Format_RGB888);
	//获得具有RGB888格式的图像后,可以通过调用其SetImage方法将其提供给 Tesseract API 实例。 
	
    //它需要的所有信息都可以从QImage实例中检索到,该实例具有 3 个通道,深度为 8 位。 因此,它为每个像素使用3字节:
    tesseractAPI->SetImage(image.bits(), image.width(), image.height(),
        3, image.bytesPerLine());

    if (detectAreaCheckBox->checkState() == Qt::Checked) {
        std::vector<cv::Rect> areas;
        cv::Mat newImage = detectTextAreas(image, areas);
        showImage(newImage);
        editor->setPlainText("");
        for(cv::Rect &rect : areas) {
            tesseractAPI->SetRectangle(rect.x, rect.y, rect.width, rect.height);
            //Tesseract API 实例获取图像后,我们可以调用其GetUTF8Text()方法来获取其从图像中识别的文本。 在这里值得注意的是,调用者有责任释放此方法的结果数据缓冲区。
            char *outText = tesseractAPI->GetUTF8Text();
            editor->setPlainText(editor->toPlainText() + outText);
            delete [] outText;
        }
    } else {
        char *outText = tesseractAPI->GetUTF8Text();
        editor->setPlainText(outText);
        delete [] outText;
    }
	//OCR 工作完成后,我们将使用保存的LC_ALL值恢复语言环境设置。
    setlocale(LC_ALL, old_ctype);
    free(old_ctype);
}

使用 OpenCV 检测文本区域

//我们将使用带有 OpenCV 的 EAST 文本检测器来检测图像中是否存在文本。 EAST有效且准确的场景文本检测器的缩写,它是基于神经网络的算法,但是其神经网络模型的架构和训练过程不在本章范围之内。 在本节中,我们将重点介绍如何使用 OpenCV 的 EAST 文本检测器的预训练模型。

// 按照预期,此方法将QImage对象作为输入图像作为其第一个参数
//它的第二个参数是对cv::Rect向量的引用,该向量用于保存检测到的文本区域
//该方法的返回值为cv::Mat,它表示在其上绘制了检测到的矩形的输入图像
cv::Mat MainWindow::detectTextAreas(QImage &image, std::vector<cv::Rect> &areas)
{
    float confThreshold = 0.5;
    float nmsThreshold = 0.4;
    int inputWidth = 320;
    int inputHeight = 320;
    std::string model = "/home/xz/study/qt_collect_pro/qt_5_opencv/data/frozen_east_text_detection.pb";
    // Load DNN network.
    if (net.empty()) {
        net = cv::dnn::readNet(model);
    }
	//将加载 DNN 模型。 现在,让我们将输入图像发送到模型以执行文本检测:
 	//我们定义了一个cv::Mat向量,以保存模型的输出层
    std::vector<cv::Mat> outs;
    //然后,将需要从 DNN 模型中提取的两层的名称放入字符串向量,即layerNames变量。 这两个层包含我们想要的信息
    std::vector<std::string> layerNames(2);
  	//第一层feature_fusion/Conv_7/Sigmoid是 Sigmoid 激活的输出层。 该层中的数据包含给定区域是否包含文本的概率。
    layerNames[0] = "feature_fusion/Conv_7/Sigmoid";
    //第二层feature_fusion/concat_3是特征映射的输出层。 该层中的数据包含图像的几何形状。 通过稍后在此层中解码数据,我们将获得许多边界框。
    layerNames[1] = "feature_fusion/concat_3";
	//之后,我们将输入图像从QImage转换为cv::Mat,然后将矩阵转换为另一个矩阵,该矩阵是一个 4 维 BLOB,可以用作 DNN 模型的输入,换句话说, 输入层。 后一种转换是通过在 OpenCV 库的cv::dnn名称空间中调用blobFromImage函数来实现的。 在此转换中执行许多操作,例如从中心调整大小和裁剪图像,减去平均值,通过比例因子缩放值以及交换 R 和 B 通道。 在对blobFromImage函数的调用中,我们有很多参数。 现在让我们一一解释:
    cv::Mat frame = cv::Mat(
        image.height(),
        image.width(),
        CV_8UC3,
        image.bits(),
        image.bytesPerLine()).clone();
    cv::Mat blob;
	//第一个参数是输入图像
    //第二个参数是输出图像
    //第三个参数是每个像素值的比例因子。 我们使用 1.0,因为我们不需要在此处缩放像素
    //第四个参数是输出图像的空间大小。 我们说过,此尺寸的宽度和高度必须是 32 的倍数,此处我们将320 x 320与我们定义的变量一起使用。
    //第五个参数是应该从每个图像中减去的平均值,因为在训练模型时已使用了该平均值。 在此,使用的平均值为(123.68, 116.78, 103.94)。
    //下一个参数是我们是否要交换 R 和 B 通道。 这是必需的,因为 OpenCV 使用 BGR 格式,而 TensorFlow 使用 RGB 格式
    //最后一个参数是我们是否要裁剪图像并进行中心裁剪。 在这种情况下,我们指定false。
    cv::dnn::blobFromImage(
        frame, blob,
        1.0, cv::Size(inputWidth, inputHeight),
        cv::Scalar(123.68, 116.78, 103.94), true, false
    );
    //在该调用返回之后,我们得到了可用作 DNN 模型输入的 Blob。 然后,将其传递给神经网络,并通过调用模型的setInput方法和forward方法执行一轮转发以获取输出层。 转发完成后,我们想要的两个输出层将存储在我们定义的outs向量中。 
    net.setInput(blob);
    net.forward(outs, layerNames);
	//下一步是处理这些输出层以获取文本区域:
    //outs向量的第一个元素是得分
    cv::Mat scores = outs[0];
    //而第二个元素是几何形状
    cv::Mat geometry = outs[1];

    //然后,我们调用MainWindow类的另一种方法decode,以解码文本框的位置及其方向。
    std::vector<cv::RotatedRect> boxes;
    std::vector<float> confidences;
    //通过此解码过程,我们将候选文本区域作为cv::RotatedRect并将其存储在boxes变量中。 这些框的相应置信度存储在confidences变量中。
    decode(scores, geometry, confThreshold, boxes, confidences);
	//由于我们可能会为文本框找到许多候选对象,因此我们需要过滤掉外观最好的文本框。 这是使用非最大抑制来完成的,即对NMSBoxes方法的调用。 在此调用中,我们给出解码后的框,置信度以及置信度和非最大值抑制的阈值,未消除的框的索引将存储在最后一个参数indices中。
    std::vector<int> indices;
    cv::dnn::NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);

    // Render detections.
 	//现在,我们将所有文本区域作为cv::RotatedRect的实例,并且这些区域用于调整大小的图像,因此我们应该将它们映射到原始输入图像上:
    cv::Point2f ratio((float)frame.cols / inputWidth, (float)frame.rows / inputHeight);
    cv::Scalar green = cv::Scalar(0, 255, 0);
	//为了将文本区域映射到原始图像,我们应该知道在将图像发送到 DNN 模型之前如何调整图像大小,然后逆转文本区域的大小调整过程。
        // 因此,我们根据宽度和高度方面计算尺寸调整率,然后将它们保存到cv::Point2f ratio中。
        //然后,我们迭代保留的索引,并获得每个索引指示的每个cv::RotatedRect对象。
    for (size_t i = 0; i < indices.size(); ++i) {
        
        cv::RotatedRect& box = boxes[indices[i]];
        cv::Rect area = box.boundingRect();
        //为了降低代码的复杂性,我们无需将cv::RotatedRect及其内容旋转为规则矩形,而是简单地获取其边界矩形。 
        area.x *= ratio.x;
        area.width *= ratio.x;
        area.y *= ratio.y;
        area.height *= ratio.y;
        //然后,我们对矩形进行反向调整大小,然后将其推入areas向量。 
        areas.push_back(area);
        cv::rectangle(frame, area, green, 1);
        QString index = QString("%1").arg(i);
        cv::putText(
            frame, index.toStdString(), cv::Point2f(area.x, area.y - 2),
            cv::FONT_HERSHEY_SIMPLEX, 0.5, green, 1
        );
    }
    return frame;
}
//decode方法用于从输出层提取置信度和框信息。 可以在这个页面中找到其实现。 要理解它,您应该了解 DNN 模型中的数据结构,尤其是输出层中的数据结构。 但是,这超出了本书的范围。 如果您对此感兴趣,可以在这个页面上参阅与 EAST 有关的论文,并在这个页面上使用 Tensorflow 来实现它的一种实现。
void MainWindow::decode(const cv::Mat& scores, const cv::Mat& geometry, float scoreThresh,
    std::vector<cv::RotatedRect>& detections, std::vector<float>& confidences)
{
    CV_Assert(scores.dims == 4); CV_Assert(geometry.dims == 4);
    CV_Assert(scores.size[0] == 1); CV_Assert(scores.size[1] == 1);
    CV_Assert(geometry.size[0] == 1);  CV_Assert(geometry.size[1] == 5);
    CV_Assert(scores.size[2] == geometry.size[2]);
    CV_Assert(scores.size[3] == geometry.size[3]);

    detections.clear();
    const int height = scores.size[2];
    const int width = scores.size[3];
    for (int y = 0; y < height; ++y) {
        const float* scoresData = scores.ptr<float>(0, 0, y);
        const float* x0_data = geometry.ptr<float>(0, 0, y);
        const float* x1_data = geometry.ptr<float>(0, 1, y);
        const float* x2_data = geometry.ptr<float>(0, 2, y);
        const float* x3_data = geometry.ptr<float>(0, 3, y);
        const float* anglesData = geometry.ptr<float>(0, 4, y);
        for (int x = 0; x < width; ++x) {
            float score = scoresData[x];
            if (score < scoreThresh)
                continue;

            // Decode a prediction.
            // Multiple by 4 because feature maps are 4 time less than input image.
            float offsetX = x * 4.0f, offsetY = y * 4.0f;
            float angle = anglesData[x];
            float cosA = std::cos(angle);
            float sinA = std::sin(angle);
            float h = x0_data[x] + x2_data[x];
            float w = x1_data[x] + x3_data[x];

            cv::Point2f offset(offsetX + cosA * x1_data[x] + sinA * x2_data[x],
                offsetY - sinA * x1_data[x] + cosA * x2_data[x]);
            cv::Point2f p1 = cv::Point2f(-sinA * h, -cosA * h) + offset;
            cv::Point2f p3 = cv::Point2f(-cosA * w, sinA * w) + offset;
            cv::RotatedRect r(0.5f * (p1 + p3), cv::Size2f(w, h), -angle * 180.0f / (float)CV_PI);
            detections.push_back(r);
            confidences.push_back(score);
        }
    }
}

Qt-5-and-OpenCV-4-Computer-Vision-Projects 学习笔记 - 光学字符识别_第1张图片
Qt-5-and-OpenCV-4-Computer-Vision-Projects 学习笔记 - 光学字符识别_第2张图片

你可能感兴趣的:(Qt,学习,opencv,学习)