OpenCV——图像基本操作(一)
图像基本操作
- 一、图像读写与显示
- 1.1、图像的读取
- 1.2、图像的保存
- 1.3、图像的显示
- 1.4、示例
- 二、绘制函数
- 2.1、绘制直线
- 2.2、绘制矩形
- 2.3、绘制圆形
- 2.4、绘制椭圆
- 2.5、绘制多边形
- 2.6、绘制文字
- 2.7、绘制箭头
- 2.8、绘制外框
- 三、颜色空间操作
- 3.1、颜色空间的转换
- 3.2、根据色调分割HSV图像
- 3.3、图像通道的拆分与合并
一、图像读写与显示
1.1、图像的读取
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方向标志旋转图像 |
读取图像文件时注意事项如下:
- 如果因为某些原因(文件不存在、没有权限、文件格式不被支持或无效等)无法读取图像,则返回一个空矩阵
- 由于Java中反斜杠
\
用作转义符,因为绝对路径中的分隔符不能用\
,而要用\\
或/
- OpenCV支持TIFF、PNG、JPEG、BMP等常用文件格式,但不支持GIF文件格式
- 该函数根据文件内容而不是文件扩展名来确定图像的类型
- 如读取的文件是彩色图像,解码后的图像通道按B、G、R顺序存储
- 如果flags为IMREAD_GRAYSCALE,则灰度转换的结果可能与cvtColor()函数的输出不同
- 默认情况下,图像像素必须小于2^30。如有必要,可通过修改系统变量OPENCV_IO_MAX_IMAGE_PIXELS设置最大像素
1.2、图像的保存
boolean Imgcodecs.imwrite(String filename, Mat img)
- filename:指定的文件名
- img:要保存的图像
保存图像时的格式依据filename的扩展名确定,此函数通常仅支持单通道或三通道(按照B、G、R顺序)图像的保存,但有一下例外:
- 16位无符号整数类型(CV_16U)的图像可以保存为PNG、JPEG2000或TIFF格式
- 32位浮点数类型(CV_32F)的图像可保存为TIFF、OpenEXR及HDR格式
- 有Alpha通道的PNG图像也可以用此函数保存。保存时需要先创建8位/16位四通道图像BGRA,其中Alpha通道排在最后:完全透明像素的Alpha值设为0,完全不透明的像素值设为255或65535
- 要在一个文件中保存多个图像,可采用TIFF文件格式
- 如果乳香格式不被支持,则图像将被转换为8位无符号整数类型(CV_8U)存储
1.3、图像的显示
void HighGui.imshow(String winname, Mat img)
- winname:显示图像的窗口名称
- img:要显示的图像
在调用imshow()函数后,需要通过waitKey()函数告知系统图像在屏幕上停留的时间,如果不用这个函数,则屏幕上不会显示图像。
int HighGui.waitKey(int delay)
- delay:需要等待的时间,单位为毫秒ms。如将delay设为0,表示等待用户按键结束此函数
- 返回值:如果某按键被按下,则返回键值对应的ASCII码,否则返回-1
此函数只存在HighGUI窗口时才有效。如果同时存在多个窗口,则其中任意一个均可被激活。
1.4、示例
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、绘制直线
void Imgproc.line(Mat img, Point pt1, Point pt2, Scalar color, int thickness)
- img:用于绘图的图像
- pt1:线段的端点1
- pt2:线段的端点2
- color:线段的颜色
- thickness:线的粗细
2.2、绘制矩形
void Imgproc.rectangle(Mat img, Point pt1, Point pt2, Scalar color, int thickness)
- img:用于绘图的图像
- pt1:矩形两个对角点之一
- pt2:pt1的对角点
- color:矩形的颜色
- thickness:轮廓线的粗细。负值表示画一个实心矩形
2.3、绘制圆形
void Imgproc.circle(Mat img, Point center, int radius, Scalar color, int thickness)
- img:用于绘图的图像
- center:圆的圆心
- radius:圆的半径
- color:圆的颜色
- thickness:轮廓线的粗细。负值表示画一个实心圆
示例:
public class Draw {static {OpenCV.loadLocally(); // 自动下载并加载本地库}public static void main(String[] args) {//用于画图的背景图,尺寸为800*800Mat 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.
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、绘制多边形
void Imgproc.polylines(Mat img, List<MatOfPoint> pts, boolean isClosed, Scalar color, int thickness)
- img:用户绘图的图像
- pts:多边形的多个顶点
- isClosed:多边形是否闭合
- color:多边形的颜色
- thickness:线的粗细
2.6、绘制文字
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这种形式。
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、绘制箭头
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、绘制外框
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
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、颜色空间的转换
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
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。
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类型的255data[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()函数。
void Core.split(Mat m, List<Mat> mv)
- m:输入图像(多通道)
- mv:分离后的多个单通道图像
void Core.merge(List<Mat> mv, Mat dst)
- mv:需要合并通道的图像组,数据类型为Mat类的列表
- m:合并通道后的通道图像
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:
如果想要将灰度图显示为蓝、绿、宏的图像,需要另行处理,结果如下: