Amplitude Modulated (AM) Digital Halftoning

Amplitude Modulated (AM) Digital Halftoning

Key Steps

  1. Generate a threshold array

    • Typically small, such as 8×8 , 12×12 , or 16×16
    • Contains arranged threshold values used to modulate dot growth
  2. Tile the threshold array over the image

    • Repeat it across the entire image so each input pixel is matched to a threshold
    • The array acts like a stencil for comparing tones
  3. Compare each image pixel to the matching threshold value

    • For each pixel:
      Output={255if Intensity>Threshold0otherwise \text{Output} = \begin{cases} 255 & \text{if } \text{Intensity} > \text{Threshold} \\ 0 & \text{otherwise} \end{cases} Output={2550if Intensity>Thresholdotherwise
    • This produces a binary result per pixel: "on" (255) or "off" (0)
  4. Resulting pixels form clustered dots

    • Dot size grows with tone intensity
    • Dark tones → more "on" pixels in a tile; light tones → fewer

Note:

In AM halftoning , threshold values are usually arranged to produce round, square, or elliptical dot shapes and are reused across the image to maintain a regular grid structure.


detail

Steps to Build a Threshold Matrix

It's a small grid (like 8×8 or 16×16) containing values from 0 to 255 that define the dot shape and growth order. When tiled across an image, it tells each pixel when to turn black based on its tone.

🔹 1. Choose Cell Size & Screen Parameters

  • Pick cell size (e.g., 8×8 pixels)
  • Select screen frequency (dots/inch)
  • Choose a screen angle (dot orientation)

🔹 2. Assign Normalized Coordinates

Inside the cell:

  • Each pixel gets (x, y) ranging from −1 to +1

  • You rotate the coordinates to match the screen angle:

    math 复制代码
    x′ = x * cos(θ) − y * sin(θ)  
    y′ = x * sin(θ) + y * cos(θ)

🔹 3. Apply Spot Function

Choose a cost function to shape the dot---examples:

  1. Circle: x′2+y′2x'^2 + y'^2x′2+y′2
  2. Line: x′x'x′
  3. Square : max⁡(x′,y′)\max(x', y')max(x′,y′)
  4. Diamond: ∣x∣+∣y′∣|x|+ |y'|∣x∣+∣y′∣

This gives each pixel a cost, which defines when it should turn black as tone increases.

🔹 4. Histogram Equalization

  • Sort the cost values from low to high
  • Map them linearly to threshold values from 0--255
  • Ties (same cost) can be broken by angle to center or manually

🔹 5. Assemble the Matrix

You now have a full threshold matrix that:

  • Encodes dot shape
  • Responds smoothly to tone
  • Can be tiled across any grayscale image for halftoning

Would you like help building one programmatically or testing how different spot functions affect the pattern? I can simulate a couple matrix styles to show their real-world effect!

AM Halftoning Scenario Setup

Given a grayscale image: 300×300 pixels , and a printer with 600 DPI resolution, we want to convert the image into a halftone while:

  • Maintaining physical print size
  • Adapting to printer grid
  • Computing halftone cell size via screen frequency

Step 1: Image DPI and Physical Size

If the image is created at 300 DPI:

  • Physical size = \\frac{300 \\text{ pixels}}{300 \\text{ DPI}} = 1 \\text{ inch}

  • The printer uses 600 DPI , so to preserve the same printed size, we need to resample the image to:

    Resampled size=Original Pixel Width×Printer DPIImage DPI=300×600300=600 pixels \text{Resampled size} = \text{Original Pixel Width} × \frac{\text{Printer DPI}}{\text{Image DPI}} = 300 × \frac{600}{300} = 600 \text{ pixels} Resampled size=Original Pixel Width×Image DPIPrinter DPI=300×300600=600 pixels

Final resampled image: 600 × 600 pixels


Step 2: Screen Frequency and Cell Size

Screen frequency controls how many dot clusters (halftone cells) are printed per inch.

Let's say:

  • Screen frequency (LPI) = 60 lines/inch
  • Printer resolution (DPI) = 600 dots/inch

Then the cell size in pixels is:

Halftone Cell Size=Printer DPIScreen Frequency=60060=10 pixels \text{Halftone Cell Size} = \frac{\text{Printer DPI}}{\text{Screen Frequency}} = \frac{600}{60} = 10 \text{ pixels} Halftone Cell Size=Screen FrequencyPrinter DPI=60600=10 pixels

So each halftone cell covers:

  • 10 × 10 device pixels

The final image will have:

  • 60010=60 halftone cells per row or column \frac{600}{10} = 60 \text{ halftone cells per row or column} 10600=60 halftone cells per row or column
    Resulting in 60 × 60 dots forming the halftone pattern across the printed image

📌 Summary Table

Image DPI Printer DPI Screen Frequency (LPI) Resampled Size Cell Size (pixels) Dots Across Image
300 600 60 600 × 600 px 10 × 10 px 60 × 60

🛠️ Step-by-Step Guide (with Code)

⚙️ 1. Resample Image to Match DPI

Let's assume your screen frequency is 60 lines/inch → each halftone cell is:

python 复制代码
cell_size = int(600 / 60)  # = 10 pixels per cell

Now, convert your image to match printer resolution (600×600):

python 复制代码
import cv2

image = cv2.imread("input_image.jpg", cv2.IMREAD_GRAYSCALE)
image_resampled = cv2.resize(image, (600, 600), interpolation=cv2.INTER_CUBIC)

🧩 2. Define Spot Function (e.g., Circular)

python 复制代码
def spot_function(x, y):
    return x**2 + y**2

🔄 3. Tile Image & Apply Halftoning

python 复制代码
import numpy as np

def am_halftone(image, cell_size, angle_degrees):
    angle = np.deg2rad(angle_degrees)
    height, width = image.shape
    output = np.zeros_like(image)

    for i in range(0, height, cell_size):
        for j in range(0, width, cell_size):
            tile = image[i:i+cell_size, j:j+cell_size]
            cost_map = []
            for u in range(tile.shape[0]):
                for v in range(tile.shape[1]):
                    # Normalize coords
                    x = 2 * (u + 0.5)/cell_size - 1
                    y = 2 * (v + 0.5)/cell_size - 1
                    # Rotate
                    x_r = x * np.cos(angle) - y * np.sin(angle)
                    y_r = x * np.sin(angle) + y * np.cos(angle)
                    cost = spot_function(x_r, y_r)
                    cost_map.append((u, v, cost))
            # Histogram equalization
            cost_map.sort(key=lambda x: x[2])
            thresholds = np.linspace(0, 255, len(cost_map))
            for idx, (u, v, _) in enumerate(cost_map):
                thresh = thresholds[idx]
                output[i+u, j+v] = 0 if tile[u, v] > thresh else 255
    return output

📤 4. Run the Conversion

python 复制代码
halftone_image = am_halftone(image_resampled, cell_size=10, angle_degrees=45)
cv2.imwrite("halftone_output.png", halftone_image)

🔍 Key Insight

Resampling makes your image match the printer's pixel grid, so that each halftone cell aligns with real device pixels. That's critical---otherwise your dot shapes will be distorted or irregular.

Would you like to experiment with elliptical spot functions or visualize how dot shapes evolve when you change resolution or screen frequency? We can make a little halftoning playground together!

附录

符号说明

1. Physical size

  • Definition: The physical dimensions of an image when printed.

  • Units: Inches (in)

  • Formula:

    Physical Width (in)=Pixel WidthDPI,Physical Height (in)=Pixel HeightDPI \text{Physical Width (in)} = \frac{\text{Pixel Width}}{\text{DPI}}, \quad \text{Physical Height (in)} = \frac{\text{Pixel Height}}{\text{DPI}} Physical Width (in)=DPIPixel Width,Physical Height (in)=DPIPixel Height

  • Example: An image with 3000×2400 pixels at 300 DPI has a physical size of 10×8 inches.


2. DPI (Dots Per Inch)

  • Definition: How many dots a printer can place in one inch when printing.
  • Used for: Output resolution (printing).
  • Note: Often stored in image metadata (like TIFF, JPEG).
  • Common values: 72 (screen), 300 (high quality print), 600+ (laser printer).

3. PPI (Pixels Per Inch)

  • Definition: The pixel density of a digital image or screen---how many pixels are packed into one inch.

  • Used for: Screen/display resolution.

  • Clarification:

    • In images, PPI and DPI are often used interchangeably, but:

      • DPI is output-oriented (printing),
      • PPI is input/display-oriented (screens, raster images).

4. Resolution

  • Definition: General term referring to the level of detail in an image.

  • Can mean:

    • DPI (for physical output/print),
    • PPI (for screen/display),
    • Or pixel dimensions (width × height in pixels).

5. Pixel array size (pixel dimensions)

  • Definition: Number of pixels that make up the image.

  • Components:

    • Pixel Width: number of columns
    • Pixel Height: number of rows
  • Units: Pixels (px)

  • Example: 1920×1080 means 1920 columns and 1080 rows of pixels.


🔁 Relationships

If you know any two of:

  • pixel dimensions,
  • physical size,
  • resolution (DPI or PPI),

...you can compute the third.

Example:
  • Pixel size: 6000×4000
  • DPI: 300
    → Physical size = 20×13.33 inches

Summary Table

Term Definition Unit Applies To
Physical Size Size when printed Inches Print
DPI Dots per inch (printer dots) Dots/inch Print device
PPI Pixels per inch (image/screen density) Pixels/in Screen/Image
Resolution Level of detail DPI/PPI Varies
Pixel Size Number of pixels in width/height Pixels Image data

参考文献

https://engineering.purdue.edu/\~bouman/ece637/notes/

https://github.com/philgyford/python-halftone

介绍https://shankhya.github.io/musings/halftoning.pdf

https://stackoverflow.com/questions/10572274/how-to-create-cmyk-halftone-images-from-a-color-image/10575940#10575940

https://github.com/tfuxu/dither-go/tree/977f24c2d937eaff404f15270f29977c0719f5f8

研究人

https://ttwong12.github.io/

ReversibleHalftoning代码

https://github.com/MenghanXia/ReversibleHalftoning

艺术半调 https://github.com/setanarut/halftonism

源码

python 复制代码
import numpy as np
import cv2

def spot_function(x, y, mode='circle'):
    """
    Spot function for AM halftoning.
    mode: 'circle', 'line', 'square', 'diamond'
    """
    if mode == 'circle':
        return x**2 + y**2
    elif mode == 'line':
        return x
    elif mode == 'square':
        return max(abs(x), abs(y))
    elif mode == 'diamond':
        return abs(x) + abs(y)
    # 可扩展更多形状
    else:
        raise ValueError(f"Unknown spot function mode: {mode}")

def am_halftone(image, cell_size, angle_degrees, spot_mode='circle'):
    angle = np.deg2rad(angle_degrees)
    height, width = image.shape
    output = np.zeros_like(image)

    # 预先计算cost_map和thresholds
    cost_map = []
    for u in range(cell_size):
        for v in range(cell_size):
            x = 2 * (u + 0.5) / cell_size - 1
            y = 2 * (v + 0.5) / cell_size - 1
            x_r = x * np.cos(angle) - y * np.sin(angle)
            y_r = x * np.sin(angle) + y * np.cos(angle)
            cost = spot_function(x_r, y_r, mode=spot_mode)
            cost_map.append((u, v, cost))
    cost_map.sort(key=lambda x: x[2])
    thresholds = np.linspace(0, 255, len(cost_map))

    for i in range(0, height, cell_size):
        for j in range(0, width, cell_size):
            tile = image[i:i+cell_size, j:j+cell_size]
            for idx, (u, v, _) in enumerate(cost_map):
                if u < tile.shape[0] and v < tile.shape[1]:
                    thresh = thresholds[idx]
                    output[i+u, j+v] = 0 if tile[u, v] < thresh else 255
    return output

def main():
    import argparse
    parser = argparse.ArgumentParser(description="AM Halftoning")
    parser.add_argument("input_image", help="Path to input image (grayscale or RGB)")
    parser.add_argument("--cell_size", type=int, default=8, help="Cell size for halftoning")
    parser.add_argument("--angle", type=float, default=0, help="Screen angle for grayscale or Red channel")
    parser.add_argument("--angle_g", type=float, default=15, help="Screen angle for Green channel (RGB only)")
    parser.add_argument("--angle_b", type=float, default=30, help="Screen angle for Blue channel (RGB only)")
    parser.add_argument("--angle_a", type=float, default=45, help="Screen angle for Blue channel (RGB only)")
    parser.add_argument("--spot_mode", type=str, default="diamond", choices=["circle", "line", "square", "diamond"], help="Spot function mode")
    parser.add_argument("--output", type=str, default="E:\\code\\python\\Halftoning\\image\\halftone_diamond.bmp", help="Output image file name")
    args = parser.parse_args()

    image = cv2.imread(args.input_image, cv2.IMREAD_UNCHANGED)
    if image is None:
        print(f"Failed to load image: {args.input_image}")
        return
    print("image shape:", image.shape)
    if len(image.shape) == 2:  # Grayscale
        halftone_image = am_halftone(image, cell_size=args.cell_size, angle_degrees=args.angle, spot_mode=args.spot_mode)
    elif len(image.shape) == 3 and image.shape[2] == 3:  # RGB
        angles = [args.angle, args.angle_g, args.angle_b]
        channels = cv2.split(image)
        halftoned_channels = []
        for ch, ang in zip(channels, angles):
            halftoned = am_halftone(ch, cell_size=args.cell_size, angle_degrees=ang, spot_mode=args.spot_mode)
            halftoned_channels.append(halftoned)
        halftone_image = cv2.merge(halftoned_channels)
    elif len(image.shape) == 3 and image.shape[2] == 4:  # RGBA (actually BGRA in OpenCV)
        angles = [args.angle_b, args.angle_g, args.angle]  # B, G, R
        b, g, r, a = cv2.split(image)
        bgra_channels = [b, g, r]
        halftoned_bgr = []
        for ch, ang in zip(bgra_channels, angles):
            halftoned = am_halftone(ch, cell_size=args.cell_size, angle_degrees=ang, spot_mode=args.spot_mode)
            halftoned_bgr.append(halftoned)
        halftone_image = cv2.merge(halftoned_bgr + [a])
    else:
        print("Unsupported image format.")
        return

    cv2.imwrite(args.output, halftone_image)
    print(f"Halftone image saved to {args.output}")

if __name__ == "__main__":
    main()
相关推荐
jndingxin5 小时前
OpenCV图像注册模块
人工智能·opencv·计算机视觉
荼蘼5 小时前
OpenCv(三)——图像平滑处理
人工智能·opencv·计算机视觉
Monkey PilotX6 小时前
机器人“ChatGPT 时刻”倒计时
人工智能·机器学习·计算机视觉·自动驾驶
程序猿小D16 小时前
【完整源码+数据集+部署教程】孔洞检测系统源码和数据集:改进yolo11-RetBlock
yolo·计算机视觉·毕业设计·数据集·yolo11·孔洞检测
图灵学术计算机论文辅导18 小时前
傅里叶变换+attention机制,深耕深度学习领域
人工智能·python·深度学习·计算机网络·考研·机器学习·计算机视觉
Struart_R1 天前
SpatialVLM和SpatialRGPT论文解读
计算机视觉·语言模型·transformer·大语言模型·vlm·视觉理解·空间推理
软件测试-阿涛1 天前
【AI绘画】Stable Diffusion webUI 常用功能使用技巧
人工智能·深度学习·计算机视觉·ai作画·stable diffusion
荼蘼1 天前
OpenCv(二)——边界填充、阈值处理
人工智能·opencv·计算机视觉
思通数据1 天前
AI视频监控:重构安防行业智能化新生态
人工智能·安全·目标检测·机器学习·计算机视觉·重构·数据挖掘
CV实验室2 天前
ICCV 2025 | 4相机干掉480机位?CMU MonoFusion高斯泼溅重构4D人体!
人工智能·数码相机·计算机视觉·论文