使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理

年底冲KPI很多项目要结项,临时被拉去开发了一些”很有意思”的项目,感觉自从离开学校以后很长时间都没有再接触过图像处理领域了,有点跟熟悉的陌生人打交道的快感。这里简单记录一下相关代码实现,便于后续归档与复现。

使用纯JavaCV实现图像处理

*

+
* (1)轮廓提取与颜色分割处理
* (2)离焦处理
* (3)运动模糊处理
* (4)灰度化处理

写在前面:
之前读研的时候在学校偶尔做图像预处理,都是使用的OpenCV图像库,用Python或C++语言进行图像处理编程的时候都很方便。但Java Web的服务端在银河麒麟或CentOS等Linux系统下部署时,该依赖库着实有些水土不服,每次都需要重新编译动态链接库不说,光是离线编译OpenCV库本身就费老劲了。无奈只能撸起袖子用Java重写相关的计算库与方法,想着可否跳过OpenCV封装好的函数,采用原生的写法。

(1)轮廓提取与颜色分割处理

我使用的是 org.bytedeco.javacv依赖库,不需要编译安装opencv,在java环境下会更为便捷, pom.xml中引入如下依赖:

<dependency>
    <groupid>org.bytedeco</groupid>
    <artifactid>javacv</artifactid>
    <version>1.4.1</version>
</dependency>

纯javacv代码如下,需要注意Javacv的语法与Opencv区别还挺大的,有很多接口不再适配。

import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacpp.opencv_core;
import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_highgui.cvShowImage;
import static org.bytedeco.javacpp.opencv_highgui.cvWaitKey;
import static org.bytedeco.javacpp.opencv_imgcodecs.cvLoadImage;
import static org.bytedeco.javacpp.opencv_imgproc.*;

public class colorSelect {

    public static opencv_core.CvScalar g_min = cvScalar(35, 43, 46, 0);
    public static opencv_core.CvScalar g_max= cvScalar(77, 255, 220, 0);

    public static void main(String[] args) {

        IplImage orgImg = cvLoadImage("/opt/project/ColorBbox-OpencvTest/test.png");

        IplImage hsv = IplImage.create( orgImg.width(), orgImg.height(), orgImg.depth(), orgImg.nChannels() );
        cvCvtColor( orgImg, hsv, CV_BGR2HSV );
        IplImage imgThreshold = cvCreateImage(cvGetSize(orgImg), 8, 1);

        cvInRangeS(hsv, g_min, g_max, imgThreshold);

        IplImage Morphology_result  = IplImage.create(orgImg.width(),orgImg.height(), IPL_DEPTH_8U, 1);
        IplConvKernel kernelCross = cvCreateStructuringElementEx(21, 21,7,7, CV_SHAPE_RECT);
        cvMorphologyEx(imgThreshold, Morphology_result, Morphology_result, kernelCross, MORPH_CLOSE, 1);

        IplImage erosion_dst  = IplImage.create(orgImg.width(),orgImg.height(), IPL_DEPTH_8U, 1);
        IplImage dilate_dst  = IplImage.create(orgImg.width(),orgImg.height(), IPL_DEPTH_8U, 1);
        IplConvKernel kernel=cvCreateStructuringElementEx(3,3,1,1,CV_SHAPE_RECT);
        cvErode( Morphology_result, erosion_dst, kernel,3);
        cvDilate( erosion_dst, dilate_dst, kernel,4);

        CvMemStorage mem = CvMemStorage.create();
        CvSeq contours = new CvSeq();
        CvSeq ptr = new CvSeq();
        cvFindContours(dilate_dst, mem, contours, Loader.sizeof(CvContour.class) , CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
        CvRect boundingBox;
        int index = 1;
        for (ptr = contours; ptr != null; ptr = ptr.h_next()) {
            boundingBox = cvBoundingRect(ptr, 0);
            cvRectangle( orgImg , cvPoint( boundingBox.x(), boundingBox.y() ),
                    cvPoint( boundingBox.x() + boundingBox.width(), boundingBox.y() + boundingBox.height()),
                    cvScalar( 0, 255, 255, 255 ), 2, 0, 0 );
            System.out.println("boundingBox_index" + index + ".x     :     " + boundingBox.x());
            System.out.println("boundingBox_index" + index + ".y     :     " + boundingBox.y());
            System.out.println("boundingBox_index" + index + ".width     :     " + boundingBox.width());
            System.out.println("boundingBox_index" + index + ".height     :     " + boundingBox.height());
            index++;
        }
        cvShowImage( "Contours", orgImg );
        cvWaitKey(0);
    }
}

使用七巧板图像进行测试,测试图像如下,这里选择的是HSV色域中的绿色区域:

使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理
阈值化后,再经过膨胀腐蚀处理后的效果如下所示:
使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理
对颜色轮廓进行提取后,进行颜色区域的标注,标注Bbox效果如下所示,这里将绿色区域都框出来了:
使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理
注意 cvScalar()的参数为HSV色域值,而不是RBG或BGR。

HSV&#xFF08;Hue, Saturation, Value&#xFF09;是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。这个模型中颜色的参数分别是:色调(H),饱和度(S),亮度(V)。

色调H:用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°;
饱和度S:取值范围为0.0~1.0;
亮度V:取值范围为0.0(黑色)~1.0(白色)。

使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理

对于基本色中对应的HSV分量需要给定一个严格的范围,下面是一些颜色的模糊范围:
H: 0— 180
S: 0— 255
V: 0— 255

使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理

(2)离焦处理

Google到了一个牛逼文档,里面图像处理的例子写的挺齐全的,可以作为参考:http://www.jhlabs.com/ip/blurring.html
另外离焦(高斯模糊)的实现原理参考了这篇大牛技术贴:https://www.cnblogs.com/invisible2/p/9177018.html

下面给出我离焦处理的工具类详细代码:


public class GaussianBlurUtils {

    public static void gaussianBlurTrans(String imagePath, int radius, String outPath) throws IOException {
        File file = new File(imagePath);
        BufferedImage src = ImageIO.read(file);
        int width = src.getWidth();
        int height = src.getHeight();
        int[] pixels = new int[width * height];
        int[] outPixels = new int[width * height];
        src.getRGB(0, 0, width, height, pixels, 0, width);

        int r = 0, g = 0, b = 0;

        double[][] weights = getGaussianWeight(radius);

        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                int rSum = 0;
                int gSum = 0;
                int bSum = 0;
                for (int i = -radius; i  radius; i++) {
                    int roffset = row + i;
                    roffset = (roffset < 0) ? 0 : (roffset >= height ? height - 1 : roffset);
                    for (int j = -radius; j  radius; j++) {
                        int coffset = col + j;
                        coffset = (coffset < 0) ? 0 : (coffset >= width ? width - 1 : coffset);
                        int pixel = pixels[roffset * width + coffset];
                        r = (pixel >> 16) & 0XFF;
                        g = (pixel >> 8) & 0xff;
                        b = pixel & 0xff;
                        rSum += r * weights[i + radius][j + radius];
                        gSum += g * weights[i + radius][j + radius];
                        bSum += b * weights[i + radius][j + radius];
                    }
                }
                r = rSum;
                g = gSum;
                b = bSum;
                outPixels[row * width + col] = (255 << 24) | (clamp(r) << 16) | (clamp(g) << 8) | clamp(b);
            }
        }

        BufferedImage dest = new BufferedImage(width, height, src.getType());
        dest.setRGB(0, 0, width, height, outPixels, 0, width);
        File newFile = new File(outPath);
        ImageIO.write(dest, "jpg", newFile);
    }

    public static double[][] getGaussianWeight(int r) {
        int n = 2*r+1;
        double[][] a = new double[n][n];
        double totalWeight = 0;

        for (int i=0; i<n; i++) {
            for (int j=0; j<n; j++) {
                int x = j - r;
                int y = i - r;
                a[i][j] = getGaussianFunction(x, y);
                totalWeight += a[i][j];
            }
        }

        for (int i=0; i<n; i++) {
            for (int j=0; j<n; j++) {
                a[i][j] = a[i][j] / totalWeight;
            }
        }
        return a;
    }

    public static double getGaussianFunction(int x, int y) {
        double a = 1.5;
        double b = -(x*x + y*y)/(2*a*a);
        double c = Math.exp(b)/(2*Math.PI*a*a);
        return c;
    }

    private static int clamp(int value) {
        return value > 255 ? 255 : (Math.max(value, 0));
    }
}

radius 模糊半径可调,依旧是这张七巧板测试图像,离焦模糊效果如下:

使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理

(3)运动模糊处理

运动模糊这里实现了线性模糊,旋转模糊,以及含有畸变效果的尺度模糊。
下面给出运动模糊处理的工具类详细代码,定义变量为:

        float centreX;     //X&#x65B9;&#x5411;&#x7CFB;&#x6570;
        float centreY;     //Y&#x65B9;&#x5411;&#x7CFB;&#x6570;
        float distance;    //&#x8FD9;&#x91CC;&#x8BBE;&#x7F6E;&#x8FD0;&#x52A8;&#x8DDD;&#x79BB;
        float angle;       //&#x8FD0;&#x52A8;&#x65B9;&#x5411;(&#x89D2;&#x5EA6;)
        float rotation;    //&#x65CB;&#x8F6C;&#x89D2;&#x5EA6;
        float zoom;        //&#x5C3A;&#x5EA6;

public class MotionBlurUtils {

    private static int log2( int n ) {
        int m = 1;
        int log2n = 0;
        while (m < n) {
            m *= 2;
            log2n++;
        }
        return log2n;
    }

    public static void MotionBlurTrans(String imagePath, String outPath, float centreX, float centreY,float distance, float angle, float rotation, float zoom) throws IOException {

        File file = new File(imagePath);
        BufferedImage ImageBuffer = ImageIO.read(file);
        BufferedImage ResultImageBuffer = ImageBuffer;
        float cx = (float) ImageBuffer.getWidth() * centreX;
        float cy = (float) ImageBuffer.getHeight() * centreY;
        float imageRadius = (float) Math.sqrt( cx*cx + cy*cy );
        float translateX = (float) (distance * Math.cos( angle ));
        float translateY = (float) (distance * -Math.sin( angle ));
        float scale = zoom;
        float rotate = rotation;
        float maxDistance = distance + Math.abs(rotation*imageRadius) + zoom*imageRadius;
        int steps = log2((int)maxDistance);

        translateX /= maxDistance;
        translateY /= maxDistance;
        scale /= maxDistance;
        rotate /= maxDistance;

        if ( steps == 0 ) {
            File newFile = new File(outPath);
            ImageIO.write(ResultImageBuffer, "jpg", newFile);
            return;
        }

        BufferedImage tmp = new BufferedImage(ImageBuffer.getWidth(), ImageBuffer.getHeight(), ImageBuffer.getType());
        for ( int i = 0; i < steps; i++ ) {
            Graphics2D g = tmp.createGraphics();
            g.drawImage( ResultImageBuffer, null, null );
            g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
            g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR );
            g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.5f ) );

            g.translate( cx+translateX, cy+translateY );
            g.scale( 1.0001+scale, 1.0001+scale );
            if ( rotation != 0 )
                g.rotate( rotate );
            g.translate( -cx, -cy );
            g.drawImage( tmp, null, null );
            g.dispose();
            ResultImageBuffer = tmp;

            translateX *= 2;
            translateY *= 2;
            scale *= 2;
            rotate *= 2;
        }
        File newFile = new File(outPath);
        ImageIO.write(ResultImageBuffer, "jpg", newFile);
    }
}

还是这张七巧板测试图像,当不调整运动方向角度时,运动模糊为水平运动模糊,其水平线性运动模糊的效果如下:

使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理
当调整运动方向角度时,运动模糊为沿着该角度方向的线性模糊,例如设置 angle=10,效果如下,可以发现模糊方向是朝着右上角10度方向倾斜模糊的:
使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理
当调整旋转角度时,其旋转模糊效果如下所示:
使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理

(4)灰度化处理

参考了一下这篇技术贴:
https://www.cnblogs.com/deng-c-q/p/8710252.html

图片灰度化方式这里分为四种:
(1)最大值法(取颜色RGB中的最大值作为灰度值)
(2)最小值法(取颜色RGB的最小值作为灰度值)
(3)均值法(取颜色的RGB的平均值作为灰度值)
(4)加权法

下面给出灰度化处理的工具类详细代码,如下所示:


public class GrayTransUtils {

    private static int colorToRGB(int alpha, int red, int green, int blue) {
        int newPixel = 0;
        newPixel += alpha;
        newPixel = newPixel << 8;
        newPixel += red;
        newPixel = newPixel << 8;
        newPixel += green;
        newPixel = newPixel << 8;
        newPixel += blue;

        return newPixel;
    }

    public static BufferedImage grayImageTrans(int status, BufferedImage image) throws IOException {

        int width = image.getWidth();
        int height = image.getHeight();

        BufferedImage grayImage = new BufferedImage(width, height,  image.getType());

        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                int color = image.getRGB(i, j);
                final int r = (color >> 16) & 0xff;
                final int g = (color >> 8) & 0xff;
                final int b = color & 0xff;
                int gray = 0;
                if(status == 1){
                    gray = getBigger(r, g, b);
                }else if(status==2){
                    gray = getSmall(r, g, b);
                }else if(status==3){
                    gray = getAvg(r, g, b);
                }else if(status==4){
                    gray = (int) (0.3 * r + 0.59 * g + 0.11 * b);
                }

                grayImage.setRGB(i, j, colorToRGB(0, gray, gray, gray));
            }
        }

        return grayImage;
    }

    public static int getBigger(int x,int y,int z){
        if(x>=y&&x>=z){
            return x;
        }else if(y>=x&&y>=z){
            return y;
        }else if(z>=x&&z>=y){
            return z;
        }else{
            return 0;
        }
    }

    public static int getSmall(int x,int y,int z){
        if(xy&&xz){
            return x;
        }else if(y>=x&&y>=z){
            return y;
        }else if(z>=x&&z>=y){
            return z;
        }else{
            return 0;
        }
    }

    public static int getAvg(int x,int y,int z){
        int avg=(x+y+z)/3;
        return avg;
    }
}

最后给出这四种灰度二值化处理的效果图,个人感觉第三种效果更好一些(依旧是这张七巧板测试图例,有一丢丢看吐了…):

使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理
使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理
使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理
使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理

每天都想午休但睡不着的乔木小姐
2021.11.25

Original: https://blog.csdn.net/nannan7777/article/details/121305779
Author: 码代码的乔木
Title: 使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/644753/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球