mirror of https://github.com/opencv/opencv.git
Add Java and Python code for Image Segmentation with Distance Transform and Watershed Algorithm tutorial. Use more Pythonic code.
parent
db48f7b5d1
commit
7469981d1a
7 changed files with 555 additions and 89 deletions
@ -0,0 +1,215 @@ |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Random; |
||||
|
||||
import org.opencv.core.Core; |
||||
import org.opencv.core.CvType; |
||||
import org.opencv.core.Mat; |
||||
import org.opencv.core.MatOfPoint; |
||||
import org.opencv.core.Point; |
||||
import org.opencv.core.Scalar; |
||||
import org.opencv.highgui.HighGui; |
||||
import org.opencv.imgcodecs.Imgcodecs; |
||||
import org.opencv.imgproc.Imgproc; |
||||
|
||||
/** |
||||
* |
||||
* @brief Sample code showing how to segment overlapping objects using Laplacian filtering, in addition to Watershed |
||||
* and Distance Transformation |
||||
* |
||||
*/ |
||||
class ImageSegmentation { |
||||
public void run(String[] args) { |
||||
//! [load_image]
|
||||
// Load the image
|
||||
String filename = args.length > 0 ? args[0] : "../data/cards.png"; |
||||
Mat srcOriginal = Imgcodecs.imread(filename); |
||||
if (srcOriginal.empty()) { |
||||
System.err.println("Cannot read image: " + filename); |
||||
System.exit(0); |
||||
} |
||||
|
||||
// Show source image
|
||||
HighGui.imshow("Source Image", srcOriginal); |
||||
//! [load_image]
|
||||
|
||||
//! [black_bg]
|
||||
// Change the background from white to black, since that will help later to
|
||||
// extract
|
||||
// better results during the use of Distance Transform
|
||||
Mat src = srcOriginal.clone(); |
||||
byte[] srcData = new byte[(int) (src.total() * src.channels())]; |
||||
src.get(0, 0, srcData); |
||||
for (int i = 0; i < src.rows(); i++) { |
||||
for (int j = 0; j < src.cols(); j++) { |
||||
if (srcData[(i * src.cols() + j) * 3] == (byte) 255 && srcData[(i * src.cols() + j) * 3 + 1] == (byte) 255 |
||||
&& srcData[(i * src.cols() + j) * 3 + 2] == (byte) 255) { |
||||
srcData[(i * src.cols() + j) * 3] = 0; |
||||
srcData[(i * src.cols() + j) * 3 + 1] = 0; |
||||
srcData[(i * src.cols() + j) * 3 + 2] = 0; |
||||
} |
||||
} |
||||
} |
||||
src.put(0, 0, srcData); |
||||
|
||||
// Show output image
|
||||
HighGui.imshow("Black Background Image", src); |
||||
//! [black_bg]
|
||||
|
||||
//! [sharp]
|
||||
// Create a kernel that we will use to sharpen our image
|
||||
Mat kernel = new Mat(3, 3, CvType.CV_32F); |
||||
// an approximation of second derivative, a quite strong kernel
|
||||
float[] kernelData = new float[(int) (kernel.total() * kernel.channels())]; |
||||
kernelData[0] = 1; kernelData[1] = 1; kernelData[2] = 1; |
||||
kernelData[3] = 1; kernelData[4] = -8; kernelData[5] = 1; |
||||
kernelData[6] = 1; kernelData[7] = 1; kernelData[8] = 1; |
||||
kernel.put(0, 0, kernelData); |
||||
|
||||
// do the laplacian filtering as it is
|
||||
// well, we need to convert everything in something more deeper then CV_8U
|
||||
// because the kernel has some negative values,
|
||||
// and we can expect in general to have a Laplacian image with negative values
|
||||
// BUT a 8bits unsigned int (the one we are working with) can contain values
|
||||
// from 0 to 255
|
||||
// so the possible negative number will be truncated
|
||||
Mat imgLaplacian = new Mat(); |
||||
Imgproc.filter2D(src, imgLaplacian, CvType.CV_32F, kernel); |
||||
Mat sharp = new Mat(); |
||||
src.convertTo(sharp, CvType.CV_32F); |
||||
Mat imgResult = new Mat(); |
||||
Core.subtract(sharp, imgLaplacian, imgResult); |
||||
|
||||
// convert back to 8bits gray scale
|
||||
imgResult.convertTo(imgResult, CvType.CV_8UC3); |
||||
imgLaplacian.convertTo(imgLaplacian, CvType.CV_8UC3); |
||||
|
||||
// imshow( "Laplace Filtered Image", imgLaplacian );
|
||||
HighGui.imshow("New Sharped Image", imgResult); |
||||
//! [sharp]
|
||||
|
||||
//! [bin]
|
||||
// Create binary image from source image
|
||||
Mat bw = new Mat(); |
||||
Imgproc.cvtColor(imgResult, bw, Imgproc.COLOR_BGR2GRAY); |
||||
Imgproc.threshold(bw, bw, 40, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU); |
||||
HighGui.imshow("Binary Image", bw); |
||||
//! [bin]
|
||||
|
||||
//! [dist]
|
||||
// Perform the distance transform algorithm
|
||||
Mat dist = new Mat(); |
||||
Imgproc.distanceTransform(bw, dist, Imgproc.DIST_L2, 3); |
||||
|
||||
// Normalize the distance image for range = {0.0, 1.0}
|
||||
// so we can visualize and threshold it
|
||||
Core.normalize(dist, dist, 0, 1., Core.NORM_MINMAX); |
||||
Mat distDisplayScaled = dist.mul(dist, 255); |
||||
Mat distDisplay = new Mat(); |
||||
distDisplayScaled.convertTo(distDisplay, CvType.CV_8U); |
||||
HighGui.imshow("Distance Transform Image", distDisplay); |
||||
//! [dist]
|
||||
|
||||
//! [peaks]
|
||||
// Threshold to obtain the peaks
|
||||
// This will be the markers for the foreground objects
|
||||
Imgproc.threshold(dist, dist, .4, 1., Imgproc.THRESH_BINARY); |
||||
|
||||
// Dilate a bit the dist image
|
||||
Mat kernel1 = Mat.ones(3, 3, CvType.CV_8U); |
||||
Imgproc.dilate(dist, dist, kernel1); |
||||
Mat distDisplay2 = new Mat(); |
||||
dist.convertTo(distDisplay2, CvType.CV_8U); |
||||
distDisplay2 = distDisplay2.mul(distDisplay2, 255); |
||||
HighGui.imshow("Peaks", distDisplay2); |
||||
//! [peaks]
|
||||
|
||||
//! [seeds]
|
||||
// Create the CV_8U version of the distance image
|
||||
// It is needed for findContours()
|
||||
Mat dist_8u = new Mat(); |
||||
dist.convertTo(dist_8u, CvType.CV_8U); |
||||
|
||||
// Find total markers
|
||||
List<MatOfPoint> contours = new ArrayList<>(); |
||||
Mat hierarchy = new Mat(); |
||||
Imgproc.findContours(dist_8u, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE); |
||||
|
||||
// Create the marker image for the watershed algorithm
|
||||
Mat markers = Mat.zeros(dist.size(), CvType.CV_32S); |
||||
|
||||
// Draw the foreground markers
|
||||
for (int i = 0; i < contours.size(); i++) { |
||||
Imgproc.drawContours(markers, contours, i, new Scalar(i + 1), -1); |
||||
} |
||||
|
||||
// Draw the background marker
|
||||
Imgproc.circle(markers, new Point(5, 5), 3, new Scalar(255, 255, 255), -1); |
||||
Mat markersScaled = markers.mul(markers, 10000); |
||||
Mat markersDisplay = new Mat(); |
||||
markersScaled.convertTo(markersDisplay, CvType.CV_8U); |
||||
HighGui.imshow("Markers", markersDisplay); |
||||
//! [seeds]
|
||||
|
||||
//! [watershed]
|
||||
// Perform the watershed algorithm
|
||||
Imgproc.watershed(imgResult, markers); |
||||
|
||||
Mat mark = Mat.zeros(markers.size(), CvType.CV_8U); |
||||
markers.convertTo(mark, CvType.CV_8UC1); |
||||
Core.bitwise_not(mark, mark); |
||||
// imshow("Markers_v2", mark); // uncomment this if you want to see how the mark
|
||||
// image looks like at that point
|
||||
|
||||
// Generate random colors
|
||||
Random rng = new Random(12345); |
||||
List<Scalar> colors = new ArrayList<>(contours.size()); |
||||
for (int i = 0; i < contours.size(); i++) { |
||||
int b = rng.nextInt(256); |
||||
int g = rng.nextInt(256); |
||||
int r = rng.nextInt(256); |
||||
|
||||
colors.add(new Scalar(b, g, r)); |
||||
} |
||||
|
||||
// Create the result image
|
||||
Mat dst = Mat.zeros(markers.size(), CvType.CV_8UC3); |
||||
byte[] dstData = new byte[(int) (dst.total() * dst.channels())]; |
||||
dst.get(0, 0, dstData); |
||||
|
||||
// Fill labeled objects with random colors
|
||||
int[] markersData = new int[(int) (markers.total() * markers.channels())]; |
||||
markers.get(0, 0, markersData); |
||||
for (int i = 0; i < markers.rows(); i++) { |
||||
for (int j = 0; j < markers.cols(); j++) { |
||||
int index = markersData[i * markers.cols() + j]; |
||||
if (index > 0 && index <= contours.size()) { |
||||
dstData[(i * dst.cols() + j) * 3 + 0] = (byte) colors.get(index - 1).val[0]; |
||||
dstData[(i * dst.cols() + j) * 3 + 1] = (byte) colors.get(index - 1).val[1]; |
||||
dstData[(i * dst.cols() + j) * 3 + 2] = (byte) colors.get(index - 1).val[2]; |
||||
} else { |
||||
dstData[(i * dst.cols() + j) * 3 + 0] = 0; |
||||
dstData[(i * dst.cols() + j) * 3 + 1] = 0; |
||||
dstData[(i * dst.cols() + j) * 3 + 2] = 0; |
||||
} |
||||
} |
||||
} |
||||
dst.put(0, 0, dstData); |
||||
|
||||
// Visualize the final image
|
||||
HighGui.imshow("Final Result", dst); |
||||
//! [watershed]
|
||||
|
||||
HighGui.waitKey(); |
||||
System.exit(0); |
||||
} |
||||
} |
||||
|
||||
public class ImageSegmentationDemo { |
||||
public static void main(String[] args) { |
||||
// Load the native OpenCV library
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME); |
||||
|
||||
new ImageSegmentation().run(args); |
||||
} |
||||
} |
@ -0,0 +1,138 @@ |
||||
from __future__ import print_function |
||||
import cv2 as cv |
||||
import numpy as np |
||||
import argparse |
||||
import random as rng |
||||
|
||||
rng.seed(12345) |
||||
|
||||
## [load_image] |
||||
# Load the image |
||||
parser = argparse.ArgumentParser(description='Code for Image Segmentation with Distance Transform and Watershed Algorithm.\ |
||||
Sample code showing how to segment overlapping objects using Laplacian filtering, \ |
||||
in addition to Watershed and Distance Transformation') |
||||
parser.add_argument('--input', help='Path to input image.', default='../data/cards.png') |
||||
args = parser.parse_args() |
||||
|
||||
src = cv.imread(args.input) |
||||
if src is None: |
||||
print('Could not open or find the image:', args.input) |
||||
exit(0) |
||||
|
||||
# Show source image |
||||
cv.imshow('Source Image', src) |
||||
## [load_image] |
||||
|
||||
## [black_bg] |
||||
# Change the background from white to black, since that will help later to extract |
||||
# better results during the use of Distance Transform |
||||
src[np.all(src == 255, axis=2)] = 0 |
||||
|
||||
# Show output image |
||||
cv.imshow('Black Background Image', src) |
||||
## [black_bg] |
||||
|
||||
## [sharp] |
||||
# Create a kernel that we will use to sharpen our image |
||||
# an approximation of second derivative, a quite strong kernel |
||||
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]], dtype=np.float32) |
||||
|
||||
# do the laplacian filtering as it is |
||||
# well, we need to convert everything in something more deeper then CV_8U |
||||
# because the kernel has some negative values, |
||||
# and we can expect in general to have a Laplacian image with negative values |
||||
# BUT a 8bits unsigned int (the one we are working with) can contain values from 0 to 255 |
||||
# so the possible negative number will be truncated |
||||
imgLaplacian = cv.filter2D(src, cv.CV_32F, kernel) |
||||
sharp = np.float32(src) |
||||
imgResult = sharp - imgLaplacian |
||||
|
||||
# convert back to 8bits gray scale |
||||
imgResult = np.clip(imgResult, 0, 255) |
||||
imgResult = imgResult.astype('uint8') |
||||
imgLaplacian = np.clip(imgLaplacian, 0, 255) |
||||
imgLaplacian = np.uint8(imgLaplacian) |
||||
|
||||
#cv.imshow('Laplace Filtered Image', imgLaplacian) |
||||
cv.imshow('New Sharped Image', imgResult) |
||||
## [sharp] |
||||
|
||||
## [bin] |
||||
# Create binary image from source image |
||||
bw = cv.cvtColor(imgResult, cv.COLOR_BGR2GRAY) |
||||
_, bw = cv.threshold(bw, 40, 255, cv.THRESH_BINARY | cv.THRESH_OTSU) |
||||
cv.imshow('Binary Image', bw) |
||||
## [bin] |
||||
|
||||
## [dist] |
||||
# Perform the distance transform algorithm |
||||
dist = cv.distanceTransform(bw, cv.DIST_L2, 3) |
||||
|
||||
# Normalize the distance image for range = {0.0, 1.0} |
||||
# so we can visualize and threshold it |
||||
cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX) |
||||
cv.imshow('Distance Transform Image', dist) |
||||
## [dist] |
||||
|
||||
## [peaks] |
||||
# Threshold to obtain the peaks |
||||
# This will be the markers for the foreground objects |
||||
_, dist = cv.threshold(dist, 0.4, 1.0, cv.THRESH_BINARY) |
||||
|
||||
# Dilate a bit the dist image |
||||
kernel1 = np.ones((3,3), dtype=np.uint8) |
||||
dist = cv.dilate(dist, kernel1) |
||||
cv.imshow('Peaks', dist) |
||||
## [peaks] |
||||
|
||||
## [seeds] |
||||
# Create the CV_8U version of the distance image |
||||
# It is needed for findContours() |
||||
dist_8u = dist.astype('uint8') |
||||
|
||||
# Find total markers |
||||
_, contours, _ = cv.findContours(dist_8u, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) |
||||
|
||||
# Create the marker image for the watershed algorithm |
||||
markers = np.zeros(dist.shape, dtype=np.int32) |
||||
|
||||
# Draw the foreground markers |
||||
for i in range(len(contours)): |
||||
cv.drawContours(markers, contours, i, (i+1), -1) |
||||
|
||||
# Draw the background marker |
||||
cv.circle(markers, (5,5), 3, (255,255,255), -1) |
||||
cv.imshow('Markers', markers*10000) |
||||
## [seeds] |
||||
|
||||
## [watershed] |
||||
# Perform the watershed algorithm |
||||
cv.watershed(imgResult, markers) |
||||
|
||||
#mark = np.zeros(markers.shape, dtype=np.uint8) |
||||
mark = markers.astype('uint8') |
||||
mark = cv.bitwise_not(mark) |
||||
# uncomment this if you want to see how the mark |
||||
# image looks like at that point |
||||
#cv.imshow('Markers_v2', mark) |
||||
|
||||
# Generate random colors |
||||
colors = [] |
||||
for contour in contours: |
||||
colors.append((rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))) |
||||
|
||||
# Create the result image |
||||
dst = np.zeros((markers.shape[0], markers.shape[1], 3), dtype=np.uint8) |
||||
|
||||
# Fill labeled objects with random colors |
||||
for i in range(markers.shape[0]): |
||||
for j in range(markers.shape[1]): |
||||
index = markers[i,j] |
||||
if index > 0 and index <= len(contours): |
||||
dst[i,j,:] = colors[index-1] |
||||
|
||||
# Visualize the final image |
||||
cv.imshow('Final Result', dst) |
||||
## [watershed] |
||||
|
||||
cv.waitKey() |
Loading…
Reference in new issue