年底冲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色域中的绿色区域:
阈值化后,再经过膨胀腐蚀处理后的效果如下所示:
对颜色轮廓进行提取后,进行颜色区域的标注,标注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
下面给出我离焦处理的工具类详细代码:
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
模糊半径可调,依旧是这张七巧板测试图像,离焦模糊效果如下:
(3)运动模糊处理
运动模糊这里实现了线性模糊,旋转模糊,以及含有畸变效果的尺度模糊。
下面给出运动模糊处理的工具类详细代码,定义变量为:
float centreX; //X方向系数
float centreY; //Y方向系数
float distance; //这里设置运动距离
float angle; //运动方向(角度)
float rotation; //旋转角度
float zoom; //尺度
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)灰度化处理
图片灰度化方式这里分为四种:
(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;
}
}
最后给出这四种灰度二值化处理的效果图,个人感觉第三种效果更好一些(依旧是这张七巧板测试图例,有一丢丢看吐了…):
每天都想午休但睡不着的乔木小姐
2021.11.25
Original: https://blog.csdn.net/nannan7777/article/details/121305779
Author: 码代码的乔木
Title: 使用纯JavaCV实现颜色分割 / 轮廓提取 / 离焦 / 线性旋转变焦模糊 / 灰度化 / 标注等处理
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/644753/
转载文章受原作者版权保护。转载请注明原作者出处!