使用纯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.*;/*** 颜色分割变换* rina 2021.11.14*/
public class colorSelect {//hsv绿色范围public static opencv_core.CvScalar g_min = cvScalar(35, 43, 46, 0); //HSV色域public static opencv_core.CvScalar g_max= cvScalar(77, 255, 220, 0); //HSV色域public static void main(String[] args) {//读入图片IplImage orgImg = cvLoadImage("/opt/project/ColorBbox-OpencvTest/test.png");//rgb->hsvIplImage 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色域中的绿色区域:
阈值化后,再经过膨胀腐蚀处理后的效果如下所示:
对颜色轮廓进行提取后,进行颜色区域的标注,标注Bbox效果如下所示,这里将绿色区域都框出来了:
注意cvScalar()
的参数为HSV色域值,而不是RBG或BGR。
HSV(Hue, Saturation, Value)
是根据颜色的直观特性由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(白色)。
对于基本色中对应的HSV分量需要给定一个严格的范围,下面是一些颜色的模糊范围:
H: 0— 180
S: 0— 255
V: 0— 255
(2)离焦处理
Google到了一个牛逼文档,里面图像处理的例子写的挺齐全的,可以作为参考:http://www.jhlabs.com/ip/blurring.html
另外离焦(高斯模糊)的实现原理参考了这篇大牛技术贴:https://www.cnblogs.com/invisible2/p/9177018.html
下面给出我离焦处理的工具类详细代码:
/*** 离焦 高斯模糊变换* rina 2021.11.24*/
public class GaussianBlurUtils {/*** 高斯模糊* 参考:https://www.cnblogs.com/invisible2/p/9177018.html* @param imagePath* @param radius 模糊半径* @param outPath* @return*/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);// 均值滤波使用的卷积模板半径,这里使用5*5均值,所以半径使用2int 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);}/*** 获取(2*r+1)*(2*r+1)图像(半径为r)的高斯权重* 以图像中心为坐标原点来计算所有点的权重* 离中心点越近,权重越大* @param r 半径* @return 图像所有点的权重*/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];}}// 让它们的权重之和等于1for (int i=0; i<n; i++) {for (int j=0; j<n; j++) {a[i][j] = a[i][j] / totalWeight;}}return a;}/*** 高斯函数,根据坐标计算每个点的权重,3*3图像的原点默认是中间的那个点* 参考:https://www.cnblogs.com/invisible2/p/9177018.html* @param x 横坐标* @param y 纵坐标* @return*/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;}/*** 如果像素点的值超过了0-255的范围,予以调整* @param value 输入值* @return 输出值*/private static int clamp(int value) {return value > 255 ? 255 : (Math.max(value, 0));}
}
radius
模糊半径可调,依旧是这张七巧板测试图像,离焦模糊效果如下:
(3)运动模糊处理
运动模糊这里实现了线性模糊,旋转模糊,以及含有畸变效果的尺度模糊。
下面给出运动模糊处理的工具类详细代码,定义变量为:
float centreX; //X方向系数float centreY; //Y方向系数float distance; //这里设置运动距离float angle; //运动方向(角度)float rotation; //旋转角度float zoom; //尺度
/*** 运动模糊变换* rina 2021.11.24*/
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);}
}
还是这张七巧板测试图像,当不调整运动方向角度时,运动模糊为水平运动模糊,其水平线性运动模糊的效果如下:
当调整运动方向角度时,运动模糊为沿着该角度方向的线性模糊,例如设置angle=10
,效果如下,可以发现模糊方向是朝着右上角10度方向倾斜模糊的:
当调整旋转角度时,其旋转模糊效果如下所示:
(4)灰度化处理
参考了一下这篇技术贴:
https://www.cnblogs.com/deng-c-q/p/8710252.html
图片灰度化方式这里分为四种:
(1)最大值法(取颜色RGB中的最大值作为灰度值)
(2)最小值法(取颜色RGB的最小值作为灰度值)
(3)均值法(取颜色的RGB的平均值作为灰度值)
(4)加权法
下面给出灰度化处理的工具类详细代码,如下所示:
/*** 图片灰度化变换* rina 2021.11.24*/
public class GrayTransUtils {/*** 颜色分量转换为RGB值* @param alpha* @param red* @param green* @param blue* @return*/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;}/*** 图片灰度化的方法* @param status 灰度化方法的种类,1表示最大值法,2表示最小值法,3表示均值法,4加权法* @param image 需要灰度化的图片* @throws IOException*/public static BufferedImage grayImageTrans(int status, BufferedImage image) throws IOException {//File file = new File(imagePath);//BufferedImage image = ImageIO.read(file);int width = image.getWidth();int height = image.getHeight();BufferedImage grayImage = new BufferedImage(width, height, image.getType());//BufferedImage grayImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);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);//加权法灰度化}//System.out.println("像素坐标:" + " x=" + i + " y=" + j + " 灰度值=" + gray);grayImage.setRGB(i, j, colorToRGB(0, gray, gray, gray));}}//File newFile = new File(outPath);//ImageIO.write(grayImage, "jpg", newFile);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(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 getAvg(int x,int y,int z){int avg=(x+y+z)/3;return avg;}
}
最后给出这四种灰度二值化处理的效果图,个人感觉第三种效果更好一些(依旧是这张七巧板测试图例,有一丢丢看吐了…):
每天都想午休但睡不着的乔木小姐
2021.11.25
使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理相关推荐
- 六、HSV颜色空间应用实例——颜色分割提取与替换
教程汇总:python基础入门系列 通过之前的章节(四.OpenCV颜色空间--HSV颜色模型),我们已经初步认识了HSV颜色空间的特性与优势,现在就来看两个典型的应用实例,颜色分割提取 与 颜色替换 ...
- matlab 图像 轮廓 填充颜色,基于Matlab的图形轮廓提取及填充
计算机工程应用技术 本栏目责任编辑: 贾薇薇 电脑知识与技术 基于 Matlab 的图形轮廓提取及填充 井艾斌,柳青,孟祥增 (山东师范大学, 山东 济南 250014) 摘要: 提取图形的形状特征是 ...
- Python OpenCV GrabCut进行前景分割和提取
Python OpenCV GrabCut进行前景分割和提取 1. 效果图 1.1 边界框GrabCut效果图 1.2 Mask GrabCut效果图 2. GrabCut原理 2.1 GrabCut ...
- Python OpenCV分水岭算法分割和提取重叠或有衔接的图像中的对象
本文将介绍如何使用分水岭算法对触摸和重叠的图像中的对象进行分割和提取. 参考:https://www.pyimagesearch.com/2015/11/02/watershed-opencv/ 分水 ...
- 基于深度学习的图像边缘和轮廓提取
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 导读:边缘和轮廓的提取是一个非常棘手的工作,细节也许就会被过强的图 ...
- python 黑白tif提取边界像素坐标_OpenCV GrabCut算法:前景分割和提取
点击上方"蓝色小字"关注我呀 文章翻译自光头哥哥的博客,原文链接: https://www.pyimagesearch.com/2020/07/27/opencv-grabcut- ...
- python opencv轮廓提取_Python + Opencv2 实现轮廓提取,轮廓区域面积计算
对图像处理时,会遇到这样一个场景:找到图像主体轮廓,这是其一,可能为了凸显轮廓,需要用指定的颜色进行标记:轮廓标记完可能任务还没有结束,还需对轮廓所勾勒的像素面积区域统计计算. 本篇文章的主要内容就是 ...
- 【OpenCV函数】轮廓提取;轮廓绘制;轮廓面积;外接矩形
FindContours 在二值图像中寻找轮廓 int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_cont ...
- python cv2 轮廓的包络 面积_Python + Opencv2 实现轮廓提取,轮廓区域面积计算
对图像处理时,会遇到这样一个场景:找到图像主体轮廓,这是其一,可能为了凸显轮廓,需要用指定的颜色进行标记:轮廓标记完可能任务还没有结束,还需对轮廓所勾勒的像素面积区域统计计算. 本篇文章的主要内容就是 ...
最新文章
- 边缘检测、霍夫变换、重映射、仿射变换、直方图均衡化
- 清华唐杰教授:人工智能的下一个十年,认知推理是重点(附PPT下载)
- mysql延时优化教程_Slave延迟很大的优化方法总结(MySQL优化)
- 使用ffmpeg+nginx将rtmp直播流转为hls直播流
- IDT系列:(二)中断处理过程,使用bochs调试IDT中的中断服务程序
- jzoj2940-生成输入数据【最小生成树,并查集】
- 服务器的虚拟化配置,怎么配置服务器的虚拟化环境
- 手机也能当电脑用?--谈谈未来智能手机操作系统的走向
- 多态——面向接口编程
- 深挖 AI 价值与温度,AETA 地震预测 AI 算法大赛开启
- 了解Spring AOP吗
- ASP.NET MVC+EF框架+EasyUI实现权限管理系列(19)-用户信息的修改和浏览
- 常见shader原理及实现(三)边缘查找,锐化,浮雕
- Linux镜像文件提取,如何提取linux 系统镜像文件
- VC 轻松实现非客户区按钮
- 学java,报班还是自学?
- 808计算机基础考试时间,成都信息工程大学808计算机基础2020年考研大纲
- win7 设定固定的ip地址
- 下载 | 5 本程序员成长必读书籍
- Edge导航网址变成了毒霸的,如何改动?
热门文章
- SAP客户主数据相关表
- 爱企查青龙可跑 一个月一张爱奇艺月卡最细教程
- html使元素不被内容撑开,flex布局被子元素撑开,如何保持内容不超出容器
- 爬去东方财富网龙虎榜(wechat:15353378609)
- ie浏览器通过ActiveX控件获取本机macip(附带批处理一键开启)
- UP主分析丨B站“肝视频”第一人,追剧的速度都跟不上他。
- 24. 两两交换链表中的节点-两两反转链表-LeetCode
- JC-6、OpenCV+Tensorflow入门人工智能图像处理
- 人物志-丘吉尔 Success consists of going from failure to failure without loss of enthusiasm. —— Winston Chu
- Excel如何统计数据个数