[OpenCV] 数字图像处理 C++ 学习——16直方图均衡化、直方图比较 详细讲解+附完整代码

news/2024/11/15 4:55:19/

文章目录

  • 前言
  • 1.直方图均衡化的理论基础
    • (1)什么是直方图
    • (2)直方图均衡化原理
    • (3)直方图均衡化公式
  • 2.直方图比较理论基础
    • (1)相关性 (Correlation)——HISTCMP_CORREL
    • (2)卡方 (Chi-Square)——HISTCMP_CHISQR
    • (3)十字交叉性 (Intersection) ——HISTCMP_INTERSECT
    • (4)巴氏距离 (Bhattacharyya Distance)——HISTCMP_BHATTACHARYYA
  • 2.代码实现
    • (1)直方图均衡化
    • (2)直方图计算
    • (3)直方图比较
  • 4.完整代码

前言

直方图是描述图像像素值分布的重要工具,它能够帮助我们分析图像的对比度、亮度和动态范围等信息。通过直方图的比较,我们可以衡量不同图像之间的相似度,从而在图像检索、匹配等应用中发挥重要作用。本篇博客将详细介绍直方图均衡化、四种常见的直方图比较方法——相关性(Correlation)、卡方(Chi-Square)、交叉性(Intersection)和巴氏距离(Bhattacharyya),并附完整的 C++ 代码示例

1.直方图均衡化的理论基础

(1)什么是直方图

直方图是用来表示图像中每个灰度级别像素数量的分布图。通过统计图像中每个灰度值(0-255)的出现次数,可以得到图像的灰度分布情况。是图像的统计学特征。

如下图假设有图像数据8*8,像素值范围0~14共15个灰度等级,统计得到各个等级出现次数及其直方图。

在这里插入图片描述

(2)直方图均衡化原理

直方图均衡化的目标是通过重新分配图像的灰度值,使得灰度值的分布更加均匀,从而增强图像的对比度。简单来说,直方图均衡化将原来分布不均匀的直方图拉伸,使得所有的灰度值在图像中的出现概率相对均匀。

像素灰度分布从一个分布映射到另一个分布,然后再得到映射后的像素值。

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=02%2525E7%25259B%2525B4%2525E6%252596%2525B9%2525E5%25259B%2525BE%2525E5%25259D%252587%2525E8%2525A1%2525A1%2525E5%25258C%252596.png&pos_id=img-Lc5Yl3kI-1727013471437
)

(3)直方图均衡化公式

s k = L − 1 M N ∑ i = 0 k h ( i ) s_k = \frac{L-1}{MN} \sum_{i=0}^{k} h(i) sk=MNL1i=0kh(i)

其中:

s k s_k sk表示均衡化后灰度值为 k k k的新灰度值;

L 是图像的灰度级别总数(通常为 256);

M和N分别是图像的宽度和高度;

h(i) 是灰度值为 ( i ) 的像素在图像中出现的次数。

这个公式将每个灰度值的累计分布映射为新的灰度值,从而使直方图更加均匀。

2.直方图比较理论基础

直方图比较的原理是通过对比两幅图像的直方图分布情况,来衡量它们的相似性。在 OpenCV 中,提供了四种常见的直方图比较方法,每种方法的原理和计算方式有所不同。

(1)相关性 (Correlation)——HISTCMP_CORREL

计算两个直方图之间的相关性,值范围为 -1 到 1,值越接近 1 表示两个直方图越相似,越接近 -1 表示它们越不相似。

d ( H 1 , H 2 ) = ∑ ( H 1 ( i ) − H 1 ˉ ) ( H 2 ( i ) − H 2 ˉ ) ∑ ( H 1 ( i ) − H 1 ˉ ) 2 ∑ ( H 2 ( i ) − H 2 ˉ ) 2 d(H_1, H_2) = \frac{\sum (H_1(i) - \bar{H_1})(H_2(i) - \bar{H_2})}{\sqrt{\sum (H_1(i) - \bar{H_1})^2 \sum (H_2(i) - \bar{H_2})^2}} d(H1,H2)=(H1(i)H1ˉ)2(H2(i)H2ˉ)2 (H1(i)H1ˉ)(H2(i)H2ˉ)

(2)卡方 (Chi-Square)——HISTCMP_CHISQR

卡方用于衡量两个直方图之间的差异,值越小表示越相似

d ( H 1 , H 2 ) = ∑ ( H 1 ( i ) − H 2 ( i ) ) 2 H 1 ( i ) d(H_1, H_2) = \sum \frac{(H_1(i) - H_2(i))^2}{H_1(i)} d(H1,H2)=H1(i)(H1(i)H2(i))2

(3)十字交叉性 (Intersection) ——HISTCMP_INTERSECT

十字交叉性通过计算两个直方图交集的大小来衡量相似性,值越大表示越相似

( H 1 , H 2 ) = ∑ m i n ( H 1 ( i ) , H 2 ( i ) ) (H1,H2) = ∑min (H1(i),H2(i)) (H1,H2)=min(H1(i),H2(i))

(4)巴氏距离 (Bhattacharyya Distance)——HISTCMP_BHATTACHARYYA

通过计算两个直方图之间的重叠程度来衡量相似性,值越小表示越相似

d ( H 1 , H 2 ) = 1 − 1 H 1 ˉ H 2 ˉ n 2 ∑ H 1 ( i ) H 2 ( i ) d(H_1, H_2) = \sqrt{1 - \frac{1}{\sqrt{\bar{H_1} \bar{H_2} n^2}} \sum \sqrt{H_1(i) H_2(i)}} d(H1,H2)=1H1ˉH2ˉn2 1H1(i)H2(i)

2.代码实现

直方图均衡化函数原型

void cv::equalizeHist(InputArray src, OutputArray dst)

src:输入的单通道图像,通常是灰度图像;

dst:输出的图像,即经过直方图均衡化处理后的图像。

(1)直方图均衡化

   // 读取输入图像Mat src = imread("lena.png", IMREAD_COLOR);if (src.empty()) {cout << "Could not open or find the image!" << endl;return;}// 显示原始图像namedWindow("Original Image", WINDOW_AUTOSIZE);imshow("Original Image", src);// 将图像转换为灰度图Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);// 进行直方图均衡化Mat equalizedImage;equalizeHist(gray, equalizedImage);// 显示均衡化后的图像namedWindow("Equalized Image", WINDOW_AUTOSIZE);imshow("Equalized Image", equalizedImage);

结果:

直方图均衡化后图像对比度增强
在这里插入图片描述

(2)直方图计算

计算并展示图像的蓝色、绿色和红色通道的直方图。先分离图像的三个 BGR 通道,计算每个通道的直方图,对直方图进行归一化处理,接着展示图像中三个颜色通道的像素分布情况。

/*********************直方图计算和显示*******************************/// 分离图像的BGR通道vector<Mat> bgr_planes;split(src, bgr_planes);// 定义直方图的参数int histSize = 256;float range[] = { 0, 256 };  // 灰度值范围const float* histRange = { range };Mat b_hist, g_hist, r_hist;// 分别计算三个通道的直方图calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange);calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange);calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange);// 归一化直方图,使得图像高度为 400 像素int histImageHeight = 400;int histImageWidth = 512;normalize(b_hist, b_hist, 0, histImageHeight, NORM_MINMAX);normalize(g_hist, g_hist, 0, histImageHeight, NORM_MINMAX);normalize(r_hist, r_hist, 0, histImageHeight, NORM_MINMAX);// 创建显示直方图的图像,大小为 512x400,背景为黑色Mat histImage(histImageHeight, histImageWidth, CV_8UC3, Scalar(0, 0, 0));// 在同一张图像上绘制蓝、绿、红三个通道的直方图for (int i = 1; i < histSize; i++) {line(histImage,Point((i - 1) * histImageWidth / histSize, histImageHeight - cvRound(b_hist.at<float>(i - 1))),Point(i * histImageWidth / histSize, histImageHeight - cvRound(b_hist.at<float>(i))),Scalar(255, 0, 0), 2); // 蓝色line(histImage,Point((i - 1) * histImageWidth / histSize, histImageHeight - cvRound(g_hist.at<float>(i - 1))),Point(i * histImageWidth / histSize, histImageHeight - cvRound(g_hist.at<float>(i))),Scalar(0, 255, 0), 2); // 绿色line(histImage,Point((i - 1) * histImageWidth / histSize, histImageHeight - cvRound(r_hist.at<float>(i - 1))),Point(i * histImageWidth / histSize, histImageHeight - cvRound(r_hist.at<float>(i))),Scalar(0, 0, 255), 2); // 红色}// 显示合并的直方图imshow("Combined BGR Histogram", histImage);

结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(3)直方图比较

在 HSV 色彩空间中对三张图像的 H 和 S 通道进行直方图计算,可以使用四种方法比较它们的相似性。

#define INPUT_TITLE0 "input image src"
#define INPUT_TITLE1 "input image srctest1"
#define INPUT_TITLE2 "input image srctest2"string convertToString(double d);
void Histogram_comparison()
{// 加载图像Mat src, srctest1, srctest2;src = imread("sherlock.jpg");srctest1 = imread("sherlock2.jpg");srctest2 = imread("lena.png");if (!src.data || !srctest1.data || !srctest2.data) {cout << "ERROR: Could not load image." << endl;return;}imshow("【src 原图】", src);imshow("【srctest1 原图】", srctest1);imshow("【srctest2 原图】", srctest2);// 从RGB色彩空间转换为HSV色彩空间cvtColor(src, src, COLOR_BGR2HSV);cvtColor(srctest1, srctest1, COLOR_BGR2HSV);cvtColor(srctest2, srctest2, COLOR_BGR2HSV);// 定义直方图计算所需的参数,主要是 H 和 S 两个通道int h_bins = 50;  // H 通道的直方图 bin 数int s_bins = 60;  // S 通道的直方图 bin 数int histSize[] = { h_bins, s_bins };// H 和 S 通道的取值范围float h_ranges[] = { 0, 180 };float s_ranges[] = { 0, 256 };const float* ranges[] = { h_ranges, s_ranges };// 使用 H 和 S 通道int channels[] = { 0, 1 };// MatND 是 Mat 的别名,区分经过直方图计算处理后的数据和原始图像MatND hist_src, hist_srctest1, hist_srctest2;// 计算直方图并进行归一化calcHist(&src, 1, channels, Mat(), hist_src, 2, histSize, ranges, true, false);normalize(hist_src, hist_src, 0, 1, NORM_MINMAX, -1, Mat());calcHist(&srctest1, 1, channels, Mat(), hist_srctest1, 2, histSize, ranges, true, false);normalize(hist_srctest1, hist_srctest1, 0, 1, NORM_MINMAX, -1, Mat());calcHist(&srctest2, 1, channels, Mat(), hist_srctest2, 2, histSize, ranges, true, false);normalize(hist_srctest2, hist_srctest2, 0, 1, NORM_MINMAX, -1, Mat());// 直方图比较   使用不同方法直接修改//相关性HISTCMP_CORREL   卡方HISTCMP_CHISQR  十字交叉性HISTCMP_INTERSECT 巴氏距离HISTCMP_BHATTACHARYYAdouble src_src = compareHist(hist_src, hist_src, HISTCMP_CHISQR);double src_srctest1 = compareHist(hist_src, hist_srctest1, HISTCMP_CHISQR);double src_srctest2 = compareHist(hist_src, hist_srctest2, HISTCMP_CHISQR);double srctest1_srctest2 = compareHist(hist_srctest1, hist_srctest2, HISTCMP_CHISQR);cout << "src compare with src correlation value: " << src_src << endl;cout << "src compare with srctest1 correlation value: " << src_srctest1 << endl;cout << "src compare with srctest2 correlation value: " << src_srctest2 << endl;cout << "srctest1 compare with srctest2 correlation value: " << srctest1_srctest2 << endl;// 在图像上添加比较结果的文本putText(src, convertToString(src_src), Point(50, 50), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 255), 2, LINE_AA);putText(srctest1, convertToString(src_srctest1), Point(50, 50), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);putText(srctest2, convertToString(src_srctest2), Point(50, 50), FONT_HERSHEY_COMPLEX, 1, Scalar(255, 0, 255), 2, LINE_AA);// 显示结果图像namedWindow(INPUT_TITLE0, WINDOW_AUTOSIZE);namedWindow(INPUT_TITLE1, WINDOW_AUTOSIZE);namedWindow(INPUT_TITLE2, WINDOW_AUTOSIZE);imshow(INPUT_TITLE0, src);imshow(INPUT_TITLE1, srctest1);imshow(INPUT_TITLE2, srctest2);waitKey(0);
}
// 转换 double 类型为字符串
string convertToString(double d) {ostringstream os;if (os << d) {return os.str();}return "invalid conversion";
}

相关性 (Correlation)——HISTCMP_CORREL结果:
使用相关性 (Correlation) 作为度量标准来比较三张图像的直方图,src 和 srctest1 的相关性为 0.999054,接近 1,表示它们的直方图非常相似,这两张图像的内容几乎一致;而 src 和 srctest2 的相关性为 -0.00756844,接近 0,甚至是负值,表明这两张图像的直方图几乎没有相关性;同时 srctest1 和 srctest2 的相关性为 -0.00750053,这两者之间的图像差异非常显著, src 和 srctest1 是非常相似的图像, srctest2 与其他两张图像的差异较大。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

卡方 (Chi-Square)——HISTCMP_CHISQR结果:

使用卡方(Chi-Square)作为比较方法时,结果显示 src 与 srctest1 的卡方值为 0.664607,它们的直方图之间存在一些差异,但差异较小;而 src 与 srctest2 的卡方值 13425.5,这两张图像的直方图有极大的差异,图像内容显著不同。同样,srctest1 和 srctest2 之间的卡方值为 15850.8,说明 srctest1 和 srctest2 之间的内容差异也非常明显。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.完整代码

#include<opencv2/opencv.hpp>
#include<highgui.hpp>
#include<iostream>
#include<math.h>using namespace cv;
using namespace std;void histogram_equalization()
{// 读取输入图像Mat src = imread("lena.png", IMREAD_COLOR);if (src.empty()) {cout << "Could not open or find the image!" << endl;return;}// 显示原始图像namedWindow("Original Image", WINDOW_AUTOSIZE);imshow("Original Image", src);// 将图像转换为灰度图Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);// 进行直方图均衡化Mat equalizedImage;equalizeHist(gray, equalizedImage);// 显示均衡化后的图像namedWindow("Equalized Image", WINDOW_AUTOSIZE);imshow("Equalized Image", equalizedImage);/*********************直方图计算和显示*******************************/// 分离图像的BGR通道vector<Mat> bgr_planes;split(src, bgr_planes);// 定义直方图的参数int histSize = 256;float range[] = { 0, 256 };  // 灰度值范围const float* histRange = { range };Mat b_hist, g_hist, r_hist;// 分别计算三个通道的直方图calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange);calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange);calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange);// 归一化直方图,使得图像高度为 400 像素int histImageHeight = 400;int histImageWidth = 512;normalize(b_hist, b_hist, 0, histImageHeight, NORM_MINMAX);normalize(g_hist, g_hist, 0, histImageHeight, NORM_MINMAX);normalize(r_hist, r_hist, 0, histImageHeight, NORM_MINMAX);// 创建显示直方图的图像,大小为 512x400,背景为黑色Mat histImage(histImageHeight, histImageWidth, CV_8UC3, Scalar(0, 0, 0));// 在同一张图像上绘制蓝、绿、红三个通道的直方图for (int i = 1; i < histSize; i++) {line(histImage,Point((i - 1) * histImageWidth / histSize, histImageHeight - cvRound(b_hist.at<float>(i - 1))),Point(i * histImageWidth / histSize, histImageHeight - cvRound(b_hist.at<float>(i))),Scalar(255, 0, 0), 2); // 蓝色line(histImage,Point((i - 1) * histImageWidth / histSize, histImageHeight - cvRound(g_hist.at<float>(i - 1))),Point(i * histImageWidth / histSize, histImageHeight - cvRound(g_hist.at<float>(i))),Scalar(0, 255, 0), 2); // 绿色line(histImage,Point((i - 1) * histImageWidth / histSize, histImageHeight - cvRound(r_hist.at<float>(i - 1))),Point(i * histImageWidth / histSize, histImageHeight - cvRound(r_hist.at<float>(i))),Scalar(0, 0, 255), 2); // 红色}// 显示合并的直方图imshow("Combined BGR Histogram", histImage);waitKey(0);
}
#define INPUT_TITLE0 "input image src"
#define INPUT_TITLE1 "input image srctest1"
#define INPUT_TITLE2 "input image srctest2"string convertToString(double d);
void Histogram_comparison()
{// 加载图像Mat src, srctest1, srctest2;src = imread("sherlock.jpg");srctest1 = imread("sherlock2.jpg");srctest2 = imread("lena.png");if (!src.data || !srctest1.data || !srctest2.data) {cout << "ERROR: Could not load image." << endl;return;}imshow("【src 原图】", src);imshow("【srctest1 原图】", srctest1);imshow("【srctest2 原图】", srctest2);// 从RGB色彩空间转换为HSV色彩空间cvtColor(src, src, COLOR_BGR2HSV);cvtColor(srctest1, srctest1, COLOR_BGR2HSV);cvtColor(srctest2, srctest2, COLOR_BGR2HSV);// 定义直方图计算所需的参数,主要是 H 和 S 两个通道int h_bins = 50;  // H 通道的直方图 bin 数int s_bins = 60;  // S 通道的直方图 bin 数int histSize[] = { h_bins, s_bins };// H 和 S 通道的取值范围float h_ranges[] = { 0, 180 };float s_ranges[] = { 0, 256 };const float* ranges[] = { h_ranges, s_ranges };// 使用 H 和 S 通道int channels[] = { 0, 1 };// MatND 是 Mat 的别名,区分经过直方图计算处理后的数据和原始图像MatND hist_src, hist_srctest1, hist_srctest2;// 计算直方图并进行归一化calcHist(&src, 1, channels, Mat(), hist_src, 2, histSize, ranges, true, false);normalize(hist_src, hist_src, 0, 1, NORM_MINMAX, -1, Mat());calcHist(&srctest1, 1, channels, Mat(), hist_srctest1, 2, histSize, ranges, true, false);normalize(hist_srctest1, hist_srctest1, 0, 1, NORM_MINMAX, -1, Mat());calcHist(&srctest2, 1, channels, Mat(), hist_srctest2, 2, histSize, ranges, true, false);normalize(hist_srctest2, hist_srctest2, 0, 1, NORM_MINMAX, -1, Mat());// 直方图比较   使用不同方法直接修改//相关性HISTCMP_CORREL   卡方HISTCMP_CHISQR  十字交叉性HISTCMP_INTERSECT 巴氏距离HISTCMP_BHATTACHARYYAdouble src_src = compareHist(hist_src, hist_src, HISTCMP_CHISQR);double src_srctest1 = compareHist(hist_src, hist_srctest1, HISTCMP_CHISQR);double src_srctest2 = compareHist(hist_src, hist_srctest2, HISTCMP_CHISQR);double srctest1_srctest2 = compareHist(hist_srctest1, hist_srctest2, HISTCMP_CHISQR);cout << "src compare with src correlation value: " << src_src << endl;cout << "src compare with srctest1 correlation value: " << src_srctest1 << endl;cout << "src compare with srctest2 correlation value: " << src_srctest2 << endl;cout << "srctest1 compare with srctest2 correlation value: " << srctest1_srctest2 << endl;// 在图像上添加比较结果的文本putText(src, convertToString(src_src), Point(50, 50), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 255), 2, LINE_AA);putText(srctest1, convertToString(src_srctest1), Point(50, 50), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);putText(srctest2, convertToString(src_srctest2), Point(50, 50), FONT_HERSHEY_COMPLEX, 1, Scalar(255, 0, 255), 2, LINE_AA);// 显示结果图像namedWindow(INPUT_TITLE0, WINDOW_AUTOSIZE);namedWindow(INPUT_TITLE1, WINDOW_AUTOSIZE);namedWindow(INPUT_TITLE2, WINDOW_AUTOSIZE);imshow(INPUT_TITLE0, src);imshow(INPUT_TITLE1, srctest1);imshow(INPUT_TITLE2, srctest2);waitKey(0);
}
// 转换 double 类型为字符串
string convertToString(double d) {ostringstream os;if (os << d) {return os.str();}return "invalid conversion";
}
int main() 
{histogram_equalization();Histogram_comparison();return 0;
}

http://www.ppmy.cn/news/1529248.html

相关文章

Linux C高级day3

一、思维导图 二、练习 #!/bin/bash mkdir ~/dir mkdir ~/dir/dir1 mkdir ~/dir/dir2 cp -r * ~/dir/dir1/ cp -r *.sh ~/dir/dir2/ cd ~/dir/dir2/ tar -cvJf dir2.tar.xz dir2 mv dir2.tar.xz ~/dir/dir1/ cd ~/dir/dir1 tar -xvJf dir2.tar.xz #!/bin/bash head -5 /etc/gr…

Study Plan For Algorithms - Part37

1. 圆圈中最后剩下的数字 0,1,,n-1 这 n 个数字排成一个圆圈&#xff0c;从数字 0 开始&#xff0c;每次从这个圆圈里删除第 m 个数字&#xff08;删除后从下一个数字开始计数&#xff09;。求出这个圆圈里剩下的最后一个数字。 方法一&#xff1a; def lastRemaining(n, m):r…

从Profinet到Ethernet IP网关技术重塑工业网络,数据传输更流畅

Profinet转Ethernet IP网关在未来工业领域可能产生以下重要影响并发挥关键作用&#xff1a;促进工业设备集成与互操作性&#xff1a;打破协议壁垒&#xff1a;在工业场景中&#xff0c;存在多种不同的工业以太网协议&#xff0c;设备往往因协议差异而难以直接通信。 Profinet转…

自监督的主要学习方法

自监督学习是一种机器学习方法&#xff0c;其中模型从未标注的数据中学习生成标签&#xff0c;通常通过构造预训练任务或预测任务来从数据的内部结构中提取信息。它的核心目标是利用无监督的数据进行学习&#xff0c;从而在下游任务中更好地利用监督信号。自监督学习的主要方法…

【React】使用 umi4 搭建项目的一些小问题解决方案

umi-request umi-request 在 umi4 中被废弃&#xff0c;使用 import { request } from ‘/plugin-request’ 来&#xff08;对 axios 进行的二次封装&#xff09;替代。 引入 ant-design/icons 不生效 // import {PlusOutlined, EllipsisOutlined} from “ant-design/icons”…

Python 从入门到实战24(类的继承)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了类的定义、使用方法、property的相关知识。今…

实用好软-----电脑端 全能音视频转换器 转换各种音视频格式

软件介绍&#xff1a; 工具是一款免费的视频格式转换软件&#xff0c;支持几乎所有视频格式的转换&#xff0c;基本的有DVD, AVI, MP4, 3GP, WMV, ASF等格式。对于一些特殊格式的视频&#xff0c;不用担心看不到&#xff0c;除了保证转换质量&#xff0c;还能转换为你想要的类…

Kotlin 函数和变量(四)

导读大纲 1.1 基本要素: 函数和变量1.1.1 声明变量以存储数据1.1.2 将变量标记为只读或可重新赋值1.1.3 更简单的字符串格式化: 字符串模板 1.1 基本要素: 函数和变量 本节将向你介绍每个 Kotlin 程序都包含的基本元素: 函数和变量 你将编写自己的第一个 Kotlin 程序,了解 Kotl…