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()
相关推荐
千宇宙航5 小时前
闲庭信步使用图像验证平台加速FPGA的开发:第二十一课——高斯下采样后图像还原的FPGA实现
图像处理·计算机视觉·fpga开发
顾随9 小时前
(三)OpenCV——图像形态学
图像处理·人工智能·python·opencv·计算机视觉
格林威13 小时前
Baumer工业相机堡盟工业相机如何通过YoloV8模型实现人物识别(C#)
开发语言·人工智能·数码相机·yolo·计算机视觉·c#
Virgil13915 小时前
数据分布是如何影响目标检测精度的
人工智能·深度学习·yolo·目标检测·计算机视觉
CoovallyAIHub16 小时前
YOLO11 vs LMWP-YOLO:参数量-52.5%,mAP+22.07%,小型无人机的远距离检测
深度学习·算法·计算机视觉
zhongqu_3dnest1 天前
众趣SDK重磅升级:空间物联IOT新视界,赋能实景三维场景深度应用
人工智能·物联网·计算机视觉·3d·点云处理·点云扫描
Blossom.1181 天前
深度学习中的注意力机制:原理、应用与实践
人工智能·深度学习·神经网络·机器学习·生成对抗网络·计算机视觉·sklearn
Lum11041 天前
MER-Factory:多模态情感识别与推理数据集自动化工厂工具介绍
运维·人工智能·深度学习·计算机视觉·语言模型·自然语言处理·自动化
Mikowoo0071 天前
03_opencv_imwrite()函数
opencv·计算机视觉