Search code examples
javascriptimagechartsdetectioncurve

How to detect a curve in a B&W image with a background grid?


I am struggling to extract the curve from this image:

curve

It represents scientific data which I don't want to touch, to avoid introducing errors, so I cannot just manually redraw the line in a plot digitizer: I want to automatically extract the curve, which then I will feed into a plot digitizer.

I tried programmatically drawing a white grid over the image, trying to exactly overlap the grid and delete it; but unfortunately this is the scanning of a paper sheet, so the grid is not perfect, hence spacing between lines is not always the same.

test1

test2

Code used for grid:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="generator" content="PSPad editor, www.pspad.com">
  <title></title>
  <script>
        sourcename = "converted";
        originalHeight = 0;
        originalWidth = 0;


function renderImage()
{
    var canvas = document.getElementById("grid");
    var context = canvas.getContext("2d");
    var image = new Image();
    image.onload = function() {
        selectedimage = image;
        originalHeight = image.height;
        originalWidth = image.width;
        canvas.height = image.height;
        canvas.width = image.width;
        context.drawImage(image,  0, 0, canvas.width, canvas.height);
        xstep = document.getElementById("rngXstep").value*1;
        ystep = document.getElementById("rngYstep").value*1;
        xoffset = document.getElementById("rngxoffset").value*1;
        yoffset = document.getElementById("rngyoffset").value*1;
        strokewidth = document.getElementById("rngstrokewidth").value*1;
        color = document.getElementById("color").value;
        document.getElementById("xstep").value = document.getElementById("rngXstep").value*1
        document.getElementById("ystep").value = document.getElementById("rngYstep").value*1
        document.getElementById("xoffset").value = document.getElementById("rngxoffset").value*1
        document.getElementById("yoffset").value = document.getElementById("rngyoffset").value*1
        document.getElementById("strokewidth").value = document.getElementById("rngstrokewidth").value*1
        drawGrid(context,0,0,0,xoffset,yoffset,xstep,ystep,0,0,color,strokewidth);
    };
    image.src = "chart-test-2.png";
}

var drawGrid = function(context, w, h, id, xoff, yoff, xstep, ystep, xmax, ymax, color,width) {
            for (var x = 0; x <= originalWidth; x += xstep) {
                context.moveTo(1 + x + xoff, 0 + yoff);
                context.lineTo(1 + x + xoff, originalHeight + yoff);
            }


            for (var x = 0; x <= originalHeight; x += ystep) {
                context.moveTo(0, 1 + x + yoff);
                context.lineTo(originalWidth, 1 + x + yoff);
            }

            context.strokeStyle = color;
            context.lineWidth = width;
            context.stroke();
}


</script>
  </head>
  <body>
x step: <input type="text" id="xstep" name="xstep" value=4.1>
<input type="range" id="rngXstep" name = "rngXstep" value="4.1" onchange="renderImage()" step=.1><br>

y step:<input type="text" id="ystep" name="ystep" value=4.1>
<input type="range" id="rngYstep" name = "rngYstep" value="4.1" onchange="renderImage()" step=.1><br>

x offset: <input type="text" id="xoffset" name="xoffset" value=0>
<input type="range" id="rngxoffset" name = "rngxoffset" value="0" onchange="renderImage()" step=.1><br>

y offset:<input type="text" id="yoffset" name="yoffset" value=0>
<input type="range" id="rngyoffset" name = "rngyoffset" value="0" onchange="renderImage()" step=.1><br>

width: <input type="text" id="strokewidth" name="strokewidth" value=2>
<input type="range" id="rngstrokewidth" name = "rngstrokewidth" value="2" onchange="renderImage()" step=.1><br>

color: <input type="text" id="color" name="color" value="white"> (red, white, blue,...)<br>

<canvas id="grid"></canvas><br>
    <img id="chart" name="chart" src="chart-test-2.png"  ><br>
  </body>
</html>

Putting directly the image into a plot digitizer does not work, as the curve has same color of grid, and plot digitizers use color to automatically detect curves.

Then I decided to try a different approach: looking for the thickest line in the image; I coould try writing my own algorithm, scanning each single pixel and determining which one has more black neighbours ... but probably I would reinvent the wheel; I asked to ImageMagick author if his program is able to do such thing, but he said it does not.

Then I found that OpenCV implements some algorithms such as Hough Transform, Contour detector, and probably many others; but I am completely new to OpenCV, so I would need some hints about which functions best fits my need.

Can Hough transform detect also curved lines, or only segments? Can contour detector also detect open curves or just closed lines? Or should I use canny edge detection?

But another problem is that I need javascript examples, not C++ or Python.

But I am open to any javascript solution, OpenCV is not the only option.


Solution

  • After several experiments (based on these examples) and tests, I ended up with this code:

    let src = cv.imread('canvasInput');
    let dst = new cv.Mat();
    let M = cv.Mat.ones(3, 3, cv.CV_8U);
    let anchor = new cv.Point(-1, -1);
    
    cv.dilate(src, dst, M, anchor, 1, cv.BORDER_CONSTANT, cv.morphologyDefaultBorderValue());
    cv.imshow('canvasOutput', dst);
    src.delete(); dst.delete(); M.delete();
    

    Probably it is possible to get even better results by concatenating multiple further filters, anyway already in this way I have acceptable results.

    Full source image: Source

    Edited, but data untouched:

    annotated

    Processed by above code ("dilate"):

    processed

    Submitted to webplotdigitizer (look at settings on the right; yellow area is the only one processed by webplotdigitizer):

    submitted to webplotdigitizer

    Raw processing by webplotdigitizer ; some stray points appear, but they can be easily manually deleted:

    digitized

    Cleaned up:

    cleaned

    Re-plotted:

    replotted

    Note: in original image, horizontal and vertical scales are different: horizontal one is 4x w.r.t vertical one; resizing vertically 4x I get:

    resized

    Overlaying final image on source image, we can see it matches perfectly:

    overlay

    Of course it would be better to have digitization inside the same source code, but this is another story...