轮廓发现与绘制
一般而言,图像的轮廓都是由一系列的像素点构成,这些像素点属于二值图像的前景图像,每个轮廓都是一组点,而各组点则组成了轮廓。该方式的原理很复杂,简述即通过定义一系列的边缘点类型与拓扑集合结构类型,然后对二值图像的扫描来完成边缘类型的寻找与拓扑结构的构建,以此完成轮廓的发现。轮廓发现的函数声明如下:
findContours(image, contours, hierarchy, mode, method, offset);
各参数解释如下:
-
image
8单通道的输入图像。 -
contours
List类型的像素点集合,即泛型为MatOfPoint类型。 -
hierarchy
拓扑信息。 -
mode
返回的轮廓拓扑模式,其枚举有以下四种:RETR_EXTERNAL(表示获取最大层最大的轮廓)、RETR_LIST(表示获取所有轮廓)、RETR_CCOMP(表示获取的所有轮廓呈现的双层组织结构,第一层为外部边界,第二层为孔边界)、RETR_TREE(表示对获取的轮廓进行按照树结构组织,显示归属于嵌套层次) -
method
描述轮廓的方法,其枚举有以下四种:CHAIN_APPROX_NONE(将链式编码中的说有点都转换为点输出)、CHAIN_APPROX_SIMPLE(压缩水平,垂直,倾斜部分的轮廓点输出)、CHAIN_APPROX_TC89_L1(Teh-Chin链式逼近算法)、CHAIN_APPROX_TC89_KCOS(Teh-Chin链式逼近算法)、 -
offset
是否有位移。无位移则该参数为相对原点值为Point(0,0)。
轮廓发现后即可使用轮廓绘制函数进行操作。由于轮廓发现计算的是List,那么迭代循环轮廓即可。轮廓绘制的函数声明如下:
drawContours(image, contours,contourIdx, color , thickness);
各参数解释如下:
-
image
待绘制轮廓的图像。 -
contours
List集合,即上文提到的轮廓发现输出集合。 -
contourIdx
声明绘制第几个轮廓。 -
color
线条颜色。 -
thickness
厚度,通常为1或2。
本例采用二值输入作为源,但也可采用Canny边缘检测作为输入。前者是基于二者分割,后者基于梯度计算,后者一般而言可以更好的反应图像轮廓的信息。
Java代码(Controller层)
public class Controller{
@FXML private Text fxText;
@FXML private ImageView imageView;
@FXML public void handleButtonEvent(ActionEvent actionEvent) throws IOException {
Node source = (Node) actionEvent.getSource();
Window theStage = source.getScene().getWindow();
FileChooser fileChooser = new FileChooser();
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("PNG files (*.png)", "*.png");
fileChooser.getExtensionFilters().add(extFilter);
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("JPG Files(*.jpg)", "*.jpg"));
File file = fileChooser.showOpenDialog(theStage);
runInSubThread(file.getPath());
}
private void runInSubThread(String filePath){
new Thread(new Runnable() {
@Override
public void run() {
try {
WritableImage writableImage = houghCircles(filePath);
Platform.runLater(new Runnable() {
@Override
public void run() {
imageView.setImage(writableImage);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private WritableImage contourDiscoveryAndRendering(String filePath) throws IOException {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Mat src = Imgcodecs.imread(filePath);
Mat dst = new Mat();
Mat gray = new Mat();
Mat binary = new Mat();
// Binary image.
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
Imgproc.threshold(gray, binary, 0,255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
// Contour discovery
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
Imgproc.findContours(binary, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE, new Point(0,0));
// Contour rendering
dst.create(src.size(), src.type());
for (int i = 0; i < contours.size(); i++) {
Imgproc.drawContours(dst, contours, i, new Scalar(0,0,255),2);
}
MatOfByte matOfByte = new MatOfByte();
Imgcodecs.imencode(".jpg", dst, matOfByte);
byte[] bytes = matOfByte.toArray();
InputStream in = new ByteArrayInputStream(bytes);
BufferedImage bufImage = ImageIO.read(in);
WritableImage writableImage = SwingFXUtils.toFXImage(bufImage, null);
return writableImage;
}
}
运行图
原图