赞
踩
本篇文章旨在通过详细的代码逐行注释,介绍如何分别使用 C/C++/Python 对彩色图像做灰度化(最大值法、平均值法、加权平均值法)和二值化处理(平均值法、双峰法、OTSU法)。
数字图像通常采用矩阵表示。以一幅数字图像F左上角像素中心为坐标原点,一幅MxN的数字图像用矩阵表示为:
根据描述像素的灰度以及颜色模式的不同,可将其分为灰度图像、二值图像、彩色图像。
二值图像,从名字即可猜出一二,它有两个值,0和1,0表示黑或背景,1表示白或前景。每个像素只需要1bit就可以完整存储信息。即图像上的每一个像素点的像素值只有两种可能的取值或灰度等级状态。如下图所示。
灰度图像,是指每个像素的信息由一个量化的灰度来描述的图像,没有彩色信息。
每个像素通过一个颜色通道来表示灰度值。颜色深度通常为8位(1字节),颜色数为256,即1字节(8位)可表示256级灰度[0,255]。这类图像通常显示为从最暗黑色到最亮白色的灰度。灰度图像是在单个电磁波频谱(如可见光)内测量每个像素的亮度得到的,通常用每个采样像素8位的非线性尺度来保存,这样可以有256级灰度(如果用16位,则有65536级)。
彩色图像是指每个像素由R、G、B分量构成的图像,其中R、G、B由不同的灰度级来描述,每个分量有256级灰度。3字节(24位)可表示一个像素。
灰度化处理就是将彩色图像转化为灰度图像,使得彩色的R、G、B分量相等的过程。彩色图像分为R、G、B三个分量,分别显示出红绿蓝等各种颜色。灰度值大的像素点比较亮(像素值最大为255,为白色),反之比较暗(像素最小为0,为黑色)。
图像灰度化的算法主要有以下三种:
最大值法使转化后的R、G、B的值等于转化前3个值中最大的一个,即:
R = G = B = m a x ( R , G , B ) R=G=B=max(R,G,B) R=G=B=max(R,G,B)
平均值法使转化后R、G、B的值为转化前R、G、B的平均值。即:
R = G = B = ( R + G + B ) / 3 R=G=B=(R+G+B)/3 R=G=B=(R+G+B)/3
加权平均值法按照一定权值,对R、G、B的值加权平均,即:
R = G = B = ( w R R + w G G + w B B ) / 3 R=G=B=(w_RR+w_GG+w_BB)/3 R=G=B=(wRR+wGG+wBB)/3
图像二值化(Image Binarization)就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。二值图像每个像素只有两种取值:要么纯黑,要么纯白。
由于二值图像数据足够简单,许多视觉算法都依赖二值图像。通过二值图像,能更好地分析物体的形状和轮廓。二值图像也常常用作原始图像的掩模(又称遮罩、蒙版,Mask):它就像一张部分镂空的纸,把我们不感兴趣的区域遮掉。进行二值化有多种方式,其中最常用的就是采用阈值法(Thresholding)进行二值化。其将大于某个临界灰度值的像素灰度设为灰度极大值,小于这个值的为灰度极小值,从而实现二值化。
阈值法又分为全局阈值(Global Method)和局部阈值(Local Method),又称自适应阈值(Adaptive Thresholding)。本文主要实现全局阈值,阈值的选取基于以下三种方法:
平均值法是一种简单的图像二值化算法,基本原理是通过计算图像的平均灰度值作为阈值,将像素的灰度值与阈值进行比较,将大于阈值的像素设为白色(255),将小于等于阈值的像素设为黑色(0)。
双峰法是一种常用的图像二值化方法,特别适用于具有明显前景和背景差异的图像。它基于图像的灰度直方图,可以确定一个合适的阈值,将图像分为前景和背景。双峰(bimodal histogram)即表示图像灰度值中两个峰值之间的谷底,该谷底代表了前景和背景之间的分界点。
大津法(OTSU)是一种自动选择阈值(无参数且无监督)的的图像分割方法,日本学者 Nobuyuki Otsu 1979年提出。该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。
算法原理
当使用OTSU法进行图像二值化时,算法求解过程如下:
统计每个灰度级别的像素数目:
计算总的像素数目:
初始化最大类间方差为0,最佳阈值为0。
对于每个可能的阈值
K
K
K(从 0 到 255)进行以下计算:
a. 将灰度图像分为两个类别:
使用最佳阈值将原始灰度图像进行二值化处理:
最后,即可得到经过OTSU法二值化处理后的图像。
#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" void jpeg_to_gray(const char* input_filename, const char* output_filename) { int width, height, channels; // 加载JPEG图像 unsigned char *image_data = stbi_load(input_filename, &width, &height, &channels, 0); if (!image_data) { printf("Failed to load input image.\n"); return; } // 创建一个新的灰度图像数据数组,大小为图像的宽度乘以高度 unsigned char *gray_image_data = (unsigned char*)malloc(width * height); // 对每个像素计算灰度值 for (int i = 0; i < width * height; i++) { unsigned char gray = 0; // 最大值法:将RGB分量中的最大值作为灰度值 for (int j = 0; j < channels; j++) { gray = (image_data[i * channels + j] > gray) ? image_data[i * channels + j] : gray; } // 将计算得到的灰度值存储在新的灰度图像数据数组中 gray_image_data[i] = gray; } /*------------------------平均值法-----------------------------------------*/ /*// 平均值法:将RGB分量取平均值作为灰度值 for (int i = 0; i < width * height; i++) { unsigned char gray = 0; // 计算RGB分量的平均值 for (int j = 0; j < channels; j++) { gray += image_data[i * channels + j]; } gray /= channels; // 将计算得到的灰度值存储在新的灰度图像数据数组中 gray_image_data[i] = gray; }*/ /*------------------------加权平均值法-----------------------------------------*/ /*// 加权平均值法:将RGB分量按照一定权重相加作为灰度值 const float weights[] = {0.2989f, 0.5870f, 0.1140f}; // RGB通道权重 for (int i = 0; i < width * height; i++) { float gray = 0.0f; // 加权求和 for (int j = 0; j < channels; j++) { gray += weights[j] * image_data[i * channels + j]; } // 将计算得到的灰度值存储在新的灰度图像数据数组中 gray_image_data[i] = static_cast<unsigned char>(gray); } */ // 将灰度图像数据保存为JPEG图像 stbi_write_jpg(output_filename, width, height, 1, gray_image_data, 100); // 释放内存 stbi_image_free(image_data); free(gray_image_data); } int main() { const char* input_filename = "demo.jpg"; const char* output_filename = "output.jpg"; // JPEG图像灰度化处理 jpeg_to_gray(input_filename, output_filename); printf("JPEG to grayscale conversion completed.\n"); return 0; }
#include <opencv2/opencv.hpp> void jpeg_to_gray(const std::string& input_filename, const std::string& output_filename) { // 读取JPEG图像 cv::Mat image = cv::imread(input_filename, cv::IMREAD_COLOR); if (image.empty()) { std::cout << "Failed to load input image." << std::endl; return; } // 获取图像的宽度和高度 int width = image.cols; int height = image.rows; // 创建灰度图像 cv::Mat gray_image(height, width, CV_8UC1); // 遍历图像像素,计算最大值法的灰度值 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // 获取像素的RGB分量 cv::Vec3b pixel = image.at<cv::Vec3b>(y, x); // 计算灰度值,即RGB分量中的最大值 uchar gray = std::max(std::max(pixel[0], pixel[1]), pixel[2]); // 设置灰度图像像素值 gray_image.at<uchar>(y, x) = gray; } } /*------------------------平均值法-----------------------------------------*/ /*// 遍历图像像素,计算平均值法的灰度值 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // 获取像素的RGB分量 cv::Vec3b pixel = image.at<cv::Vec3b>(y, x); // 计算灰度值,即RGB分量的平均值 uchar gray = (pixel[0] + pixel[1] + pixel[2]) / 3; // 设置灰度图像像素值 gray_image.at<uchar>(y, x) = gray; } }*/ /*------------------------加权平均值法-----------------------------------------*/ /*// 加权平均值法:将RGB分量按照一定权重相加作为灰度值 const float weights[] = {0.2989f, 0.5870f, 0.1140f}; // RGB通道权重 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // 获取像素的RGB分量 cv::Vec3b pixel = image.at<cv::Vec3b>(y, x); // 计算灰度值,即RGB分量的加权平均值 uchar gray = static_cast<uchar>(pixel[0] * weights[0] + pixel[1] * weights[1] + pixel[2] * weights[2]); // 设置灰度图像像素值 gray_image.at<uchar>(y, x) = gray; } } */ // 保存灰度图像为JPEG cv::imwrite(output_filename, gray_image); std::cout << "JPEG to grayscale conversion using maximum method completed." << std::endl; } int main() { std::string input_filename = "demo.jpg"; std::string output_filename = "output.jpg"; // 调用函数进行JPEG图像灰度化处理 jpeg_to_gray(input_filename, output_filename); return 0; }
import cv2 import numpy as np def jpeg_to_gray(input_filename, output_filename): # 读取JPEG图像 image = cv2.imread(input_filename, cv2.IMREAD_COLOR) if image is None: print("Failed to load input image.") return # ------------------------------------------------最大值法----------------------------------- gray = np.max(image, axis=2) # # ------------------------------------------------平均值法-------------------------------- # gray = np.mean(image, axis=2).astype(np.uint8) # -----------------------------------------------加权平均值法------------------------------- # weights = [0.2989, 0.5870, 0.1140] # gray = np.dot(image.astype(float), weights).astype(np.uint8) # 保存灰度图像 cv2.imwrite(output_filename, gray) # cv2.imwrite(output_filename, gray_avg) # cv2.imwrite(output_filename, gray_weighted) print("JPEG to grayscale conversion completed.") input_filename = "demo.jpg" output_filename = "output.jpg" # 调用函数进行JPEG图像灰度化处理 jpeg_to_gray(input_filename, output_filename)
#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" void jpeg_to_binary(const char* input_filename, const char* output_filename, int threshold) { int width, height, channels; // 加载JPEG图像 unsigned char* image_data = stbi_load(input_filename, &width, &height, &channels, 0); if (!image_data) { printf("Failed to load input image.\n"); return; } // 创建一个新的二值化图像数据数组,大小为图像的宽度乘以高度 unsigned char* binary_image_data = (unsigned char*)malloc(width * height); // 平均值法进行二值化处理 for (int i = 0; i < width * height; i++) { int sum = 0; // 计算RGB分量的平均值 for (int j = 0; j < channels; j++) { sum += image_data[i * channels + j]; } // 计算平均灰度值 int average = sum / channels; // 根据平均灰度值进行二值化处理 if (average >= threshold) { binary_image_data[i] = 255; // 前景(白色) } else { binary_image_data[i] = 0; // 背景(黑色) } } // 将二值化图像数据保存为JPEG图像 stbi_write_jpg(output_filename, width, height, 1, binary_image_data, 100); // 释放内存 stbi_image_free(image_data); free(binary_image_data); } int main() { const char* input_filename = "demo.jpg"; const char* output_filename = "output.jpg"; int threshold = 128; // 二值化阈值 // 调用函数进行JPEG图像二值化处理 jpeg_to_binary(input_filename, output_filename, threshold); printf("JPEG image binarization using average method completed.\n"); return 0; }
#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" #include <stdbool.h> void jpeg_to_binary(const char* input_filename, const char* output_filename, int threshold) { int width, height, channels; // 加载JPEG图像 unsigned char* image_data = stbi_load(input_filename, &width, &height, &channels, 0); if (!image_data) { printf("Failed to load input image.\n"); return; } // 创建一个新的二值化图像数据数组,大小为图像的宽度乘以高度 unsigned char* binary_image_data = (unsigned char*)malloc(width * height); // 统计灰度级别的像素数目 int histogram[256] = {0}; for (int i = 0; i < width * height * channels; i += channels) { int gray = (image_data[i] + image_data[i + 1] + image_data[i + 2]) / 3; // 计算灰度值 histogram[gray]++; } // 寻找双峰的峰值 int peak1 = 0; // 第一个峰值 int peak2 = 0; // 第二个峰值 int maxPeak = 0; // 最大峰值 bool foundPeak1 = false; bool foundPeak2 = false; for (int i = 0; i < 256; i++) { if (histogram[i] > maxPeak) { maxPeak = histogram[i]; if (!foundPeak1) { peak1 = i; foundPeak1 = true; } else { peak2 = i; foundPeak2 = true; } } } // 计算阈值 int threshold_1 = (peak1 + peak2) / 2; // 根据阈值进行二值化处理 for (int i = 0; i < width * height * channels; i += channels) { int gray = (image_data[i] + image_data[i + 1] + image_data[i + 2]) / 3; // 计算灰度值 if (gray >= threshold_1) { binary_image_data[i / channels] = 255; // 前景(白色) } else { binary_image_data[i / channels] = 0; // 背景(黑色) } } // 将二值化图像数据保存为JPEG图像 stbi_write_jpg(output_filename, width, height, 1, binary_image_data, 100); // 释放内存 stbi_image_free(image_data); free(binary_image_data); } int main() { const char* input_filename = "demo.jpg"; const char* output_filename = "output.jpg"; int threshold = 0; // 二值化阈值 // 调用函数进行JPEG图像二值化处理 jpeg_to_binary(input_filename, output_filename, threshold); printf("JPEG image binarization using bimodal method completed.\n"); return 0; }
#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" void jpeg_to_binary(const char* input_filename, const char* output_filename) { // 加载JPEG图像 int width, height, channels; unsigned char* image_data = stbi_load(input_filename, &width, &height, &channels, 0); if (!image_data) { printf("Failed to load input image.\n"); return; } // 创建灰度图像数据数组 unsigned char* gray_image_data = (unsigned char*)malloc(width * height); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int gray_value = 0; // 计算灰度值 for (int c = 0; c < channels; c++) { gray_value += image_data[(y * width + x) * channels + c]; } gray_value /= channels; // 存储灰度值到灰度图像数据数组中 gray_image_data[y * width + x] = gray_value; } } // 统计灰度级别的像素数目 int histogram[256] = {0}; for (int i = 0; i < width * height; i++) { histogram[gray_image_data[i]]++; } // 计算总的像素数目 int N = width * height; double max_variance = 0; // 最大类间方差 int best_threshold = 0; // 最佳阈值 // 计算最佳阈值 for (int K = 0; K < 256; K++) { int n1 = 0; // 类别1的像素数目 int n2 = 0; // 类别2的像素数目 double p1 = 0; // 类别1的概率 double p2 = 0; // 类别2的概率 // 计算类别1和类别2的像素数目和概率 for (int i = 0; i <= K; i++) { n1 += histogram[i]; } for (int i = K + 1; i < 256; i++) { n2 += histogram[i]; } p1 = (double)n1 / N; p2 = (double)n2 / N; double M_c1 = 0; // 类别1的像素均值 double M_c2 = 0; // 类别2的像素均值 // 计算类别1和类别2的像素均值 for (int i = 0; i <= K; i++) { M_c1 += i * histogram[i]; } M_c1 /= n1; for (int i = K + 1; i < 256; i++) { M_c2 += i * histogram[i]; } M_c2 /= n2; // 计算类间方差 double variance = p1 * p2 * pow((M_c1 - M_c2), 2); // 更新最大类间方差和最佳阈值 if (variance > max_variance) { max_variance = variance; best_threshold = K; } } // 使用最佳阈值将灰度图像进行二值化处理 unsigned char* binary_image_data = (unsigned char*)malloc(width * height); for (int i = 0; i < width * height; i++) { if (gray_image_data[i] >= best_threshold) { binary_image_data[i] = 255; // 前景(白色) } else { binary_image_data[i] = 0; // 背景(黑色) } } // 将二值化图像数据保存为JPEG图像 stbi_write_jpg(output_filename, width, height, 1, binary_image_data, 100); // 释放内存 stbi_image_free(image_data); free(gray_image_data); free(binary_image_data); } int main() { const char* input_filename = "demo.jpg"; const char* output_filename = "output.jpg"; jpeg_to_binary(input_filename, output_filename); printf("JPEG image binarization using OTSU method completed.\n"); return 0; }
#include <opencv2/opencv.hpp> void jpeg_to_binary(const char* input_filename, const char* output_filename, int threshold) { // 读取JPEG图像 cv::Mat image = cv::imread(input_filename); if (image.empty()) { printf("Failed to load input image.\n"); return; } // 转换为灰度图像 cv::Mat gray_image; cv::cvtColor(image, gray_image, cv::COLOR_BGR2GRAY); // 进行二值化处理 cv::Mat binary_image; cv::threshold(gray_image, binary_image, threshold, 255, cv::THRESH_BINARY); // 保存二值化图像为JPEG格式 cv::imwrite(output_filename, binary_image); // 释放内存 image.release(); gray_image.release(); binary_image.release(); } int main() { const char* input_filename = "demo.jpg"; const char* output_filename = "output.jpg"; int threshold = 128; // 二值化阈值 // 将灰度图像进行二值化处理 jpeg_to_binary(input_filename, output_filename, threshold); printf("JPEG image binarization using average method completed.\n"); return 0; }
#include <opencv2/opencv.hpp> void jpeg_to_binary(const char* input_filename, const char* output_filename, int threshold) { // 加载JPEG图像 cv::Mat image = cv::imread(input_filename); if (image.empty()) { printf("Failed to load input image.\n"); return; } // 转换为灰度图像 cv::Mat gray_image; cv::cvtColor(image, gray_image, cv::COLOR_BGR2GRAY); // 将灰度图像进行二值化处理 cv::Mat binary_image; cv::threshold(gray_image, binary_image, threshold, 255, cv::THRESH_BINARY); // 保存二值化图像为JPEG图像 cv::imwrite(output_filename, binary_image); // 释放内存 image.release(); gray_image.release(); binary_image.release(); } int main() { const char* input_filename = "demo.jpg"; const char* output_filename = "output.jpg"; int threshold = 0; // 二值化阈值 // 加载JPEG图像 cv::Mat image = cv::imread(input_filename); // 如果图像加载失败,则打印错误消息并返回 if (image.empty()) { printf("Failed to load input image.\n"); return -1; } // 彩色图像转换为灰度图像 cv::Mat gray_image; cv::cvtColor(image, gray_image, cv::COLOR_BGR2GRAY); // 进行高斯滤波去噪 cv::Mat blurred_image; cv::GaussianBlur(gray_image, blurred_image, cv::Size(5, 5), 0); // 计算直方图 cv::Mat1f hist; int histSize = 256; float range[] = {0, 256}; const float* histRange = {range}; cv::calcHist(&blurred_image, 1, 0, cv::Mat(), hist, 1, &histSize, &histRange); // 查找双峰的峰值 int peak1 = 0; // 第一个峰值 int peak2 = 0; // 第二个峰值 int maxPeak = 0; // 最大峰值 bool foundPeak1 = false; bool foundPeak2 = false; //遍历直方图,查找双峰的峰值 for (int i = 1; i < histSize - 1; i++) { float prev = hist(i - 1); float curr = hist(i); float next = hist(i + 1); if (curr > prev && curr > next && curr > maxPeak) { maxPeak = curr; if (!foundPeak1) { peak1 = i; foundPeak1 = true; } else { peak2 = i; foundPeak2 = true; } } } // 根据找到的两个峰值的位置,计算出阈值 if (foundPeak1 && foundPeak2) { threshold = (peak1 + peak2) / 2; } // 调用函数进行JPEG图像二值化处理 jpeg_to_binary(input_filename, output_filename, threshold); printf("JPEG image binarization using bimodal method completed.\n"); return 0; }
#include <opencv2/opencv.hpp> void jpeg_to_binary(const char* input_filename, const char* output_filename) { // 以灰度模式加载JPEG图像 cv::Mat image = cv::imread(input_filename, cv::IMREAD_GRAYSCALE); if (image.empty()) { printf("Failed to load input image.\n"); return; } cv::Mat binary_image; // 应用OTSU法进行二值化处理 cv::threshold(image, binary_image, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); // 将二值化图像保存为JPEG格式 cv::imwrite(output_filename, binary_image); image.release(); binary_image.release(); } int main() { const char* input_filename = "demo.jpg"; const char* output_filename = "output.jpg"; // 调用函数进行JPEG图像二值化处理 jpeg_to_binary(input_filename, output_filename); printf("JPEG image binarization using OTSU method completed.\n"); return 0; }
import cv2 def jpeg_to_binary(input_filename, output_filename, threshold): image = cv2.imread(input_filename, cv2.IMREAD_GRAYSCALE) if image is None: print("Failed to load input image.") return _, binary_image = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY) cv2.imwrite(output_filename, binary_image) print("JPEG image binarization using average method completed.") input_filename = "demo.jpg" output_filename = "output.jpg" threshold = 128 jpeg_to_binary(input_filename, output_filename, threshold)
import cv2 import numpy as np def jpeg_to_binary(input_filename, output_filename): image = cv2.imread(input_filename, cv2.IMREAD_GRAYSCALE) if image is None: print("Failed to load input image.") return # 计算直方图 hist = cv2.calcHist([image], [0], None, [256], [0, 256]) # 查找双峰的峰值 _, maxVal, _, maxLoc = cv2.minMaxLoc(hist) hist = hist.astype(float) hist /= maxVal peak1 = 0 peak2 = 0 maxPeak = 0 foundPeak1 = False foundPeak2 = False for i in range(1, 255): prev = hist[i-1] curr = hist[i] next = hist[i+1] if curr > prev and curr > next and curr > maxPeak: maxPeak = curr if not foundPeak1: peak1 = i foundPeak1 = True else: peak2 = i foundPeak2 = True # 计算阈值 threshold = (peak1 + peak2) // 2 # 使用阈值将图像进行二值化处理 _, binary_image = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY) cv2.imwrite(output_filename, binary_image) print("JPEG image binarization using bimodal method completed.") input_filename = "demo.jpg" output_filename = "output.jpg" jpeg_to_binary(input_filename, output_filename)
import cv2 def jpeg_to_binary(input_filename, output_filename): image = cv2.imread(input_filename, cv2.IMREAD_GRAYSCALE) if image is None: print("Failed to load input image.") return _, binary_image = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) cv2.imwrite(output_filename, binary_image) print("JPEG image binarization using OTSU method completed.") input_filename = "demo.jpg" output_filename = "output.jpg" jpeg_to_binary(input_filename, output_filename)
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。