OpenCV——图像基本操作(一)

图像基本操作

一、图像读写与显示

1.1、图像的读取

java 复制代码
Mat Imgcodecs.imread(String filename, int flags)
  • filename:指定的文件名
  • flags:读取方式,如果此参数省略,则按默认方式读取图像。

读取方式取值表

参数 数字值 用法
Imgcodecs.IMREAD_UNCHANGED -1 按图像原样读取,保留Aplha通道
Imgcodecs.IMREAD_GRAYSCALE 0 读取图像并转换成单通道灰度图像
Imgcodecs.IMREAD_COLOR 1 读取图像并转换成三通道图像
Imgcodecs.IMREAD_ANYDEPTH 2 读取图像,如图像为16位/32位则保留,否则转为8位图像
Imgcodecs.IMREAD_ANYCOLOR 4 以任何可能的颜色格式读取图像
Imgcodecs.IMREAD_LOAD_GDAL 8 使用gdal驱动加载图像
Imgcodecs.IMREAD_REDUCED_GRAYSCALE_2 16 读取图像并转换成单通道灰度图像,图像尺寸变为原来的1/2
Imgcodecs.IMREAD_REDUCED_COLOR_2 17 读取图像并转换为三通道图像,图像尺寸变为原来的1/2
Imgcodecs.IMREAD_REDUCED_GRAYSCALE_4 32 读取图像并转换成单通道灰度图像,图像存储变为原来的1/4
Imgcodecs.IMREAD_REDUCED_COLOR_4 33 读取图像并转换为三通道图像,图像尺寸变为原来的1/4
Imgcodecs.IMREAD_REDUCED_GRAYSCALE_8 64 读取图像并转换为单通道灰度图像,图像变为原来的1/8
Imgcodecs.IMREAD_REDUCED_COLOR_8 65 读取图像并转换成三通道图像,图像尺寸变为原来的1/8
Imgcodecs.IMREAD_IGNORE_ORIENTATION 128 读取图像,不按EXIF方向标志旋转图像

读取图像文件时注意事项如下:

  1. 如果因为某些原因(文件不存在、没有权限、文件格式不被支持或无效等)无法读取图像,则返回一个空矩阵
  2. 由于Java中反斜杠\用作转义符,因为绝对路径中的分隔符不能用\,而要用\\/
  3. OpenCV支持TIFF、PNG、JPEG、BMP等常用文件格式,但不支持GIF文件格式
  4. 该函数根据文件内容而不是文件扩展名来确定图像的类型
  5. 如读取的文件是彩色图像,解码后的图像通道按B、G、R顺序存储
  6. 如果flags为IMREAD_GRAYSCALE,则灰度转换的结果可能与cvtColor()函数的输出不同
  7. 默认情况下,图像像素必须小于2^30。如有必要,可通过修改系统变量OPENCV_IO_MAX_IMAGE_PIXELS设置最大像素

1.2、图像的保存

java 复制代码
boolean Imgcodecs.imwrite(String filename, Mat img)
  • filename:指定的文件名
  • img:要保存的图像

保存图像时的格式依据filename的扩展名确定,此函数通常仅支持单通道或三通道(按照B、G、R顺序)图像的保存,但有一下例外:

  1. 16位无符号整数类型(CV_16U)的图像可以保存为PNG、JPEG2000或TIFF格式
  2. 32位浮点数类型(CV_32F)的图像可保存为TIFF、OpenEXR及HDR格式
  3. 有Alpha通道的PNG图像也可以用此函数保存。保存时需要先创建8位/16位四通道图像BGRA,其中Alpha通道排在最后:完全透明像素的Alpha值设为0,完全不透明的像素值设为255或65535
  4. 要在一个文件中保存多个图像,可采用TIFF文件格式
  5. 如果乳香格式不被支持,则图像将被转换为8位无符号整数类型(CV_8U)存储

1.3、图像的显示

java 复制代码
void HighGui.imshow(String winname, Mat img)
  • winname:显示图像的窗口名称
  • img:要显示的图像

在调用imshow()函数后,需要通过waitKey()函数告知系统图像在屏幕上停留的时间,如果不用这个函数,则屏幕上不会显示图像。

java 复制代码
int HighGui.waitKey(int delay)
  • delay:需要等待的时间,单位为毫秒ms。如将delay设为0,表示等待用户按键结束此函数
  • 返回值:如果某按键被按下,则返回键值对应的ASCII码,否则返回-1

此函数只存在HighGUI窗口时才有效。如果同时存在多个窗口,则其中任意一个均可被激活。

1.4、示例

java 复制代码
public class ReadFile {

    static {
        OpenCV.loadLocally(); // 自动下载并加载本地库
    }

    public static void main(String[] args) {
        //读取图像文件
        Mat fish = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/fish.png");

        //读取图像文件并转换为灰度图
        Mat grey = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/fish.png", Imgcodecs.IMREAD_GRAYSCALE);

        //将灰度图保存为图像文件
        Imgcodecs.imwrite("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/fish_grey.png", grey);

        //在屏幕上显示彩色图像和灰度图
        HighGui.imshow("color", fish);
        HighGui.waitKey(0);//任意键退出
        HighGui.imshow("grey", grey);
        HighGui.waitKey(0);//任意键退出
        System.exit(0);



    }
}

二、绘制函数

2.1、绘制直线

java 复制代码
void Imgproc.line(Mat img, Point pt1, Point pt2, Scalar color, int thickness)
  • img:用于绘图的图像
  • pt1:线段的端点1
  • pt2:线段的端点2
  • color:线段的颜色
  • thickness:线的粗细

2.2、绘制矩形

java 复制代码
void Imgproc.rectangle(Mat img, Point pt1, Point pt2, Scalar color, int thickness)
  • img:用于绘图的图像
  • pt1:矩形两个对角点之一
  • pt2:pt1的对角点
  • color:矩形的颜色
  • thickness:轮廓线的粗细。负值表示画一个实心矩形

2.3、绘制圆形

java 复制代码
void Imgproc.circle(Mat img, Point center, int radius, Scalar color, int thickness)
  • img:用于绘图的图像
  • center:圆的圆心
  • radius:圆的半径
  • color:圆的颜色
  • thickness:轮廓线的粗细。负值表示画一个实心圆

示例:

java 复制代码
public class Draw {

    static {
        OpenCV.loadLocally(); // 自动下载并加载本地库
    }

    public static void main(String[] args) {
        //用于画图的背景图,尺寸为800*800
        Mat img = Mat.zeros(800, 800, CvType.CV_8UC3);
        Scalar white = new Scalar(255, 255, 255);
        //格子大小
        int size = 40;
        //画围棋棋盘格子
        for (int i = 0; i < 19; i++) {
            Imgproc.line(img, new Point(40, 40 + size * i), new Point(760, 40 + size * i), white, 1);
            Imgproc.line(img, new Point(40 + size * i, 40), new Point( 40 + size * i, 760), white, 1);
        }
        //画棋盘的外框
        Imgproc.rectangle(img, new Point(30, 30), new Point(770, 770), white, 2);
        //画星位
        for (int i = 3; i < 19; i = i + 6) {
            for (int j = 3; j < 19; j = j + 6) {
                Imgproc.circle(img, new Point(40 + size * i, 40 + size * j), 4, white, 1);
            }
        }
        //在屏幕显示棋盘
        HighGui.imshow("img", img);
        HighGui.waitKey(0);
        System.exit(0);
    }
}

2.4、绘制椭圆

在图像上绘制一个椭圆或椭圆的一部分。如希望绘制一个完整的椭圆,则需将startAngle设为0,将endAngle设为360.

java 复制代码
void Imgproc.ellipse(Mat img, Point center, Size axes, double angle, double startAngle, double endAngle, Scalar color, int thickness)
  • img:用于绘图的图像
  • center:椭圆的中心
  • axes:椭圆主轴尺寸的一般
  • angle:椭圆旋转的角度,单位为读
  • startAngle:椭圆圆弧起始角度,单位为度
  • endAngle:椭圆圆弧终止角度,单位为度
  • color:椭圆的颜色
  • thickness:轮廓线的粗细。证书表示椭圆的轮廓,否则画一个实心椭圆

2.5、绘制多边形

java 复制代码
void Imgproc.polylines(Mat img, List<MatOfPoint> pts, boolean isClosed, Scalar color, int thickness)
  • img:用户绘图的图像
  • pts:多边形的多个顶点
  • isClosed:多边形是否闭合
  • color:多边形的颜色
  • thickness:线的粗细

2.6、绘制文字

java 复制代码
void Imgproc.putText(Mat img, String text, Point org, int fontFace, double fontScale, Scalar color)
  • img:用于绘图的图像
  • text:需要画出的问题,只支持英文
  • org:文字左下角位置
  • fontFace:字体类型,可选参数如下
    • Core.FONT_HERSHEY_SIMPLEX:正常尺寸的无衬线字体
    • Core.FONT_HERSHEY_PLAIN:小尺寸的无衬线字体
    • Core.FONT_HERSHEY_DUPLEX:正常尺寸的较复杂的无衬线字体
    • Core.FONT_HERSHEY_COMPLEX:正常尺寸的衬线字体
    • Core.FONT_HERSHEY_TRIPLEX:正常尺寸的较复杂的衬线字体
    • Core.FONT_HERSHEY_COMPLEX_SMALL:较小版的衬线字体
    • Core.FONT_HERSHEY_SCRIPT_SIMPLEX:手写字体
    • Core.FONT_HERSHEY_SCRIPT_COMPLEX:更复杂的手写字体
    • Core.FONT_ITALIC:斜体字体
  • fontScale:文字大小
  • color:文字颜色

注意:putText()函数的fontFace参数在3.4.16版本中属于Core模块。但在4.6.0版本中被调整到了Imgproc模块中,因此在4.6.0版本中应使用Imgproc.FONT_HERSHEY_SIMPLEX这种形式。

java 复制代码
public class Draw2 {

    static {
        OpenCV.loadLocally(); // 自动下载并加载本地库
    }

    public static void main(String[] args) {
        //用于绘制图形的背景图
        Mat fish = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/fish.png");
        Scalar black = new Scalar(0, 0, 0);
        Scalar white = new Scalar(255, 255, 255);
        //多边形的点集
        Point[] pt1 = new Point[5];
        pt1[0] = new Point(440, 540);
        pt1[1] = new Point(570, 470);
        pt1[2] = new Point(810, 420);
        pt1[3] = new Point(640, 540);
        pt1[4] = new Point(450, 580);
        //绘制鱼的轮廓
        MatOfPoint p = new MatOfPoint(pt1);
        List<MatOfPoint> pts = new ArrayList<>();
        pts.add(p);
        Imgproc.polylines(fish, pts, true, white, 2);
        //绘制叶子轮廓
        Imgproc.ellipse(fish, new Point(525, 130), new Size(110, 130), 95, 0.0, 360.0, white, 2);
        //在图像上绘制文字
        Imgproc.putText(fish, "Fish!", new Point(560, 520), Imgproc.FONT_HERSHEY_SIMPLEX, 1, black, 2);
        //在屏幕上显示
        HighGui.imshow("fish", fish);
        HighGui.waitKey(0);
        System.exit(0);
    }
}

2.7、绘制箭头

java 复制代码
void Imgproc.arrowedLine(Mat img, Point pt1, Point pt2, Scalar color, int thickness, int line_type, int shift, double tipLength)
  • img:用于绘图的图像
  • pt1:箭头起点
  • pt2:箭头终点
  • color:箭头的颜色
  • thickness:线的厚度
  • line_type:线型,可选参数如下:
    • Core.FILLED:填充线
    • Core.LINE_4:4联通线
    • Core.LINE_8:8联通线
    • Core.LINE_AA:抗锯齿线
  • shift:坐标系中的小数位数
  • tipLength:箭头尖端长度(相对于线段长度的比例)

2.8、绘制外框

java 复制代码
void Core.copyMakeBorder(Mat src, Mat dst, int top, int bottom, int left, int right, int borderType)
  • src:输入图像
  • dst:输出图像,和src具有相同的尺寸和数据类型
  • top:顶部边框的像素数
  • bottom:底部边框的像素数
  • left:左侧边框的像素数
  • right:右侧边框的像素数
  • borderType:边框类型,可选参数如下:
    • Core.BORDER_CONSTANT:用特定值填充,如iiiiii|abcdefgh|iiiiii中用i填充
    • Core.BORDER_REPLICATE:两端复制填充,如aaaaaa|abcdefgh|hhhhhhh
    • Core.BORDER_REFLECT:倒序填充,如fedcba|abcdefgh|hgfedcb
    • Core.BORDER_WRAP:正序填充,如cdefgh|abcdefgh|abcdefg
    • Core.BORDER_REFLECT_101:不含边界值的倒序填充,如gredcb|abcdefgh|gtedcba
    • Core.BORDER_REFLECT101:同Core.BORDER_REFLECT_101
    • Core.BORDER_DEFAULT:同Core.BORDER_REFLECT_101
java 复制代码
public class Draw3 {
    static {
        OpenCV.loadLocally(); // 自动下载并加载本地库
    }

    public static void main(String[] args) {
        //用于绘制图形的背景图
        Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/fish.png");
        //绘制边界框
        Mat dst = new Mat();
        Core.copyMakeBorder(src, dst, 9, 9, 9, 9, Core.BORDER_CONSTANT);
        //绘制箭头
        Point pt1 = new Point(760, 450);
        Point pt2 = new Point(465, 570);
        Scalar red = new Scalar(0, 0, 255);
        Imgproc.arrowedLine(dst, pt1, pt2, red, 3, Imgproc.LINE_AA, 0, 0.1);
        //在屏幕上显示绘制好的图像
        HighGui.imshow("dst", dst);
        HighGui.waitKey(0);
        System.exit(0);
    }
}

三、颜色空间操作

3.1、颜色空间的转换

java 复制代码
void Imgproc.cvtColor(Mat src, Mat dst, int code);
//将输入图像从一种颜色空间转换为另一种颜色空间。如果是在RGB颜色空间与其他颜色空间之间进行转换,则应明确指定通道的顺序(RGB或BGR)。由于OpenCV中默认颜色格式实际上是BGR。标准24位彩色图像中的第1~第3字节将是第1像素的8位蓝色、绿色、红色分量,第4~第6字节将是第2像素的蓝色、绿色、红色分量,以此类推。
  • src:输入图像
  • dst:输出图像,与src具有相同的尺寸和深度
  • code:颜色空间转换编码。常见的转换编码如下:
    • Imgproc.COLOR_BGR2GRAY:从BGR转换为灰度图
    • Imgproc.COLOR_GRAY2BGR:从灰度图转换为BGR
    • Imgproc.COLOR_BGR2BGRA:从BGR转换为BGRA
    • Imgproc.COLOR_BGRA2BGR:从BGRA转换为BGR
    • Imgproc.COLOR_BGR2RGB:从BGR转换为RGB
    • Imgproc.COLOR_RGB2BGR:从RGB转换为BGR
    • Imgproc.COLOR_BGR2HSV:从BGR转换为HSV
    • Imgproc.COLOR_RGB2HSV:从RGB转换为HSV
    • Imgproc.COLOR_HSV2BGR:从HSV转换为BGR
    • Imgproc.COLOR_HSV2RGB:从HSV换货为RGB

R、G和B像素值的范围如下:

CV_8U:0~255

CV_16U:0~65535

CV_32F:0~1

java 复制代码
public class ConvertColor {
    static {
        OpenCV.loadLocally(); // 自动下载并加载本地库
    }

    public static void main(String[] args) {
        //读取图像文件并在屏幕上显示
        Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/fish.png");
        HighGui.imshow("color", src);
        HighGui.waitKey(0);
        //将彩色图像转换为灰度图并在屏幕上显示
        Mat gray = new Mat();
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
        HighGui.imshow("gray", gray);
        HighGui.waitKey(0);
        //将彩色图像转换为HSV颜色模型并在屏幕上显示
        Mat hsv = new Mat();
        Imgproc.cvtColor(src, hsv, Imgproc.COLOR_BGR2HSV);
        HighGui.imshow("hsv", hsv);
        HighGui.waitKey(0);
        //将彩色图像转换为HSV颜色模型并在屏幕上显示
        Mat yuv = new Mat();
        Imgproc.cvtColor(src, yuv, Imgproc.COLOR_BGR2YUV);
        HighGui.imshow("yuv", yuv);
        HighGui.waitKey(0);
        System.exit(0);
    }
}

灰度图:

HSV颜色模型:

YUV颜色模型:

3.2、根据色调分割HSV图像

由于HSV颜色空间更接近人类视觉的直观感觉,因此可以在转换成HSV颜色空间后根据色彩对图像进行分割。如下图所示,图中是一副色盲测试图,如果转换成灰度图后只能看到一些灰色的圆点,则无法将中央绿色的9分离出来,因此需要将图像转换为HSV颜色模型,然后根据色调值进行分离。

分析图像可知,该图像前景主要有两种色系:绿色系和棕色系。已知OpenCV中CV_8U类型图像中绿色的色调值为60,棕色(RGB[128,64,0])为15,可以据此对图像进行分割。为了不超过8位无符号数的最大值255,OpenCV中se色调值范围为0~180。

java 复制代码
public class DetectColor {
    static {
        OpenCV.loadLocally(); // 自动下载并加载本地库
    }

    public static void main(String[] args) {
        //读取图像文件并在屏幕上显示
        Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/blind.png");
        HighGui.imshow("color", src);
        HighGui.waitKey(0);
        //将彩色图像转换为灰度图并在屏幕上显示
        Mat gray = new Mat();
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
        HighGui.imshow("gray", gray);
        HighGui.waitKey(0);
        //将彩色图像转换为HSV颜色模型
        Mat hsv = new Mat();
        Imgproc.cvtColor(src, hsv, Imgproc.COLOR_BGR2HSV);
        //克隆src,用于输出分割后的图像
        Mat dst = src.clone();
        //搜索政府图像,并根据色调值分割图像
        for (int i = 0; i < hsv.rows(); i++) {
            for (int j = 0; j < hsv.cols(); j++) {
                //获取HSV颜色模型中的色调值(H值)
                byte[] data = new byte[3];
                hsv.get(i, j, data);
                //根据色调值判断,如为绿色系(45~60)则用黑色画出,否则用白色
                if ((data[0] > 45) & (data[0] < 80)) {
                    data[0] = 0;
                    data[1] = 0;
                    data[2] = 0;
                } else {
                    //byte类型的-1将被映射为CV_8U类型的255
                    data[0] = -1;
                    data[1] = -1;
                    data[2] = -1;
                }
                //修改dst中RGB的值
                dst.put(i, j, data);
            }
        }
        //在屏幕上显示分割后的图像
        HighGui.imshow("Divided", dst);
        HighGui.waitKey(0);
        System.exit(0);
    }
}

原图:

灰度图:

分割后的:

3.3、图像通道的拆分与合并

根据不同的应用场景,有时需要将彩色图像的3种颜色通道拆开后分别进行操作,另一些时候则需要把独立通道的图片合而为一,此时就会用到OpenCV中的split()和merge()函数。

java 复制代码
void Core.split(Mat m, List<Mat> mv)
  • m:输入图像(多通道)
  • mv:分离后的多个单通道图像
java 复制代码
void Core.merge(List<Mat> mv, Mat dst)
  • mv:需要合并通道的图像组,数据类型为Mat类的列表
  • m:合并通道后的通道图像
java 复制代码
public class SplitMerge {
    static {
        OpenCV.loadLocally(); // 自动下载并加载本地库
    }

    public static void main(String[] args) {
        //读取图像文件并在屏幕上显示
        Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/blind.png");
        HighGui.imshow("src", src);
        HighGui.waitKey(0);
        //拆分图像通道
        List<Mat> dst = new ArrayList<>();
        Core.split(src, dst);
        //显示拆分后的各个图像
        for (Mat m : dst) {
            HighGui.imshow("split", m);
            HighGui.waitKey(0);
        }
        //合并图像通道
        Mat src2 = new Mat();
        Core.merge(dst, src2);

        //在屏幕上显示拆分后再合并的图像
        HighGui.imshow("Split and Merge", src2);
        HighGui.waitKey(0);
        System.exit(0);
    }
}

拆分后均为灰度图:

拆分后的B:

拆分后的G:

拆分后的R:

如果想要将灰度图显示为蓝、绿、宏的图像,需要另行处理,结果如下:


相关推荐
一勺汤23 分钟前
YOLO12 改进|融入 大 - 小卷积LS Convolution 捕获全局上下文与小核分支提取局部细节,提升目标检测中的多尺度
yolo·计算机视觉·多尺度·yolo12·yolo12改进·lsconv·小目标
敲键盘的小夜猫30 分钟前
大模型智能体核心技术:CoT与ReAct深度解析
人工智能·python
华科云商xiao徐37 分钟前
Python利用Scrapy框架部署分布式爬虫
python·scrapy
小前端大牛马38 分钟前
java教程笔记(十四)-线程池
java·笔记·python
老歌老听老掉牙1 小时前
旋量理论:刚体运动的几何描述与机器人应用
python·算法·机器学习·机器人·旋量
我是初九2 小时前
【李沐-动手学深度学习v2】1.Colab学习环境配置
人工智能·python·学习·colab
失败又激情的man2 小时前
python爬虫之数据存储
前端·数据库·python
一刀到底2112 小时前
Python 高级应用10:在python 大型项目中 FastAPI 和 Django 的相互配合
python·django·fastapi
MoRanzhi12032 小时前
245. 2019年蓝桥杯国赛 - 数正方形(困难)- 递推
python·算法·蓝桥杯·国赛·递推·2019