이미지 선명화(Sharpening)는 블러 처리와 정반대되는 개념입니다. OpenCV 라이브러리를 사용해 이미지의 선명도를 조절하려면 먼저 가우시안 필터(Gaussian Filter)를 적용해 이미지를 부드럽게 만든 뒤, 원본 이미지에서 이 흐려진 버전을 빼주는 방식으로 구현합니다. 이 과정에서 Core.addWeighted() 메서드를 활용하면 알파(alpha)와 베타(beta) 가중치를 통해 선명도를 자유롭게 제어할 수 있습니다.
예제 코드
다음은 알파 값과 베타 값을 각각 조절할 수 있는 두 개의 슬라이더를 포함한 JavaFX 프로그램 예제입니다. 슬라이더를 움직이면 실시간으로 이미지의 선명도가 변경됩니다.
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
public class SharpnessJavaFX extends Application {
double contrast = 1;
private final int rtype = -1;
double alpha = 1;
double beta = 0;
Slider slider1;
Slider slider2;
Mat src = null;
public void start(Stage stage) throws IOException {
// OpenCV 핵심 네이티브 라이브러리 로딩
System.loadLibrary( Core.NATIVE_LIBRARY_NAME );
String file ="D:\\Image\\lamma2.jpg";
src = Imgcodecs.imread(file);
WritableImage writableImage = loadImage(src);
// 이미지 뷰 설정
ImageView imageView = new ImageView(writableImage);
imageView.setX(50);
imageView.setY(25);
imageView.setFitHeight(400);
imageView.setFitWidth(550);
imageView.setPreserveRatio(true);
Label label1 = new Label("Alpha value");
// 첫 번째 슬라이더 설정 (알파 값)
slider1 = new Slider(0.1, 3, 1);
slider1.setShowTickLabels(true);
slider1.setShowTickMarks(true);
slider1.setMajorTickUnit(1);
slider1.setBlockIncrement(0.05);
// 라벨 설정
Label label2 = new Label();
Label label3 = new Label("Beta value");
// 두 번째 슬라이더 설정 (베타 값)
slider2 = new Slider(-2, 2, 0);
slider2.setShowTickLabels(true);
slider2.setShowTickMarks(true);
slider2.setMajorTickUnit(1);
slider2.setBlockIncrement(.5);
// 라벨 설정
Label label4 = new Label();
// 첫 번째 슬라이더 리스너
slider1.valueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue <?extends Number>observable, Number oldValue, Number newValue){
try {
label2.setText("α-value: " + newValue);
alpha = newValue.doubleValue();
Mat dest = new Mat(src.rows(), src.cols(), src.type());
Imgproc.GaussianBlur(src, dest, new Size(0,0), 10);
Core.addWeighted(src, alpha, dest, beta, 0, dest);
imageView.setImage(loadImage(dest));
}
catch(Exception e) {
System.out.println("");
}
}
});
// 두 번째 슬라이더 리스너
slider2.valueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue <?extends Number>observable, Number oldValue, Number newValue){
try {
label4.setText("β-value: " + newValue);
beta = newValue.doubleValue();
Mat dest = new Mat(src.rows(), src.cols(), src.type());
Imgproc.GaussianBlur(src, dest, new Size(0,0), 10);
Core.addWeighted(src, alpha, dest, beta, 0, dest);
imageView.setImage(loadImage(dest));
}
catch(Exception e) {
System.out.println("");
}
}
});
// 세 컴포넌트를 배치하는 VBox
VBox vbox = new VBox();
vbox.setPadding(new Insets(20));
vbox.setSpacing(10);
vbox.getChildren().addAll(label1, slider1, label2, imageView, label3, slider2, label4);
// 씬(Scene) 객체 생성
Scene scene = new Scene(vbox, 600, 560);
stage.setTitle("Altering the sharpness");
stage.setScene(scene);
stage.show();
}
public WritableImage loadImage(Mat image) throws IOException {
MatOfByte matOfByte = new MatOfByte();
Imgcodecs.imencode(".jpg", image, matOfByte);
// 인코딩된 Mat을 바이트 배열에 저장
byte[] byteArray = matOfByte.toArray();
// 이미지 표시
InputStream in = new ByteArrayInputStream(byteArray);
BufferedImage bufImage = ImageIO.read(in);
System.out.println("Image Loaded");
WritableImage writableImage = SwingFXUtils.toFXImage(bufImage, null);
return writableImage;
}
public static void main(String args[]) {
launch(args);
}
}입력 이미지

출력 결과

핵심 동작 원리
이 프로그램의 선명도 조절 로직은 크게 세 단계로 이루어집니다.
1. 가우시안 블러 적용 — Imgproc.GaussianBlur() 메서드로 원본 이미지를 흐리게 만듭니다. 여기서는 시그마 값 10으로 강하게 블러링합니다.
2. 가중합 연산 — Core.addWeighted() 메서드로 원본과 블러된 이미지를 가중치 기반으로 합성합니다. 알파 값이 클수록 원본의 비중이 커져 이미지가 더 선명해 보이고, 베타 값은 블러 성분의 비중을 결정합니다.
3. 실시간 UI 반영 — JavaFX 슬라이더의 ChangeListener가 값 변화를 감지할 때마다 위 연산을 수행하고 화면을 갱신합니다.
이처럼 OpenCV의 행렬 연산 기능과 JavaFX의 인터랙티브 UI를 결합하면, 사용자가 직관적으로 선명도를 조절할 수 있는 데스크톱 이미지 편집 도구를 손쉽게 만들 수 있습니다.