图像处理先填充

2016-07-29 17:46:40 Small_Mouse0 阅读数 7788

在网上搜了好多都没有特别使用的,java实现imfill的算法,有一些关于opencv的,但是作为一枚初学菜鸟,还是很难搞懂的,直入主题吧

算法设计思路

(1)输入一张二值图像(很多看似黑白的图像并不完全是黑白的,当然在此做了处理)
(2)将图像RGB值保存到一个二维数组中,例如 imageColor[i][j] = 0xFFFF00FF;
(3)对得到的二维数组进行处理,逻辑如下:
根据像素之间关系可以将一个像素理解为被八个像素包裹的像素,即matlab中imFill的所谓八连通处理,和四连通处理,此处为四连通处理
对某一个像素的上下左右四个方向进行遍历,遍历的长度是有一定要求的,根据需求而定,如果四个方向在遍历区域内都有白色出现,则将其赋值为白色,从而实现对图像的简单填充处理,
(4)创建图像缓冲区BufferedImage,存储相应像素矩阵


源码如下:

imfill实现类:

package java二值图像填充;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class imfll {

    BufferedImage grayImage;
    BufferedImage imfillImage;

    public imfll(String imFill,Graphics graphics) {
        // TODO Auto-generated constructor stub
        int[][] imageColor ;
        //imageColor = new int[][];
        try {
             grayImage = ImageIO.read(new File(imFill));

             //创建缓冲区
            imfillImage = new BufferedImage(grayImage.getWidth(),
                                            grayImage.getHeight(),
                                            grayImage.getType());
            //像素矩阵
            imageColor = new int[grayImage.getWidth()][grayImage.getHeight()];

            for (int i = 0; i < grayImage.getWidth(); i++) {
                for (int j = 0; j < grayImage.getHeight(); j++) {
                    imageColor[i][j] = grayImage.getRGB(i , j);
                }
            }

            imageColor = imfillImage(15,imageColor);
            //将像素矩阵赋值到缓冲区相应位置
            for (int i = 0; i < grayImage.getWidth(); i++) {
                for (int j = 0; j < grayImage.getHeight(); j++) {
                    imfillImage.setRGB(i, j, imageColor[i][j]);         
                }
            }

            graphics.drawImage(grayImage, 0, 0, 380, 400,null);
            graphics.drawImage(imfillImage,400,0, 380,400,null);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }



    }


    /*
     * 核心算法
     * 
     * 思路:  根据四连通启发到
     *  
     *  判断该像素上下左右四个方向,一定距离上是否都存在白色区域
     *  如果四个方向都存在,返回此处为白色
     */
    private int[][] imfillImage(int distance, int[][] imageColor) {
        // TODO Auto-generated method stub
        int[][] newImageColor;
        newImageColor = imageColor;
        int counter;
        for (int i = distance; i < imageColor.length - distance; i++) {
            for (int j = distance; j < imageColor[1].length - distance; j++) {
                counter = 0;            
                //上
                for (int k1 = 0; k1 < distance; k1++) {
                    if (imageColor[i-k1][j] > 0xffaa0000) 
                    {
                        counter++;
                        break;
                    }
                }

                //左
                for (int k1 = 0; k1 < distance; k1++) {
                    if (imageColor[i][j-k1] > 0xffaa0000) 
                    {
                        counter++;
                        break;
                    }
                }   
                //下
                for (int k1 = 0; k1 < distance; k1++) {
                    if (imageColor[i+k1][j] > 0xffaa0000) 
                    {
                        counter++;
                        break;
                    }
                }   
                //右
                for (int k1 = 0; k1 < distance; k1++) {
                    if (imageColor[i][j+k1] > 0xffaa0000) 
                    {
                        counter++;
                        break;
                    }
                }
                if (counter == 4)  newImageColor[i][j] = 0xffffffff;
            }//for (int j = 0; j < imageColor[1].length; j++)
        }//for (int i = 0; i < imageColor.length; i++)  
        return newImageColor;
    }
}

测试类:

package java二值图像填充;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class test extends JPanel{

    public static void main(String[] args) {

        JFrame mFrame = new JFrame();
        mFrame.setVisible(true);
        mFrame.setSize(800, 500);
        mFrame.add(new test());

    }
    @Override   
    public void paint(Graphics g) {
        // TODO Auto-generated method stub
        super.paint(g);     
        imfll mImfll = new imfll("color1.jpg", g);
    }
}

实现如下图:
将较小的虫洞填充
这里写图片描述
原创代码,欢迎指点

2017-10-04 17:14:21 qq_30311601 阅读数 9488

(这里的二值为0和255)

二值图像的孔洞填充是基于图像形态学操作的基本运算,本文参考数字图像处理(冈萨雷斯著)相关章节的有关内容并结合作者自己的思考,给出了基于C# 二值图像孔洞填充的可行程序。

基础知识:参考数字图像处理 P402-P415

数学形态学的语言是集合论,这里所说的孔洞是二值图像内部八连通点阵组成的闭合圈内的像素点集,孔洞填充的基本步骤如下:
1.确定二值图像像素[0,0]为初始种子点,这里认为[0,0]像素点为背景点,而非某个孔洞内部的点。

2.以种子点为起点,采用形态学膨胀算法对背景进行填充。膨胀到不能膨胀为止。膨胀运算采用四连通结构元。

 

 


四连通结构元(即中心像素为种子,以四连通的方式向周围膨胀):

 

 

3.背景填充结束后,对得到的二值图像取反得到新的二值图像,此时图像为全部孔洞的点集。

4.将第三步骤得到的二值图像与原二值图像相加及得到孔洞填充的结果。

 

这样的算法对于大多数图像有效,然而对于[0,0]位置的像素,若为某孔洞内部的点则无法实现有效的孔洞填充。为了解决这一问题,这里采用拓展图像的方法,即在原图像的上下左右分别增加一行或一列数值为255的像素。使原图像尺寸由a*b 变为(a+2)*(b+2),以增加的四周全部像素或[0,0]处的像素为种子,对原图像进行膨胀运算。

结果如下:(图中未被填充“孔洞”是由于边缘未闭合)

         





2015-05-17 22:22:17 w12345_ww 阅读数 1980

【漫水填充法】

首先,漫水填充在图像处理中是做什么的?

漫水填充,经常被用来标记或分离图像中的一部分,以便对其进行进一步的处理或者分析。漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点。

漫水填充的过程是怎样的?

漫水填充操作的结果总是在某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内事,cvFloodFill()函数将为这个点涂上颜色。可以选参数mask控制漫水法填充。OpenCV中给出了函数cvFloodFill(),下面简介下这个函数。

void cvFloodFill(
    IplImage*     img,//输入图像,8位或者浮点类型的单通道或三通道图
    CvPoint       seedPoint, //种子坐标
    CvScalar      newVal,//像素点被染色的值
    CvScalar      loDiff= cvScalarAll(0),//像素值的下限差值
    CvScalar      upDiff= cvScalarAll(0),//像素值的上限差值
    CvConnectedComp* comp=NULL,
    int           flags=4,//这个参数略复杂,下文介绍下

    //mask是掩码,既可以作为输入,也可以作为输出,若mask非空,
    //那么它必须为一个单通道、8位、源图像为width * height,
    //则掩码大小为 (width+2) * (height+2)
    CvArr*        mask=NULL 
)

关于flags参数,低八位可以被设置为4或者8,这个参数控制着填充算法的连通性,4表示在4个方向考虑连通性(上下左右), 8表示在8个方向考虑连通性(加上4个对角线方向),高八位可以设置CV_FLOODFILL_FIXED_RANGE,或者 CV_FLOODFILL_MASK_ONLY(如果设置只考虑填充MASK),flags的中间比特(8-15位)的值可以设置填充掩码的值 flags = 8 | CV_FLOODFILL_MASK_ONLY | CV_FLOODFILL_FIXED_RANGE | (43<<8)

下面给出漫水填充法函数应用的示例:

#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;

int main()
{
    IplImage* src = cvLoadImage("1.jpg", CV_LOAD_IMAGE_UNCHANGED);
    cvShowImage("source", src);
    IplImage* image1 = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
    IplImage* image2 = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);

    cvCopy(src, image1);
    cvCopy(src, image2);

    cvFloodFill(
        image1,
        cvPoint(54, 82),
        CV_RGB(255, 0, 0),
        cvScalar(20, 30, 40, 0),
        cvScalar(20, 30, 40, 0),
        NULL,
        4,
        NULL
        );
    cvShowImage("dst1", image1);

    cvFloodFill(
        image2,
        cvPoint(88,88),
        CV_RGB(255, 0, 0),
        cvScalar(20, 30, 40, 0),
        cvScalar(20, 30, 40, 0),
        NULL,
        4,
        NULL
        );
    cvShowImage("dst2", image2);

    cvWaitKey();
    cvReleaseImage(&src);
    cvReleaseImage(&image1);
    cvReleaseImage(&image2);
    cvDestroyAllWindows();

    return 0;

}

【尺寸调整】

首先,尺寸调整,顾名思义,就是将某种尺寸的图像转换为其他尺寸的图像。在OpenCV中有函数cvResize(),此函数可以将源图像精确转换为目标图像的尺寸。若源图像设置了ROI,那么函数会对ROI调整尺寸,以匹配目标图像。函数比较简单。

void cvResize(
    const  CvArr*  src,
    CvArr*         dst,
    int            interpolation=CV_INTER_LINEAR //插值方法
)

关于interpolation,有如下列表:

这里写图片描述

一般情况下,我们期望源图像和重采样后的目标图像之间的映射尽可能平滑,而参数 interpolation就是控制如何映射。当缩小图像时,目标图像的像素会映射为源图像中的多个像素,这时需要插值。当放大图像时,目标图像上的像素可能无法在源图像中找到精确对应的像素,也需要进行插值。

CV_INTER_NN: 将目标图像各点的像素值设为源图像中与其距离最近点的像素值
CV_INTER_LINEAR: 将根据图像附近的4个(2×2范围)邻近像素的线性加权计算得出,权重由4个像素到精确目标点的距离决定
CV_INTER_AREA: 新的像素点覆盖原来的像素点,然后求取覆盖区域的平均值
CV_INTER_CUBIC: 首先对源图像附近的4×4个邻近像素进行三次样条拟合,然后将目标图像对应的三次样条值作为目标图像对应像素点的值

2018-04-22 11:45:37 jkjj2015 阅读数 9686

      matlab中的imfill函数可以方便得实现二值图像的孔洞填充,而在opencv中并没有相同功能的函数。因此,在opencv的基础上编写实现孔洞填充的函数,并且能够设定阈值,对面积大于阈值的孔洞不进行填充。使用形态学重建的算法能够有效地实现孔洞填充,具体算法参照《数字图像处理》第三版9.5.9节,孔洞填充。

    主要实现代码如下所示:其中imfill函数即为空洞填充的实现函数,第一个参数是二值图像(0~1),第二个参数是填充孔洞的阈值。若孔洞面积大于阈值则不填充,反之则填充。

#include "iostream"
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
Mat inv_board(Mat src);
Mat inv_img(Mat src);
void delarea(Mat& bw, int max);
Mat imfill(Mat I, int max);
void main()
{
	Mat scr = imread("2.png");
	Mat I, src_gray, F_B, F_BI_C, temp, H, I_fill;
	cvtColor(scr, src_gray, COLOR_BGR2GRAY);
	threshold(src_gray, I, 0.1, 1,0);
	I_fill = imfill(I,40);
	imshow("原二值图", I * 255);
	imshow("填充图", I_fill*255);
	waitKey(0);
}


Mat imfill(Mat I,int max)
{
	Mat  src_gray, F_B, F_BI_C, temp, H, I_fill;
	I_fill = I.clone();
	Mat F = inv_board(I);
	Mat I_C = inv_img(I);
	Mat element = getStructuringElement(0, Size(3, 3), Point(1, 1));
	while (1)
	{


		dilate(F, F_B, element);
		F_BI_C = F_B.mul(I_C);
		temp = F_BI_C - F;
		if (sum(temp) == Scalar(0))
			break;
		else
			F = F_BI_C.clone();
	}
	H = inv_img(F_BI_C);
	Mat H_IC = H.mul(I_C);
	delarea(H_IC, max);
	for (int i = 0; i < H_IC.rows; i++)
	{
		for (int j = 0; j < H_IC.cols; j++)
		{
			if (H_IC.at<uchar>(i, j) == 1)
				I_fill.at<uchar>(i, j) = 1;
		}
	}
	return I_fill;
}
Mat inv_board(Mat src)
{
	
	int rows = src.rows;
	int cols = src.cols;
	Mat dst = Mat::zeros(rows, cols, CV_8UC1);
	for (int i = 0; i < cols; i++)
	{
		dst.at<uchar>(0, i) = 1 - src.at<uchar>(0, i);
	}
	for (int i = 0; i < cols; i++)
	{
		dst.at<uchar>(rows-1, i) = 1 - src.at<uchar>(rows - 1, i);
	}
	for (int i = 1; i < rows-1; i++)
	{
		dst.at<uchar>(i, 0) = 1 - src.at<uchar>(i, 0);
	}
	for (int i = 1; i < rows - 1; i++)
	{
		dst.at<uchar>(i, cols-1) = 1 - src.at<uchar>(i, cols-1);
	}
	return dst;
}


Mat inv_img(Mat src)
{
	int rows = src.rows;
	int cols = src.cols;
	Mat dst = src.clone();
	for (int i = 0; i < rows; i++)
		for (int j = 0; j < cols; j++)
			dst.at<uchar>(i, j) = 1 - src.at<uchar>(i, j);
	return dst;
}


void delarea(Mat& bw, int max )
{
	Mat bw_copy = bw.clone();
	int flag = 0; 
	Mat H_b, H_bw, temp;
	Mat H = Mat::zeros(bw.size(), bw.type());
	for (int i = 0; i < bw.rows; i++)
	{
		for (int j = 0; j < bw.cols; j++)
		{
			if (bw_copy.at<uchar>(i, j) == 1)
			{
				H.at<uchar>(i, j) = 1;
	Mat element = getStructuringElement(0, Size(3, 3), Point(1, 1));
	while (1)
	{
		dilate(H, H_b, element); 
		H_bw = H_b.mul(bw);
		temp = H_bw - H;
		if (sum(temp) == Scalar(0))
			break;
		else
			H = H_bw.clone();
	}
	bw_copy = bw_copy - H_bw;
    if (sum(H_bw).val[0] > max)
	{
		bw = bw - H_bw;
	}
	H = Mat::zeros(bw.size(), bw.type());
			}
			
		}
	}
}


2016-11-25 16:03:15 oHanTanYanYing 阅读数 4291

所谓图像的轮廓填充,是建立在图像的轮廓已然查找完成的情况下的,以下面图像为例:
这里写图片描述
我们首先需要查找到图像中的圆形和正方形的几个轮廓,之后才能对这些轮廓进行处理(查找的过程我们用到OpenCV的findContours函数)。
在得到轮廓之后,难点就转变为如何填充轮廓了,对于左上角的圆来说,直接填充即可,然而对于圆环和“田”字,则一般只希望填充两个轮廓直接的区域,中间的孔洞则保留,因此在对轮廓进行填充之前需要做进一步的判断工作(通过判断findContours函数的第三个参数对象实现),具体的实现代码如下:

#include "opencv2/highgui/highgui.hpp"  
#include "opencv2/imgproc/imgproc.hpp"  
#include "time.h" 
using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
    long time = clock();
    int r = 100;
    Mat src = Mat::zeros(Size(8 * r, 4 * r), CV_8UC1);
    //绘制轮廓,因为线段本身有粗细,则绘制一个圆会检测出两个轮廓,需要特别注意
    //绘制三个圆,其中一个嵌套着另一个
    circle(src, cvPoint(2 * r, 2 * r), 80, Scalar(255), 2);
    circle(src, cvPoint(2 * r, 2 * r), 50, Scalar(255), 2);
    circle(src, cvPoint(r, r), 30, Scalar(255), 2);
    //绘制一个田字
    rectangle(src, cvPoint(4 * r - r / 2, 2 * r - r / 2), cvPoint(4 * r + r / 2, 2 * r + r / 2), Scalar(255), 2);
    rectangle(src, cvPoint(4 * r - r * 2 / 5, 2 * r - r * 2 / 5), cvPoint(4 * r - r * 1 / 20, 2 * r - r * 1 / 20), Scalar(255), 2);
    rectangle(src, cvPoint(4 * r - r * 2 / 5, 2 * r + r * 1 / 20), cvPoint(4 * r - r * 1 / 20, 2 * r + r * 2 / 5), Scalar(255));
    rectangle(src, cvPoint(4 * r + r * 2 / 5, 2 * r - r * 2 / 5), cvPoint(4 * r + r * 1 / 20, 2 * r - r * 1 / 20), Scalar(255), 2);
    rectangle(src, cvPoint(4 * r + r * 2 / 5, 2 * r + r * 1 / 20), cvPoint(4 * r + r * 1 / 20, 2 * r + r * 2 / 5), Scalar(255), 2);

    Mat raw_dist1(src.size(), CV_32FC1);
    vector<vector<Point> > contours; vector<Vec4i> hierarchy;
    Mat src_copy = src.clone();
    findContours(src_copy, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE);//查找轮廓,并以树状图结果存储轮廓信息
    for (size_t i = 0; i < contours.size(); i++)
    {
        if (hierarchy[i][3] != -1)//表示其为某一个轮廓的内嵌轮廓
        {
            if (hierarchy[hierarchy[i][3]][3] == -1)//表示其为最外层轮廓,上面检测到的是其线条的内部
            {
                drawContours(raw_dist1, contours, i, Scalar(255), -1);
            }
            else
            {
                drawContours(raw_dist1, contours, i, Scalar(0), -1);
            }
        }
        else
        {
            drawContours(raw_dist1, contours, i, Scalar(0), -1);
        }
    }
    printf("花费时间%dms\n", clock() - time);
    char* source_window = "Source";
    namedWindow(source_window, CV_WINDOW_AUTOSIZE);
    imshow(source_window, src);
    namedWindow("Distance1", CV_WINDOW_AUTOSIZE);
    imshow("Distance1", raw_dist1);
    imwrite("轮廓查找图像.jpg", src);
    imwrite("轮廓查找完成图像.jpg", raw_dist1);
    waitKey(0);
    return(0);
}

通过上面的代码,我们把图像中的轮廓进行了白色的填充,结果如下图所示
这里写图片描述
到此,我们得到了一副轮廓填充完成的图像,其中的填充区域为白色,如果需要进一步处理,则可以通过连通域检测算法找到各个轮廓像素点的集合。
需要注意的是,上面的代码只支持到两层轮廓的嵌套(一般而言,对于CAD等软件出来的图形最多只会有两层),再多层的嵌套则需要修改代码中的判断部分。

图像内轮廓填充

阅读数 3935

图像的填充

阅读数 3811