从3.2升级到OpenCV 3.4.1后出现分段错误

时间:2018-06-21 11:19:15

标签: c++ opencv

openCV 3.2升级到openCV 3.4.1后,我在运行程序时得到Segmentation fault

下面的完整源代码(我不是C专家)

运行程序./txtbin input.png output.png

希望有人会有所帮助:)

错误

> Error: signal 11:
> #1 ./txtbin(_Z6bTracei+0x1c) [0x5596628d8e68]
> #2 /lib/x86_64-linux-gnu/libc.so.6(+0x33060) [0x7fa1b8481060]
> #3 /usr/local/lib/libopencv_world.so.3.4(+0xa3f7da) [0x7fa1b9ac97da]
> #4 /usr/local/lib/libopencv_world.so.3.4(+0xa2782d) [0x7fa1b9ab182d]
> #5 /usr/local/lib/libopencv_world.so.3.4(_ZN2cv7imwriteERKNS_6StringERKNS_11_InputArrayERKSt6vectorIiSaIiEE+0x8e) [0x7fa1b9ab4a4e]
> #6 ./txtbin(_ZN6Txtbin8binarizeEv+0x12d) [0x5596628d67c3]
> #7 ./txtbin(_ZN6Txtbin3runEv+0x698) [0x5596628d8c10]
> #8 ./txtbin(main+0x3fa) [0x5596628d92db]
> #9 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1) [0x7fa1b846e2e1]

input.png

enter image description here

txtbin.cpp

/*
 *  Compile
 *  # g++ -rdynamic -std=c++11 txtbin.cpp -o txtbin `pkg-config opencv --cflags --libs`
 *
 *  Get opencv version
 *  # pkg-config --modversion opencv
*/

#include <signal.h>
#include "../backtrace/backtrace.h"
#include "txtbin.hpp"

Backtrace b;
void bTrace(int sig){
    b.trace(sig);
}

void usage(const std::string VERSION){
    std::cout << "txtbin: " << VERSION << "\nOpenCV: " << CV_VERSION << "\n\nUsage: txtbin input [options] output\n"
        "Options:\n"
        "\t-w <number>          -- Set max width (keeps aspect ratio)\n"
        "\t-h <number>          -- Set max height (keeps aspect ratio)\n"
        "\t-c                   -- Crop text contours\n"
        "\t-r                   -- Rotate contents (deskew)\n"
        "\t-m <number>          -- Set margins (%)\n"
        "\t-b <number>          -- Set blockside (pixel)\n"
        "\t                        Default: 9 pixel\n"
        "\t-t <number>          -- Set threshold (%)\n"
        "\t                        Default: 85 %\n"
        "\t-v                   -- Verbose\n" << std::endl;
}

int main(int argc, char* argv[]){
    signal(SIGSEGV, &bTrace);

    Txtbin a;

    try{
        //  Parse arguments
        for(int i = 1; i < argc; i++){
            std::string arg = std::string(argv[i]);

            if(i == 1){
                a.set_input(arg);
            }
            else if(arg == "-w"){
                a.set_width(atoi(argv[++i]));
            }
            else if(arg == "-h"){
                a.set_height(atoi(argv[++i]));
            }
            else if(arg == "-c"){
                a.set_crop(true);
            }
            else if(arg == "-r"){
                a.set_rotate(true);
            }
            else if(arg == "-m"){
                a.set_margin(atoi(argv[++i]));
            }
            else if(arg == "-b"){
                a.set_blockside(atoi(argv[++i]));
            }
            else if(arg == "-t"){
                a.set_threshold(atoi(argv[++i]));
            }
            else if(arg == "-v"){
                a.set_verbose(true);
            }
            else if(i == argc - 1){
                a.set_output(arg);
            }
            else{
                throw std::runtime_error("Argument '"+arg+"' is invalid");
            }
        }

        a.run();
    }
    catch(std::exception& e){
        std::cerr << "Error: " << e.what() << "\n" << std::endl;
        usage(a.VERSION);
        return 1;
    }

    return 0;
}

txtbin.h

struct textblock{
    int left = 0;
    int top = 0;
    int right = 0;
    int bottom = 0;
};

class Txtbin{
    private:
        std::string input               = "";
        std::string output              = "output.png";
        int max_width                   = 0;
        int max_height                  = 0;
        bool is_crop                    = false;
        bool is_rotate                  = false;
        float margin                    = 0;
        int blockside                   = 9;            //  set greater for larger fonts in image and vice versa
        bool is_verbose                 = false;
        float contrast                  = 0.01;         //  set smaller for lower contrast image
        float thresholding              = 0.85;
        void binarize                   ();
        cv::Mat src;
        cv::Mat calc_block_mean_variance(cv::Mat& img);
        textblock detect_text_block     (bool test_output);
        void downsize                   ();
        void crop                       (textblock coords);
        void error                      (const std::string& s);

    public:
        Txtbin();
        const std::string VERSION   = "0.3.6";
        void set_input              (const std::string& s);
        void set_output             (const std::string& s);
        void set_height             (int h);
        void set_width              (int w);
        void set_crop               (bool c);
        void set_rotate             (bool r);
        void set_margin             (float m);
        void set_blockside          (int b);
        void set_threshold          (int t);
        void set_verbose            (bool v);
        void run                    ();
};

txtbin.hpp

#include <iostream>
#include <fstream>
#include <chrono>
#include <boost/algorithm/string.hpp>
#include "/usr/local/include/opencv2/opencv.hpp"
#include "txtbin.h"

Txtbin::Txtbin(){}

void Txtbin::set_input(const std::string& s){
    input = s;
}

void Txtbin::set_output(const std::string& s){
    output = s;
}

void Txtbin::set_height(int h){
    max_height = h;
}

void Txtbin::set_width(int w){
    max_width = w;
}

void Txtbin::set_crop(bool c){
    is_crop = c;
}

void Txtbin::set_rotate(bool r){
    is_rotate = r;
}

void Txtbin::set_margin(float m){
    margin = m;
}

void Txtbin::set_blockside(int b){
    blockside = b;
}

void Txtbin::set_threshold(int t){
    thresholding = t / 100;
}

void Txtbin::set_verbose(bool v){
    is_verbose = v;
}

void Txtbin::error(const std::string& s){
    throw std::runtime_error(s);
}

void Txtbin::binarize(){
    src.convertTo(src, CV_32FC1, 1.0 / 255.0);
    cv::Mat res = calc_block_mean_variance(src);
    imwrite(output, res * 255);
}

void Txtbin::crop(textblock coords){
    if(coords.left < coords.right && coords.top < coords.bottom){
        if(coords.left < 0){
            coords.left = 0;
        }

        int crop_width = coords.right - coords.left;
        int trim_width = coords.left + crop_width - src.cols;
        if(trim_width > 0){
            crop_width -= trim_width;
        }

        if(coords.top < 0){
            coords.top = 0;
        }

        int crop_height = coords.bottom - coords.top;
        int trim_height = coords.top + crop_height - src.rows;
        if(trim_height > 0){
            crop_height -= trim_height;
        }

        cv::Rect cut_rect = cv::Rect(coords.left, coords.top, crop_width, crop_height);
        src = src(cut_rect);
    }
    else{
        std::cout << "Warning: Invalid text block coordinates. Cropping is omitted!" << std::endl;
    }
}

void Txtbin::downsize(){
    float
        width = src.cols,
        height = src.rows,
        scale = 0;

    bool resized = false;

    if(max_width > 0 && width > max_width){
        scale = width / max_width;
        width /= scale;
        height /= scale;
        resized = true;
    }

    if(max_height > 0 && height > max_height){
        scale = height / max_height;
        width /= scale;
        height /= scale;
        resized = true;
    }

    if(resized){
        resize(src, src, cv::Size(round(width), round(height)));
    }
}

textblock Txtbin::detect_text_block(bool test_output){
    cv::Mat img = src.clone();

    // downsample image and use it for processing
    int multiplier = 2;
    pyrDown(img, img);

    textblock block;

    block.left = img.cols;
    block.top = img.rows;

    int
        rect_bottom,
        rect_right;

    // morphological gradient
    cv::Mat grad;
    cv::Mat morphKernel = getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));
    morphologyEx(img, grad, cv::MORPH_GRADIENT, morphKernel);
    // binarize
    cv::Mat bw;
    threshold(grad, bw, 0.0, 255.0, cv::THRESH_BINARY | cv::THRESH_OTSU);
    // connect horizontally oriented regions
    cv::Mat connected;
    morphKernel = getStructuringElement(cv::MORPH_RECT, cv::Size(9, 1));
    morphologyEx(bw, connected, cv::MORPH_CLOSE, morphKernel);
    // find contours
    cv::Mat mask = cv::Mat::zeros(bw.size(), CV_8UC1);
    std::vector<std::vector<cv::Point> > contours;
    std::vector<cv::Vec4i> hierarchy;
    findContours(connected, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));

    cv::Scalar color = cv::Scalar(0, 255, 0);
    cv::Scalar color2 = cv::Scalar(0, 0, 255);
    int thickness = 2;

    if(test_output){
        cv::cvtColor(img, img, CV_GRAY2BGR);
    }

    // filter contours
    if(!hierarchy.empty()){
        for(int idx = 0; idx >= 0; idx = hierarchy[idx][0]){
            cv::Rect rect = boundingRect(contours[idx]);
            cv::Mat maskROI(mask, rect);
            maskROI = cv::Scalar(0, 0, 0);
            // fill the contour
            drawContours(mask, contours, idx, cv::Scalar(255, 255, 255), CV_FILLED);
            // ratio of non-zero pixels in the filled region
            double r = (double)countNonZero(maskROI) / (rect.width * rect.height);
            // assume at least 25% of the area is filled if it contains text
            if (r > 0.25 && 
            (rect.height > 8 && rect.width > 8) // constraints on region size
            // these two conditions alone are not very robust. better to use something 
            //like the number of significant peaks in a horizontal projection as a third condition
            ){
                if(test_output){
                    rectangle(img, rect, color, thickness);
                }
                //rectangle(src, cv::Rect(rect.x * multiplier, rect.y * multiplier, rect.width * multiplier, rect.height * multiplier), color, thickness);

                if(rect.y < block.top){
                    block.top = rect.y;
                }
                rect_bottom = rect.y + rect.height;
                if(rect_bottom > block.bottom){
                    block.bottom = rect_bottom;
                }
                if(rect.x < block.left){
                    block.left = rect.x;
                }
                rect_right = rect.x + rect.width;
                if(rect_right > block.right){
                    block.right = rect_right;
                }
            }
        }
    }

    if(test_output){
        rectangle(img, cv::Point(block.left, block.top), cv::Point(block.right, block.bottom), color2, thickness);
        imwrite("test_text_contours.jpg", img);
    }
    //rectangle(src, cv::Point(block.left * multiplier, block.top * multiplier), cv::Point(block.right * multiplier, block.bottom * multiplier), color2, thickness);

    block.left *= multiplier;
    block.top *= multiplier;
    block.right *= multiplier;
    block.bottom *= multiplier;

    return block;
}

cv::Mat Txtbin::calc_block_mean_variance(cv::Mat& img){
    cv::Mat res;
    cv::Mat I;
    img.convertTo(I, CV_32FC1);
    res = cv::Mat::zeros(img.rows / blockside, img.cols / blockside, CV_32FC1);
    cv::Mat inpaintmask;
    cv::Mat patch;
    cv::Mat small_img;
    cv::Scalar m, s;
    for(int i = 0; i < img.rows - blockside; i += blockside){
        for(int j = 0; j < img.cols - blockside; j += blockside){
            patch = I(cv::Range(i, i + blockside + 1), cv::Range(j, j + blockside + 1));
            meanStdDev(patch, m, s);

            if(s[0] > contrast){
                res.at<float>(i / blockside, j / blockside) = m[0];
            }
            else{
                res.at<float>(i / blockside, j / blockside) = 0;
            }
        }
    }
    resize(I, small_img, res.size());
    threshold(res, inpaintmask, 0.02, 1.0, cv::THRESH_BINARY);
    cv::Mat inpainted;
    small_img.convertTo(small_img, CV_8UC1, 255);
    inpaintmask.convertTo(inpaintmask, CV_8UC1);
    inpaint(small_img, inpaintmask, inpainted, 5, cv::INPAINT_TELEA);
    resize(inpainted, res, img.size());
    res.convertTo(res, CV_32FC1, 1.0 / 255.0);
    res = 1.0 - res;
    res = img + res;
    threshold(res, res, thresholding, 1, cv::THRESH_BINARY);

    return res;
}

void Txtbin::run(){
    //  Return error if input file is not defined
    if(input == ""){
        error("Input file not defined");
    }
    //  Return error if input file is not found
    else{
        std::ifstream stream(input.c_str());
        if(!stream.good()){
            error("Input file not found");
        }
    }

    //  Return error if output file is not defined
    if(output == ""){
        error("Output file not defined");
    }
    //  Return error if output file is not PNG
    else{
        std::string output_lc = output;
        boost::to_lower(output_lc);
        if(output_lc.substr(output_lc.find_last_of(".") + 1) != "png"){
            error("Output file must be PNG");
        }
    }

    bool test_output = false;

    auto start = std::chrono::high_resolution_clock::now();

    src = cv::imread(input, CV_LOAD_IMAGE_GRAYSCALE);

    if(is_crop || is_verbose){
        textblock coords = detect_text_block(test_output);

        if(is_verbose){
            std::cout << "Image dimensions: " << src.cols << " x " << src.rows << std::endl;
            std::cout << "Text block coordinates: Left|Top " << coords.left << "," << coords.top << " Right|Bottom " << coords.right << "," << coords.bottom << std::endl;
        }

        if(is_crop){
            crop(coords);
        }
    }

    if(margin){
        int border = src.cols * margin / 100;
        copyMakeBorder(src, src, border, border, border, border, cv::BORDER_CONSTANT, cv::Scalar(255, 255, 255));
    }

    downsize();

    binarize();

    auto elapsed = std::chrono::high_resolution_clock::now() - start;
    if(is_verbose){
        std::cout << "Execution time: " << (std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count() / 1000) << " ms" << std::endl;
    }
}

backtrace.h

#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>

class Backtrace {
    public:
        Backtrace();
        void trace(int sig);
};

Backtrace::Backtrace(){}

void Backtrace::trace(int sig){
    void *trace[10];
    char **messages = (char **)NULL;
    int i, trace_size = 0;

    trace_size = backtrace(trace, 10);
    messages = backtrace_symbols(trace, trace_size);

    fprintf(stderr, "Error: signal %d:\n", sig);
    for(i=1; i<trace_size; ++i){
        fprintf(stderr, "#%d %s\n", i, messages[i]);
    }

    exit(1);
}

1 个答案:

答案 0 :(得分:5)

以下Txtbin::binarize的实现不会发生段错误。

void Txtbin::binarize(){
    src.convertTo(src, CV_32FC1, 1.0 / 255.0);
    cv::Mat res = calc_block_mean_variance(src) * 255;
    imwrite(output, res);
}

使用Txtbin::binarize的先前定义,cv::imwrite将类型视为_InputArray::EXPR而不是_InputArray::MAT,并将img_vec的未初始化值传递给{ {1}}。段错误是由于尝试取消引用该未初始化值而导致的。 cv::imwrite_的值来自传递给_InputArray::EXPR的{​​{1}}(a MatExpr operator *)的返回值。

使用cv::MatExpr的修改后的定义,将_InputArray::_InputArray的返回值传递给Txtbin::binarize,然后将其传递给MatExpr operator *,并将值传递给{{1} }是MatExpr::operator Mat() const而不是MatOp_AddEx::assign。简而言之,似乎分配返回值_InputArray::_InputArray可以防止错误的类型转换(cv::Mat期望其第二个参数是cv::MatExpr,而不是MatExpr operator *)。

看来cv::imwrite的较新版本不容易发生此崩溃。在this commit之前,cv::InputArray调用了cv:Mat方法,this commit将该调用恢复为默认行为(opencv子句),以替换过于具体的条件。我确认将cv::imwrite更改为getMat重新编译else可以防止崩溃,即使使用先前版本的opencv(问题中的那个),并且返回值{{1} }被分配给cv::imwrite,如Txtbin::binarize的修改定义一样。

简而言之,您可以解决issue 15545,也可以安装其修补程序。