4
votes

I've written a short program that demonstrates Hough Line Detection using OpenCV.

In the final step, the code takes the original, blurred, greyscale image, overlays the canny edge detection results, and then overlays the hough-transform detected lines.

The hough lines are being rendered as solid red (R=255), 3px lines, however when I overlay them, the image below is showing through for some reason. Example below.

Original Image:Original Image

Blurred, Greyscale image with Canny edges + Hough Lines Overlayed:enter image description here

Zoomed in segment:enter image description here

As can be seen, the greyscale image is coming through the (apparently) solid red. Why is this?

Full code is below:

houghtest.cpp

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

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include "toolbarconfig.h"


using namespace cv;


// Global variables
const char* window_name = "Hough Line Detection";

ToolbarConfig
    gaussian = ToolbarConfig(0, 15, 1, 6),
    canny = ToolbarConfig(20, 150, 2, 40),
    hough = ToolbarConfig(50, 400, 10, 200);

Mat input;

// Function prototypes
void update(int, void*);
void chromakey(const Mat under, const Mat over, Mat *dst, const Scalar& color);
void help();


/**
 * Creates an interactive example of running hough line detection on a
 * sample image
 */
int main( int argc, char** argv ) {
    const char* filename = argc >= 2 ? argv[1] : "pic1.png";

    input = imread(filename, CV_LOAD_IMAGE_COLOR); if(input.empty()) {
        help();

        std::cout << "Can not open " << filename << std::endl;
        return -1;
    }

    // Convert the image to grayscale
    cvtColor(input, input, CV_BGR2GRAY);

    // Create a window
    namedWindow(window_name, CV_WINDOW_AUTOSIZE);

    // Create trackbars for the user to enter thresholds
    createTrackbar("Gaussian Kernel Size", window_name, &(gaussian.t_current), gaussian.tmax(), update);
    createTrackbar("Canny Min Threshold", window_name, &(canny.t_current), canny.tmax(), update);
    createTrackbar("Hough Line Threshold", window_name, &(hough.t_current), hough.tmax(), update);

    // Show the image
    update(NULL, NULL);

    // Wait until user exit program by pressing a key
    waitKey(0);

    return 0;
}


/**
 * Trackbar callback - updates the display
 */
void update(int, void*) {
    const int CANNY_RATIO = 3, CANNY_KERNEL_SIZE = 3;

    Mat blurred_input, canny_edges, hough_lines;

    // Reduce noise with a gaussian kernel
    if(gaussian.current() != 0) {
        blur(input, blurred_input, Size(gaussian.current(), gaussian.current()));
    } else {
        blurred_input = input;
    }

    // Run Canny edge detector
    Canny(blurred_input, canny_edges, canny.current(), canny.current()*CANNY_RATIO, CANNY_KERNEL_SIZE);

    // ==== Begin Hough line detector phase

    // Create a vector to store the located lines in
    vector<Vec2f> line_vector;

    // Run the transform
    HoughLines(canny_edges, line_vector, 1, CV_PI/180, hough.current(), 0, 0);

    //std::cout << lines.size() << " lines detected" << std::endl;

    // Prepare the hough_lines image
    hough_lines = Mat::zeros(canny_edges.rows, canny_edges.cols, CV_8UC3);

    // Draw detected lines into an image
    for(size_t i = 0; i < line_vector.size(); i++) {
        float rho = line_vector[i][0], theta = line_vector[i][1];
        Point pt1, pt2;

        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;

        pt1.x = cvRound(x0 + 1000*(-b));
        pt1.y = cvRound(y0 + 1000*(a));
        pt2.x = cvRound(x0 - 1000*(-b));
        pt2.y = cvRound(y0 - 1000*(a));

        line(hough_lines, pt1, pt2, Scalar(0, 0, 255), 3, 0);
    }

    // Overlay the hough lines onto the original blurred image
    Mat blurred_input_color, canny_edges_color, input_with_canny, combined_images;
    cvtColor(blurred_input, blurred_input_color, CV_GRAY2BGR);
    cvtColor(canny_edges, canny_edges_color, CV_GRAY2BGR);

    chromakey(blurred_input_color, canny_edges_color, &input_with_canny, Scalar(0, 0, 0));
    chromakey(input_with_canny, hough_lines, &combined_images, Scalar(0, 0, 0));

    // Display the result
    imshow(window_name, combined_images);
}

/**
 * Takes two images and overlays them, using color as a chroma-key
 * Any pixels in the 'over' image that match the given color value will
 * effectively be transparent - the 'under' image will show through
 *
 * @precondition: All passed images must first be in BGR format
 */
void chromakey(const Mat under, const Mat over, Mat *dst, const Scalar& color) {
    // Mats must be the same size
    if(under.rows != over.rows || under.cols != over.cols) {
        std::cout << "Error, image dimensions must match" << std::endl;
        return;
    }

    // Create the destination matrix
    *dst = Mat::zeros(under.rows, under.cols, CV_8UC3);

    for(int y=0; y<under.rows; y++) {
        for(int x=0; x<under.cols; x++) {
            dst->at<Vec3b>(y,x)[0] = over.at<Vec3b>(y,x)[0] == color[0] ? under.at<Vec3b>(y,x)[0] : over.at<Vec3b>(y,x)[0];
            dst->at<Vec3b>(y,x)[1] = over.at<Vec3b>(y,x)[1] == color[1] ? under.at<Vec3b>(y,x)[1] : over.at<Vec3b>(y,x)[1];
            dst->at<Vec3b>(y,x)[2] = over.at<Vec3b>(y,x)[2] == color[2] ? under.at<Vec3b>(y,x)[2] : over.at<Vec3b>(y,x)[2];
        }
    }
}


/**
 * Prints usage information
 */
void help() {
    std::cout << "\nThis program demonstrates line finding with the Hough transform.\n" "Usage:\n"
    "./houghlines <image_name>, Default is pic1.png\n" << std::endl;
}

toolbarconfig.h

#ifndef TOOLBARCONFIG_H
#define TOOLBARCONFIG_H

class ToolbarConfig {
    public:
    ToolbarConfig(int min, int max, int stepsize, int current);

    int w2t(int world_value);
    int t2w(int toolbar_value);

    int current();
    int tmax();
    int tmin();

    int min;
    int max;
    int stepsize;

    int t_current;
};

#endif

toolbarconfig.cpp

#include <algorithm>

#include "toolbarconfig.h"

ToolbarConfig::ToolbarConfig(int min, int max, int stepsize, int current) {
    this->min = min;
    this->max = max;
    this->stepsize = stepsize;
    this->t_current = this->w2t(current);
}

int ToolbarConfig::w2t(int world_value) {
    return int((std::min(std::max(world_value, min), max) - min) / stepsize);
}

int ToolbarConfig::t2w(int toolbar_value) {
    return toolbar_value * stepsize + min;
}

int ToolbarConfig::current() {
    return t2w(t_current);
}

int ToolbarConfig::tmax() {
    return w2t(max);
}

int ToolbarConfig::tmin() {
    return w2t(min);
}

Happy to also supply my Makefile if required.

Thanks in advance.

1

1 Answers

7
votes

The error must be here:

dst->at<Vec3b>(y,x)[0] = over.at<Vec3b>(y,x)[0] == color[0] ? under.at<Vec3b>(y,x)[0] : over.at<Vec3b>(y,x)[0];
dst->at<Vec3b>(y,x)[1] = over.at<Vec3b>(y,x)[1] == color[1] ? under.at<Vec3b>(y,x)[1] : over.at<Vec3b>(y,x)[1];
dst->at<Vec3b>(y,x)[2] = over.at<Vec3b>(y,x)[2] == color[2] ? under.at<Vec3b>(y,x)[2] : over.at<Vec3b>(y,x)[2];

When you first call

chromakey(blurred_input_color, canny_edges_color, &input_with_canny, Scalar(0, 0, 0));

the canny_edges_color's white pixel value is (255, 255, 255), so in the above comparisons, you will get the over values for each channel, so the pixel's color will be (255, 255, 255) and the image will show correctly.

However, in the second case:

chromakey(input_with_canny, hough_lines, &combined_images, Scalar(0, 0, 0));

your hugh_lines red pixels have the value (0, 0, 255), so for the first two comparisons they will get the value of under, since

over.at<Vec3b>(y,x)[0] == color[0] and

over.at<Vec3b>(y,x)[1] == color[1] .

Only dst->at<Vec3b>(y,x)[2] will get the 255 value. In order for the line to appear solid, it should be dst->at<Vec3b>(y,x)[0] = 0 and dst->at<Vec3b>(y,x)[1] = 0 instead in this case.


Also, according to this answer you should probably initialize *dst like that:

*dst = Mat(under.rows,under.cols,CV_8UC3,CV_RGB(0,0,0));

since it is a 3 channel Mat.