哪里有手機網(wǎng)站定制服務(wù)器手機怎么自己制作網(wǎng)頁
對一個數(shù)字圖像處理系統(tǒng)來說,一般的處理過程為三個步驟:圖像預(yù)處理、特征抽取、圖像識別和分析。圖像的點運算就是預(yù)處理過程中的重要一步,點運算是對圖像的灰度級進(jìn)行變換。
圖像點運算概念
?點運算是指對圖像的每個像素依次進(jìn)行相同的灰度變換,然后得出的圖像就是輸出圖像。那么對于RGB彩色圖像的點運算是怎么處理的呢?是有多種的處理方案的,有簡單對R、G、B分別操作的,也有轉(zhuǎn)換為灰度操作,再還原為彩色的,讀者可以自行查閱資料。
點運算幾個形式以及作用
對比度以及亮度調(diào)整
線性變換:通過y=kx+d實現(xiàn)
- 對比度增強(k>1) 拓展灰度分布范圍,突出細(xì)節(jié)差異,例如醫(yī)學(xué)影像中通過增大斜率是的病灶邊緣更清晰
- 亮度調(diào)節(jié)(k=1 && d != 0) 整體平移灰度值,用于低曝光圖像全局提亮
- 灰度反轉(zhuǎn)(k=?1,d=255):用于突出暗區(qū)細(xì)節(jié),如X光片中的骨骼結(jié)構(gòu)顯示。
分段線性變換
對特定灰度區(qū)間進(jìn)行選擇性拉伸,例如增強中間灰度區(qū)域的對比度,保留極亮/極暗區(qū)域的信息
動態(tài)范圍優(yōu)化
非線性變換
- 對數(shù)變換:壓縮高灰度區(qū)域,擴展低灰度細(xì)節(jié),適用于傅里葉頻譜圖等動態(tài)范圍過寬的圖像。
- 伽馬變換(冪次變換):
- γ<1:擴展暗部細(xì)節(jié),適用于背光場景的修復(fù)。
- γ>1:增強亮部層次,如衛(wèi)星云圖中云層紋理的區(qū)分
- 灰度直方圖均衡化
像素、灰度、灰度級、灰度直方圖
像素點是最小的圖像單元,一張圖片由很多的像素點組成。比如之后我們用到的Kun圖片就是939*554像素的。
灰度是表明圖像明暗的數(shù)值,即黑白圖像中點的顏色深度,范圍一般從0到255,白色為255 ,黑色為0,故黑白圖片也稱灰度圖像。灰度值指的是單個像素點的亮度。灰度值越大表示越亮。
灰度就是沒有色彩,RGB色彩分量全部相等。圖像的灰度化就是讓像素點矩陣中的每一個像素點都滿足關(guān)系:R=G=B,此時的這個值叫做灰度值。如RGB(100,100,100)就代表灰度值為100,RGB(50,50,50)代表灰度值為50
灰度值與像素值的關(guān)系:黑白圖像的灰度值就是像素值,彩色圖像則需要進(jìn)行一定的映射才可以得到灰度
灰度級:不同灰度的種類個數(shù)
灰度直方圖:對灰度級進(jìn)行統(tǒng)計
灰度直方圖均衡化實操(JAVA實例附源碼)
JavaSwing進(jìn)行圖片展示
先上網(wǎng)找一張我家哥哥的圖片,初始化一下Java項目,然后就可以準(zhǔn)備開始了。
利用JavaSwing進(jìn)行圖形化顯示,注意代碼里邊標(biāo)注的,他的圖片讀取默認(rèn)目錄是項目根目錄:
代碼實現(xiàn)
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.*;
public class Main {public static void main(String[] args) {SwingUtilities.invokeLater(() -> {JFrame frame = new JFrame("圖片展示");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setSize(800, 600);Image image=null;try {//這里注意讀取的默認(rèn)位置是根目錄InputStream is = new BufferedInputStream(new FileInputStream("./asset/caixukun.png"));Image originImage = ImageIO.read(is);image=originImage;//image=originImage.getScaledInstance(400,300,Image.SCALE_SMOOTH);// BufferedImage bimage=utils.toBufferedImage(image);
// WritableRaster raster=bimage.getRaster();
// ColorModel colorModel=bimage.getColorModel();//image=originImage;// 使用JLabel顯示圖片JLabel label = new JLabel(new ImageIcon(image));frame.add(new JScrollPane(label));frame.pack();frame.setLocationRelativeTo(null);frame.setVisible(true);} catch (IOException e) {// 增強錯誤處理JOptionPane.showMessageDialog(frame,"圖片加載失敗: " + e.getMessage(),"錯誤",JOptionPane.ERROR_MESSAGE);System.exit(1);}});}
}
觀察圖片效果良好,這里讀者可能會想把圖像規(guī)范到一個固定的長寬比如400*300,作者在這里嘗試調(diào)用了Image的scale庫,最后的結(jié)果是bug預(yù)警,后續(xù)調(diào)試觀察RGB圖像矩陣的時候像素值發(fā)生巨變,通道數(shù)也變成了4,總而言之就是不太看的明白的故障,最終取消選擇手工裁剪圖片尺寸。
好,這里打上斷點查看一下圖片格式:
在raster(柵格)屬性下的data欄目,發(fā)現(xiàn)里邊包括了RGB三個通道的所有點。
彩色圖像映射成灰度圖像
- 加權(quán)平均法:由于人眼對不同顏色的敏感度不同,對綠色的最高,紅色次之,對藍(lán)色最低,所以通常的權(quán)重設(shè)置是:R=0.299, G=0.587; B=0.114;
- 上課講了U和V????
- 簡單平均法:G=(R+G+B)/3;
這里采用加權(quán)映射方法
代碼實現(xiàn)
public static BufferedImage toGrayscaleImage(Image image) {// 先將 Image 轉(zhuǎn)換為 BufferedImageBufferedImage bufferedImage = toBufferedImage(image);int width = bufferedImage.getWidth();int height = bufferedImage.getHeight();for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {Color color = new Color(bufferedImage.getRGB(x, y));int grayLevel = (int) (color.getRed() * 0.299 + color.getGreen() * 0.587 + color.getBlue() * 0.114);Color grayColor = new Color(grayLevel, grayLevel, grayLevel);bufferedImage.setRGB(x, y, grayColor.getRGB());}}return bufferedImage;}
這里返回的bufferImage是image的子類,可以在main函數(shù)里直接賦值給image看看轉(zhuǎn)換之后的效果:
灰度直方圖繪制
utils里邊增添Java代碼:
代碼實現(xiàn)[Bug預(yù)警]
public static BufferedImage drawGrayscaleHistogram(BufferedImage grayscaleImage) {int width = grayscaleImage.getWidth();int height = grayscaleImage.getHeight();// 統(tǒng)計每個灰度級的像素數(shù)量int[] histogram = new int[256];for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {Color color = new Color(grayscaleImage.getRGB(x, y));int grayLevel = color.getRed();histogram[grayLevel]++;}}// 找到直方圖中的最大像素數(shù)量int maxCount = 0;for (int count : histogram) {if (count > maxCount) {maxCount = count;}}int histogramWidth = 512;int histogramHeight = 400;BufferedImage histogramImage = new BufferedImage(histogramWidth, histogramHeight, BufferedImage.TYPE_INT_RGB);Graphics2D g2d = histogramImage.createGraphics();g2d.setColor(Color.WHITE);g2d.fillRect(0, 0, histogramWidth, histogramHeight);g2d.setColor(Color.BLACK);for (int i = 0; i < 256; i++) {int barHeight = (int) ((double) histogram[i] / maxCount * histogramHeight);g2d.drawLine(i, histogramHeight, i, histogramHeight - barHeight);}g2d.dispose();return histogramImage;}
直方圖展示
其實這個圖畫的不是很好看,點開數(shù)值看一下分布,各個地方都有,應(yīng)該是縱軸尺度太大了。
對數(shù)概率代碼實現(xiàn)(沒有意義,只是證明一下各個像素其實都有)
public static BufferedImage drawGrayscaleHistogram(BufferedImage grayscaleImage) {int width = grayscaleImage.getWidth();int height = grayscaleImage.getHeight();// 統(tǒng)計每個灰度級的像素數(shù)量int[] histogram = new int[256];for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {Color color = new Color(grayscaleImage.getRGB(x, y));int grayLevel = color.getRed();histogram[grayLevel]++;}}// 找到直方圖中的最大像素數(shù)量int maxCount = 0;for (int count : histogram) {if (count > maxCount) {maxCount = count;}}int histogramWidth = 512;int histogramHeight = 400;BufferedImage histogramImage = new BufferedImage(histogramWidth, histogramHeight, BufferedImage.TYPE_INT_RGB);Graphics2D g2d = histogramImage.createGraphics();g2d.setColor(Color.WHITE);g2d.fillRect(0, 0, histogramWidth, histogramHeight);g2d.setColor(Color.BLACK);double logMaxCount = Math.log(maxCount + 1);for (int i = 0; i < 256; i++) {// 計算對數(shù)尺度下的柱子高度double logCount = Math.log(histogram[i] + 1);int barHeight = (int) ((logCount / logMaxCount) * histogramHeight);g2d.drawLine(i * 2, histogramHeight, i * 2, histogramHeight - barHeight);}g2d.dispose();return histogramImage;}
注:最后發(fā)現(xiàn)這么稀疏的原因是因為int除法向下取整,全是0:修改utils里邊的這一小部分
for (int i = 0; i < 256; i++) {//因該是這里的問題int barHeight = (int)(((histogram[i] / (double)maxCount) * histogramHeight));g2d.drawLine(i * 2, histogramHeight, i * 2, histogramHeight - barHeight);}
經(jīng)過調(diào)試還是因為這張圖本身不是太好,確實太集中了,沒能畫出好圖,讀者感興趣可以換張圖,代碼全都相同(下邊貼一張作者換圖之后的直方圖)
換圖之后的效果
灰度直方圖分布含義
觀察灰度直方圖是為了對其進(jìn)行操作,在文章開始就有提到,圖像點運算是為了去做數(shù)據(jù)增強。
這里就得牽扯到一個對于灰度的理解。
整體灰度都偏向于一個區(qū)域的話大多數(shù)時候不是有利的,會讓畫面看起來灰蒙蒙
較黑的圖像
較亮的圖像
灰度直方圖均衡化
【直方圖均衡化的核心思想:亮度和對比度的改善】
并非灰度很高的圖片或者灰度很低的圖片看起來就會很清晰,而是對比度高的圖片看起來才會清晰,特征分明,這也就方便了各種識別任務(wù)。
過亮或者過暗的圖片經(jīng)過直方圖均衡化會變得清晰。
直方圖均衡化是一種簡單有效的圖像增強技術(shù),通過改變圖像的直方圖來改變圖像中各像素的灰度,主要用于增強動態(tài)范圍偏小的圖像的對比度。原始圖像由于其灰度分布可能集中在較窄的區(qū)間,造成圖像不夠清晰。例如,過曝光圖像的灰度級集中在高亮度范圍內(nèi),而曝光不足將使圖像灰度級集中在低亮度范圍內(nèi)。采用直方圖均衡化,可以把原始圖像的直方圖變換為均勻分布(均衡)的形式,這樣就增加了像素之間灰度值差別的動態(tài)范圍,從而達(dá)到增強圖像整體對比度的效果。換言之,直方圖均衡化的基本原理是:對在圖像中像素個數(shù)多的灰度值(即對畫面起主要作用的灰度值)進(jìn)行展寬,而對像素個數(shù)少的灰度值(即對畫面不起主要作用的灰度值)進(jìn)行歸并,從而增大對比度,使圖像清晰,達(dá)到增強的目的,如下圖所示。
如果一幅圖像整體偏暗或者偏亮,那么直方圖均衡化的方法很適用。但直方圖均衡化是一種全局處理方式,它對處理的數(shù)據(jù)不加選擇,可能會增加背景干擾信息的對比度并且降低有用信號的對比度(如果圖像某些區(qū)域?qū)Ρ榷群芎?#xff0c;而另一些區(qū)域?qū)Ρ榷炔缓?#xff0c;那采用直方圖均衡化就不一定適用)。此外,均衡化后圖像的灰度級減少,某些細(xì)節(jié)將會消失;某些圖像(如直方圖有高峰),經(jīng)過均衡化后對比度不自然的過分增強。針對直方圖均衡化的缺點,已經(jīng)有局部的直方圖均衡化方法出現(xiàn)。
灰度直方圖均衡化適用范圍
直方圖均衡化在需要增強局部細(xì)節(jié)的任務(wù)中表現(xiàn)優(yōu)異(比如識別任務(wù)),但其對全局亮度分布的破壞性處理可能影響依賴整體光照語義的應(yīng)用(如圖生文)
貼幾組均衡化的例子
左邊是均衡化之前,右邊是之后
發(fā)現(xiàn)問題
上邊的一系列例子里,我們發(fā)現(xiàn)最后一張黑貓圖片是個例外,試圖發(fā)現(xiàn)共性找出第二張可能有這種情況的例子;這樣的情況是由于黑白圖片嗎,是由于過暗的地方有光影嗎?現(xiàn)在是什么情況呢?可能原圖的色調(diào)太單一,就那么幾個,所以均衡化之后
似乎黑白圖片效果也很好和另一張有光影的圖片也很好
灰度直方圖均衡化思考
灰度直方圖呈現(xiàn)的是一種圖像整體的狀態(tài),因為灰度級統(tǒng)計的是全部的圖片,所以造成上述結(jié)果的應(yīng)該是一種“局部的特征”。我要不要找一找他的灰度分布情況呢?
回答:實時證明,均衡化對于噪聲的魯棒性很差,有噪聲的時候就會導(dǎo)致顆粒感很足,出來的畫面會很奇怪。可以檢測一下圖片里的噪聲
不難看出,不是所有的灰度圖像經(jīng)過均衡化都會變得更加清晰的,那么灰度直方圖均衡化到底在哪些圖上可能效果比較好呢?我們看看他的操作方案
灰度直方圖均衡化公式解釋
如果把映射的分子分母上的-cdfMin去掉,那是不是立馬就能看懂了,就是一個拉伸;一個簡單的解釋是為了讓映射范圍有0,所以這個公式的映射范圍就變成了0~255之間。
最后的最后,為了方便進(jìn)行對比展示,搭建了一個UI界面,代碼如下:
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Arrays;
public class ImageProcessorGUI extends JFrame {private BufferedImage originalImage;private BufferedImage processedImage;private JLabel imageLabel;private JComboBox<String> operationComboBox;private JButton fileSelectButton;private final String[] OPERATIONS = {"原始圖像", "灰度轉(zhuǎn)換", "灰度直方圖","均衡化后圖片", "均衡化后直方圖", "噪聲點統(tǒng)計"};public ImageProcessorGUI() {super("圖像處理器");initializeUI();loadDefaultImage();}private void initializeUI() {setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setSize(1000, 800);setLayout(new BorderLayout());// 控制面板JPanel controlPanel = new JPanel();fileSelectButton = new JButton("選擇圖片");operationComboBox = new JComboBox<>(OPERATIONS);JButton processButton = new JButton("執(zhí)行");JButton refreshButton = new JButton("重置");controlPanel.add(fileSelectButton);controlPanel.add(new JLabel("選擇操作:"));controlPanel.add(operationComboBox);controlPanel.add(processButton);controlPanel.add(refreshButton);// 圖像顯示區(qū)域imageLabel = new JLabel() {@Overridepublic Dimension getPreferredSize() {return getIcon() == null ? new Dimension(0, 0) : super.getPreferredSize();}};imageLabel.setHorizontalAlignment(SwingConstants.CENTER);imageLabel.setVerticalAlignment(SwingConstants.CENTER);JScrollPane scrollPane = new JScrollPane(imageLabel);add(controlPanel, BorderLayout.NORTH);add(scrollPane, BorderLayout.CENTER);// 事件監(jiān)聽fileSelectButton.addActionListener(e -> selectImageFile());processButton.addActionListener(e -> processImage());refreshButton.addActionListener(e -> showOriginalImage());}private void loadDefaultImage() {loadImage("./asset/tanzhilang.png");}private void selectImageFile() {JFileChooser fileChooser = new JFileChooser();fileChooser.setCurrentDirectory(new File("./asset"));fileChooser.setFileFilter(new FileNameExtensionFilter("圖像文件", "jpg", "jpeg", "png", "gif"));if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {loadImage(fileChooser.getSelectedFile().getAbsolutePath());}}private void loadImage(String path) {try (InputStream is = new BufferedInputStream(new FileInputStream(path))) {originalImage = ImageIO.read(is);showOriginalImage();} catch (IOException ex) {JOptionPane.showMessageDialog(this, "圖片加載失敗: " + ex.getMessage(), "錯誤", JOptionPane.ERROR_MESSAGE);}}private void showOriginalImage() {processedImage = originalImage;updateImageDisplay();}private void processImage() {int selectedIndex = operationComboBox.getSelectedIndex();try {switch (selectedIndex){case 1:processedImage = ImageUtils.toGrayscale(originalImage);break;case 2:processedImage=utils.drawGrayscaleHistogram(ImageUtils.toGrayscale(originalImage));break;case 3:processedImage = utils.histogramEqualization(ImageUtils.toGrayscale(originalImage));break;case 4:processedImage = utils.histogramEqualization(ImageUtils.toGrayscale(originalImage));processedImage=utils.drawGrayscaleHistogram(processedImage);break;case 5:int noisePoints = utils.countNoisePoints(ImageUtils.toGrayscale(originalImage), 30);JOptionPane.showMessageDialog(this, "噪聲點數(shù)量: " + noisePoints);return;default:processedImage = originalImage;}updateImageDisplay();} catch (Exception ex) {JOptionPane.showMessageDialog(this, "處理失敗: " + ex.getMessage(), "錯誤", JOptionPane.ERROR_MESSAGE);}}private void updateImageDisplay() {if (processedImage == null) return;Dimension viewSize = ((JScrollPane) getContentPane().getComponent(1)).getViewport().getExtentSize();int maxWidth = Math.max(viewSize.width - 50, 100);int maxHeight = Math.max(viewSize.height - 50, 100);Image scaledImage = processedImage.getScaledInstance(Math.min(processedImage.getWidth(), maxWidth),Math.min(processedImage.getHeight(), maxHeight),Image.SCALE_SMOOTH);imageLabel.setIcon(new ImageIcon(scaledImage));imageLabel.revalidate();imageLabel.repaint();}public static void main(String[] args) {SwingUtilities.invokeLater(() -> {ImageProcessorGUI app = new ImageProcessorGUI();app.setLocationRelativeTo(null);app.setVisible(true);});}
}class ImageUtils {// 灰度轉(zhuǎn)換public static BufferedImage toGrayscale(BufferedImage image) {BufferedImage result = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);for (int y = 0; y < image.getHeight(); y++) {for (int x = 0; x < image.getWidth(); x++) {Color color = new Color(image.getRGB(x, y));int gray = (int) (color.getRed() * 0.299 + color.getGreen() * 0.587 + color.getBlue() * 0.114);result.setRGB(x, y, new Color(gray, gray, gray).getRGB());}}return result;}}